Repository navigation
fix(service-analytics): the draft preview groups a bucketed time dimension and reads a qualified filter member as the live faces do; ObjectQL serves a backed RELATION.id dimension - #28742
Conversation
…eads qualified filter members; ObjectQL serves a backed relation.id - Draft preview: a timeDimensions entry with a granularity is grouped by its bucket whether or not it is also in dimensions, from the projectedDimensions set the live strategies read, and the bucket is in `fields`. - Draft preview: a where member spelled other than its column (OBJECT.region, CUBE.region, a dimension zone over region) is read from that column with its declared type, resolved by declaredMemberEntry over the shared collectFilterLeaves reader. - ObjectQL strategy: the cross-object path names each groupBy column once (resolveFkAttr for `<relation>.id`, a shared FK column across two dimensions), so the engine's duplicate-column refusal no longer fires on a query the native strategy serves. The refusal itself is unchanged. Real-SQLite three-face pins in preview-time-filter-fk-parity.test.ts. Co-Authored-By: Claude <noreply@anthropic.com>
📓 Docs Drift Check5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3148c8a2a57541aa897dfa5e3ffda14ef20d9d60 && git checkout 3148c8a2a57541aa897dfa5e3ffda14ef20d9d60
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin db357f6da9f787ff6285c20c70bc02a1bc138d35 400154ac098acb0cc64c2db977d659d0d660153d && git checkout -B drift-repro db357f6da9f787ff6285c20c70bc02a1bc138d35 && git merge --no-ff 400154ac098acb0cc64c2db977d659d0d660153d
node scripts/docs-audit/affected-docs.mjs --json db357f6da9f787ff6285c20c70bc02a1bc138d35 |
|
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH Contract reviewServed-tier: Read: cards #28690, #28691, #28692 (bodies, triage records 6558905217 / 6558915201 / 6558925351, claims 6559855795 / 6559874127 / 6559890848, os-dev-report 6560773894; no earlier contract record or ruling on any of the three), PR #28742 body and file list (5 files), the net diff ① Derived judgmentsAccept-set and public-surface changes the diff implies, each judged:
Card "Wanted" lines against the diff:
Tests: ② Semver level
Gate verdicts: the head's check-runs, read 2026-11-01T16:54:21Z: 48 check-runs, all completed — 35 ③ Boundary flags
Should-fix (non-blocking, not a condition of the verdict):
Must-fix: none. Implemented-by: VERDICT: PASS |
|
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH 席位核对 · ACCEPT · PR #28742 · 2026-11-01T16:55ZHead-sha: 维护者速读(席位终稿)
核对六项
|
domain:server#3 · os-project-manager · session_01FEkQ5kbUJTTWAxjV9nqr1P
Requested by Leehom · project thread
Fixes #28690
Fixes #28691
Fixes #28692
Clause-②: yes (widening)
Metadata-protocol: none
Skills-docs: none — no file under
skills/**orcontent/docsdescribes how the draft preview groups a time dimension or reads a qualified filter member, or says the ObjectQL strategy refuses aRELATION.iddimension.Card thread:
session_01FEkQ5kbUJTTWAxjV9nqr1P.维护者速读
timeDimensions按桶分组,不再要求它同时出现在dimensions里,fields里也带上这一列。② 草稿预览遇到对象名.region、数据集名.region、或声明在region上的维度zone这类筛选成员,改为读它真正对应的列(按该列的声明类型比较),不再返回[]。③ ObjectQL 策略对account.id维度、以及同一关系上的两个维度(account.id+account.name,或account+account.name),groupBy 只写一次那一列,不再被引擎以「重复列」拒绝。[]的现在给线上同样的答案,之前INVALID_QUERY的现在服务。引擎的重复 groupBy 拒绝原样保留(改在策略里,不碰packages/objectql)。预览仍拒绝经关系路径读的成员(analytics: the dataset draft preview joins nothing, so a backed dotted dimension serves one null group with the whole total #28555)。回滚即 revert。packages/services/service-analytics/src/preview-evaluator.ts分组段(改用共享的projectedDimensions)与新的whereColumns参数;analytics-service.ts新的previewWhereColumns;strategies/objectql-strategy.ts的executeCrossObject与resolveFkAttr两处 groupBy。pnpm --filter @objectstack/service-analytics exec vitest run src/__tests__/preview-time-filter-fk-parity.test.ts,预期 17 passed。What changes
preview-evaluator.ts. The grouping set is nowprojectedDimensions(query)fromorder-key-door.ts, the one definition the ObjectQL strategy's grouping, row mapping andbuildFieldMetaalready read: everydimensionsentry, then everytimeDimensionsentry with a granularity that is not already one of them. The evaluator'sfieldslist reads the same set, so the bucket column carries the cube dimension's type (timefor adatedataset dimension), as on the live faces. AtimeDimensionsentry without a granularity still only contributes its window.previewWhereColumns(query, cube)(new, module-level inanalytics-service.ts) reads thewhereleaves with the reader the strategies compile with (normalizeAnalyticsFilterTree+collectFilterLeaves, asrelationshipMembersreads them) and resolves each withdeclaredMemberEntry(cube, member, 'any'), the strategies' filter-member lookup thatassertWhereFieldsand the dimension face'spreviewEvaluationCubealready use. Each member spelled other than its bare column (OBJECT.region,CUBE.region,zoneoverregion) maps to that column. The evaluator takes the map as a new optional last argumentwhereColumns: thewherematch reads each listed member from its column, and the declared-type reader used by thewherelowering and the storage-form comparison reads the column's declaration. Nowheretree is rewritten or walked a second time; the time window and grouping read columns as before. A member that resolves to no declared entry, an expression or a relationship path is not listed (the latter is refused earlier by analytics: the dataset draft preview joins nothing, so a backed dotted dimension serves one null group with the whole total #28555's gates).strategies/objectql-strategy.ts, where the duplicate enters. Two places built agroupBynaming one column twice, and the engine's guard refused it (groupBy.1 names the column '…', which groupBy.0 already names,INVALID_QUERY):resolveFkAttrgrouped the related object by['id', attr], which is['id', 'id']forRELATION.id; andexecuteCrossObjectpushed a cross-object dimension's FK column once per dimension, soaccount.idbesideaccount.name(both FKaccount) pushedaccounttwice, and a base dimension over the FK column (accountbesideaccount.name) did the same. NowresolveFkAttrgroups['id']whenattrisid, and the cross-objectgroupBynames an FK column once (a base dimension over a column already grouped as an FK reuses it). The re-bucket reads the one column for each dimension, as before. Nothing inpackages/objectqlchanges, so its duplicate-groupBy refusal is unchanged and there are no other callers of a changed function to measure.src/__tests__/preview-time-filter-fk-parity.test.ts(17 cases, real SQLite, the analytics: the dataset draft preview serves a dimension the dataset does not declare, which the live strategies refuse INVALID_FIELD #28201 / analytics: a dotted dimension whose join the dataset does not back gets three different answers from native, ObjectQL and the draft preview #28258 / analytics: the dataset draft preview joins nothing, so a backed dotted dimension serves one null group with the whole total #28555 harness)..changeset/28690-preview-time-buckets-filter-columns-fk-id.md,@objectstack/service-analyticspatch.Why
Measured on
origin/maind62d9737(real SQLite, every face), with a probe printing each face's answer per selection. Dataset over a deal object (region,amount,closed_atdatetime,accountlookup), rows amer/2026-03-01/100/a1, amer/2026-04-01/50/a2, emea/2026-04-02/30/a1; dimensionsregion,closed_at(date),zone(fieldregion).Before / after per selection
closed_atmonth, no dimsregion+ tdclosed_atmonthOBJECT.closed_atmonthOBJECT.closed_at, 03:100, 04:80closed_atyear + April window2026:80dateGranularityOBJECT.region/CUBE.region/zone= emea[]$or[OBJECT.region$in emea, amount 50]OBJECT.closed_at$gte 2026-04-01[]account.idINVALID_QUERY(groupBy 'id' twice on the account object)account.id+account.nameINVALID_QUERY(groupBy 'account' twice)account+account.name(either order)INVALID_QUERYTests
Red first, green, ablations
d62d9737(git checkout d62d9737 -- …), the new test at HEAD:Tests 15 failed | 2 passed (17). The two green are the controls (a window-only time dimension, the preview's analytics: the dataset draft preview joins nothing, so a backed dotted dimension serves one null group with the whole total #28555 refusal). Each analytics: the dataset draft preview drops a bucketed timeDimensions entry that is not also in dimensions, so it answers one total instead of the time groups #28690/analytics: the dataset draft preview answers [] for a runtimeFilter member spelled OBJECT.region that both live strategies resolve to the declared dimension #28691 case fails on the preview leg, each analytics: the ObjectQL strategy refuses a backed account.id dimension with INVALID_QUERY because the cross-object expand groups by the related id twice #28692 case on the ObjectQL leg with the engine's duplicate-column message. Restored withgit checkout HEAD --,git statusclean.17 passed (17).de8aeea7(node scripts/ablation-replace.mjs, fix committed first, each anchor 1 → 0, restore blob == HEAD andgit diff HEADempty; the suite importssrc, no build between):query.dimensions ?? []:5 failed | 12 passed(the four bucket cases and thezone+ bucket case).--deletethepreviewWhereColumns(q, cube)argument:7 failed | 10 passed(the six filter cases and thezone+ bucket case).wheretype reader back todeclaredType(members read from their columns but typed as written):1 failed | 16 passed(theOBJECT.closed_atinstant comparison with an offset comparand).resolveFkAttrback to['id', attr]:2 failed | 15 passed(account.id,account.id+account.name).2 failed | 15 passed(account.id+account.name,accountbeforeaccount.name).1 failed | 16 passed(accountafteraccount.name).pnpm --filter @objectstack/service-analytics exec vitest runatde8aeea7:Test Files 203 passed (203),Tests 4856 passed | 282 skipped.pnpm --filter @objectstack/rest exec vitest run src/analytics(after--filter '@objectstack/rest^...' --filter @objectstack/service-analyticsbuild):Test Files 23 passed (23),Tests 298 passed | 22 skipped.pnpm --filter @objectstack/service-analytics typecheck(includes__tests__, after a full build) exit 0;eslinton the four touched source and test files exit 0.Acceptance notes
declaredMemberEntry, the lookup the strategies mirror and the preview's dimension face already uses; the leaves come from the strategies' own reader. No second walker: thewheretree is not rewritten, the evaluator reads a listed member from its column at the match.packages/objectqlis not touched, so its duplicate-groupBy refusal stands and no other caller moves.400154ac(real SQLite), not fixed: two dataset dimensions over one base column (regionandzoneoverregion) on the ObjectQL strategy's direct path answerINVALID_QUERY/ 400 (groupBy.1 names the column 'region'), while native and the preview serve amer 150 / emea 30 keyed by both names. Same root as analytics: the ObjectQL strategy refuses a backed account.id dimension with INVALID_QUERY because the cross-object expand groups by the related id twice #28692 on the non-cross-object path; reported to the seat.Verification
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 62 families derived onde8aeea7(5 paths vs merge base4bf48a8cc); each run with stdin from/dev/null, exit captured before any pipe, after a fullturbo run build --filter=!@objectstack/docs --concurrency=2(lock VERDICT command-exit 0).--ran: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN, every oneexit 0.node scripts/check-platform-checklist.mjs --changed-from origin/mainexit 0 (1 anchor judged, 0 lost);node scripts/check-commit-card-trailers.mjs --range origin/main..HEADexit 0.!and no narrowing arm.400154ac:de8aeea7with the changeset's two placeholder spellings changed (RELATION.id), no code change. On400154ac, the six changeset- and citation-reading gates (check-adr-0087-registration,check-changeset-no-major,check-empty-changesetwith--base origin/main,check:pm-changeset-deadline-census,check:issue-citations,check-closing-keyword-parity) were run again, each exit 0.The 62 derived families and their exits
node scripts/check-adr-0087-registration.mjs --base origin/main:: exit 0node scripts/check-adr-0087-registration.mjs --self-test:: exit 0node scripts/check-changeset-no-major.mjs --base origin/main:: exit 0node scripts/check-closing-keyword-parity.mjs:: exit 0node scripts/check-closing-keyword-parity.mjs --self-test:: exit 0node scripts/check-comment-mask-adoption.mjs:: exit 0node scripts/check-comment-mask-adoption.mjs --self-test:: exit 0node scripts/check-comment-mask-corpus.mjs:: exit 0node scripts/check-dts-emitted.mjs --self-test:: exit 0node scripts/check-empty-changeset.mjs --base origin/main:: exit 0node scripts/check-empty-changeset.mjs --self-test:: exit 0node scripts/check-issue-citations.mjs:: exit 0node scripts/check-plugin-teardown-shape.mjs:: exit 0node scripts/check-plugin-teardown-shape.mjs --self-test:: exit 0node scripts/check-tenant-audit-census.mjs:: exit 0node scripts/check-tenant-audit-census.mjs --self-test:: exit 0node scripts/ci/assert-turbo-remote-cache.mjs --self-test:: exit 0node scripts/ci/plan-test-core-shards.mjs --self-test:: exit 0node scripts/ci/prune-turbo-cache.mjs --self-test:: exit 0node scripts/docs-audit/check-affected-docs.mjs:: exit 0node scripts/docs-audit/check-drift-comment.mjs:: exit 0node scripts/pm/release-rehearsal-clone.mjs --self-test:: exit 0node scripts/release-pending-publish.mjs --self-test:: exit 0pnpm --filter @objectstack/spec run check:duration-unit-keys:: exit 0pnpm check:changeset-gate-self-tests:: exit 0pnpm check:ci-filter-parity:: exit 0pnpm check:closed-ceremony-text:: exit 0pnpm check:cross-package-test-inputs:: exit 0pnpm check:dispatcher-error-vocabulary:: exit 0pnpm check:doc-authoring:: exit 0pnpm check:driver-memory-census:: exit 0pnpm check:dts-closure:: exit 0pnpm check:dual-build-cjs-loads:: exit 0pnpm check:engine-double-contract:: exit 0pnpm check:error-status-conformance:: exit 0pnpm check:gitlink-declared:: exit 0pnpm check:issue-citations:: exit 0pnpm check:keyed-text-bounds:: exit 0pnpm check:lean-entry-closure:: exit 0pnpm check:logger-receiver-detach:: exit 0pnpm check:nul-bytes:: exit 0pnpm check:objectql-double-limit:: exit 0pnpm check:org-identifier:: exit 0pnpm check:page-declaration-shape:: exit 0pnpm check:platform-object-tenancy-census:: exit 0pnpm check:pm-changeset-deadline-census:: exit 0pnpm check:published-files:: exit 0pnpm check:query-options-erasure:: exit 0pnpm check:refd-timer-probe:: exit 0pnpm check:registry-log-declared:: exit 0pnpm check:rest-log-spy-declared:: exit 0pnpm check:slot-lookup:: exit 0pnpm check:sourcemap-no-sources-content:: exit 0pnpm check:swallow-census-controls:: exit 0pnpm check:system-context-census:: exit 0pnpm check:test-source-alias:: exit 0pnpm check:tier-file-adoption:: exit 0pnpm check:type-check-coverage:: exit 0pnpm check:type-check-debt:: exit 0pnpm check:undeclared-dep-imports:: exit 0pnpm check:watch-hint-literal:: exit 0pnpm check:where-matcher:: exit 0Human Minimum
packages/specholds no runtime logic. (Nopackages/specchange.)pnpm lintpasses (scoped run above; the repo-wide run is CI's).main, one pull request. (Three cards, one PR, by the dispatch order.)packages/spectouched: not touched.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01FEkQ5kbUJTTWAxjV9nqr1P