Skip to content

analytics: a filtered dataset measure asked on the cube door as CUBE.MEASURE, the name GET /analytics/meta lists, answers the unfiltered aggregate #25906

Closed

Description

@objectstack-fleet

Filing gate: ① product defect, a silent wrong number at a named location.
Reader: domain:spec / analytics seat, the next PR touching packages/services/service-analytics/src/strategies/native-sql-strategy.ts (compileClauses, the per-measure measureFilters lookup) and analytics-service.ts (namedQueryFields, same lookup).
Dedupe (open AND closed): "analytics cube query measure named with cube prefix drops its dataset measure filter, unfiltered sum" → 10 shown of 31, none on this; "measureFilters lookup qualified member name cube.measure native SQL strategy" → 10 shown of 13, none on this.
Business reason: a dashboard or API caller that uses the member name GET /analytics/meta lists for a filtered measure (say won revenue) is shown the total of every row under that name, with no error, and makes decisions on a wrong number. If nothing is done, every client that copies names from /meta keeps getting it.
reach: measured at AnalyticsService.query, the handler POST /analytics/query calls (packages/runtime/src/domains/analytics.ts), on the native-SQL strategy (SQLite dialect), at main and at the branch of PR #25903 (#25778). In-process at the service, not over HTTP.
type: Bug

What happens

A registered dataset with a filtered measure:

{ name: 'sales', object: 'deal',
  measures: [{ name: 'won_amount', aggregate: 'sum', field: 'amount', filter: { stage: 'won' } }] }

getMeta('sales') lists the measure as sales.won_amount, the CUBE.MEASURE spelling every cube member is listed under. Querying the cube door with each spelling:

measures statement run
['won_amount'] SELECT SUM(CASE WHEN stage = $1 THEN amount END) AS "won_amount" FROM "deal"
['sales.won_amount'] SELECT SUM(amount) AS "sales.won_amount" FROM "deal"

The second answers the total of every deal under the name of the won-deals measure. Nothing is refused and nothing says the filter was dropped.

Why

The member resolves through lookupMember / declaredMemberEntry, which strip the CUBE. prefix, but the measure's scoped filter is read as datasetScope.measureFilters[measure] with the spelling as asked, so the prefixed key misses. The read gate (namedQueryFields) uses the same key, so it does not judge the filter's fields either. That is not a bypass: the filter is not applied, so its fields are not read.

Expected

The prefixed spelling answers what the bare one does, the filter included, or is refused. #25778's duration measures already resolve the filter by the bare key (durationMeasureKey in duration-measure-sql.ts). Ordinary measures need the same resolution at both lookups. The DatasetExecutor path is unaffected, because it builds bare names.

Found by the independent review of PR #25903 (dataset duration measure). It is outside that card's scope.

Activity

  1. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p2 · domain:server · area:reports · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-26T03:53Z

    ⛔ Not a claim, ⛔ not a dispatch. finding removed.


    Generated by Claude Code

  2. added
    area:reportsBusiness reporting — dashboards, reports, the numbers a manager reads
    bugSomething isn't working
    domain:serverDomain lane — seat card indexed by label:pm:seat
    and removed on Oct 26, 2026
  3. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 6 · card thread
    Session: session_01R1q2xdtYgU6FKrZ7LgjPCC (the seat); the card thread is session_014YyczUJLen7od4GYKjPdY2
    Account: os-project-manager (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-hltghk (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#1
    Ruling-ref: the prefixed CUBE.MEASURE spelling answers what the bare one does, filter included: one member-name normalization feeds both measureFilters lookups (compileClauses, namedQueryFields); ⛔ not a refusal of the prefixed spelling
    File surface: packages/services/service-analytics/**, its tests, one changeset. ⛔ No packages/spec/** (stop on breach; explain in the report)
    Container & model: M, mode:thread, default model and effort (set at thread open)
    Clause-②: yes (expected; a narrowing only if the read gate now refuses a caller it served, which the dev measures and declares)
    Metadata-protocol: none
    Responsibility: measures: ['sales.won_amount'] runs SUM(amount) without the measure's filter, while ['won_amount'] applies it (card #25906, at AnalyticsService.query, native SQL, SQLite)
    Thread-read: none
    Premise: holds (read 2026-10-26T08:10Z at origin/main 05f7758f05: card open, unassigned, pm:queue, no other seat's claim; the dev reproduces red first)
    Serial constraints cleared: no open PR names #25906; the dev re-measures open PRs on its file surface before pr_create

  4. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 25906,
      "status": "done",
      "branch": "claude/project-thread-hltghk",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/26007",
      "session": "session_014YyczUJLen7od4GYKjPdY2",
      "premise_still_valid": true,
      "summary": "Every reader of a dataset measure's scoped filter now resolves the requested name through one rule, scopedMeasureKey (new scoped-measure-key.ts; durationMeasureKey delegates to it): the bare own key, else the name without its own cube's CUBE. qualifier. Seven readers moved: native compileClauses and its two routing declines (JSON-construct on unknown dialect, nested-relation), ObjectQL execute, its generateSql echo and filterMemberView, and the service's namedQueryFields (the read gate). DatasetExecutor builds bare names and is untouched; no raw measureFilters lookup is left in the package. Declared Clause-② as measured: no (narrowing), departing from the claim's expected yes, because the read gate now refuses (403 PERMISSION_DENIED) a qualified-spelling caller it used to answer with the unfiltered number; nothing is widened; contract review owed.",
      "tests": "Red first at 6c314cfe's base 05f7758f (helper present, readers unfixed): new cube-qualified-measure-filter.test.ts 5 failed / 2 passed (native received COALESCE(SUM(amount), 0) AS \"sales.won_amount\"; ObjectQL aggregation had no filter; echo SUM(amount); read gate answered the qualified spelling instead of PERMISSION_DENIED; canHandle true on a nested-relation filter). After fix 7/7. Ablation on committed 6c314cfe via scripts/ablation-replace.mjs: read-gate line reverted to the raw lookup in analytics-service.ts (anchor 1 to 0, blob f6a5b05b to c366dfb4), 1 failed / 6 passed (exactly the read-gate case), restored blob == HEAD and git diff HEAD empty (src-resolved suite, no dist step). pnpm --filter @objectstack/service-analytics test: Test Files 189 passed (189), Tests 4570 passed | 282 skipped; typecheck exit 0 (os-verify-lock VERDICT command-exit 0); eslint --no-inline-config packages/services/service-analytics exit 0. All at 6c314cfe.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 6c314cfe: 70 derived; each run with exit captured before any pipe: 68 exit 0; NOT MEASURED (exit 3, prerequisite): node scripts/check-plugin-teardown-shape.mjs --self-test (shallow clone; git fetch --unshallow failed with early EOF), pnpm check:dual-build-cjs-loads (needs a full workspace build). --ran reconciliation exit 0: 70 derived, 68 run, 2 NOT-MEASURED, 0 UNRUN. PR check-runs: in_progress at report time (not awaited, per os-dev.md).",
      "dogfood_battery": "6c314cfe · os-verify-lock VERDICT command-exit 1 (held 26m01s) · Test Files 1 failed | 295 passed | 3 skipped (299) · Tests 1 failed | 2522 passed | 18 skipped (2541) · the one failure is not this diff's: test/authz-probe-blind-spot.test.ts pins control handleI18nRequest at 3 occurrences in packages/runtime/src/domains/i18n.ts; the base tree 05f7758f already holds 4 (moved by 69fa23d6, PR #25956); this diff touches no packages/runtime or packages/qa path.",
      "mcp_calls": "0 — no MCP GitHub calls",
      "api_writes": "2 — fleet-write relay: pr_create objectstack-ai/objectstack#26007 (create + assignees leg, read-back identical 5459 bytes) run 40295522463; post-stamped comment on #25906 (this report). git push of claude/project-thread-hltghk is not a REST write.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: a · reach: dogfood battery on origin/main 05f7758f · evidence: test/authz-probe-blind-spot.test.ts 'control handleI18nRequest moved in packages/runtime/src/domains/i18n.ts: expected 4 to be 3'; git show 05f7758f:packages/runtime/src/domains/i18n.ts counts 4; last touched by 69fa23d6 (PR #25956) · dedupe words: authz probe blind-spot census, handleI18nRequest control moved, i18n.ts census pin, dogfood main red"
      ]
    }
  5. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed

    PR #26007 merged at 2026-10-26T11:05Z by the merge queue as 3117289002 on main (Fixes #25906; ACCEPT 6412750436).

    Reading Probe Result
    Queue membership git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26007-*' 0 refs
    origin/main by content git cat-file -e origin/main:packages/services/service-analytics/src/scoped-measure-key.ts present
    Ancestry git merge-base --is-ancestor 3117289002 origin/main holds

    State: this card closed by Fixes (completed); pm:dispatched removed.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:reportsBusiness reporting — dashboards, reports, the numbers a manager readsbugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions