fix(analytics): lower the closed dateRange preset vocabulary once, and refuse the rest (#16322) - #17015
Conversation
…d refuse the rest Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg
… cross-face conformance fixture Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg
…it with per-package runners The runtime-hosted fixture added a third static consumer of @objectstack/driver-memory, which the #6664 census rules is a maintainer decision (RULED_CEILING = 2), not a test-authoring one. Migrated to the repo's existing cross-driver shape instead: the cases and rules live once in @objectstack/core beside the lowering they grade, and each face runs them in its own package. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg
…alytics-daterange-closed-vocabulary-drivers
📓 Docs Drift CheckThis PR changes 4 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 140 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 29d5a0160e9a9f661f95b01b3384c71b7099278b && git checkout 29d5a0160e9a9f661f95b01b3384c71b7099278b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8a70e1bf64e2a63a9a0c597e59524272e0903c8f aaf953b49d4e43af0c9089db1aaf8c7ed3c68f82 && git checkout -B drift-repro 8a70e1bf64e2a63a9a0c597e59524272e0903c8f && git merge --no-ff aaf953b49d4e43af0c9089db1aaf8c7ed3c68f82
node scripts/docs-audit/affected-docs.mjs --json 8a70e1bf64e2a63a9a0c597e59524272e0903c8f
|
…view face too, and register it in the conformance kit The fourth analytics face — `preview-evaluator.ts`, the Live Canvas draft preview (ADR-0037 P3) — still carried the `[range, range]` fallback the other three shed. After the rest of this branch a VALID `last_30_days` lowered there to `v >= 'last_30_days' && v <= 'last_30_days~'` — zero rows, silently — while the published chart beside it answered a real window, breaking exactly the publish-boundary continuity a draft preview exists to provide. It now calls the same `resolveAnalyticsDateRangeString` the two strategies and `driver-memory`'s cube face call, and refuses a non-preset string with the same ADR-0112 `400 ANALYTICS_DATE_RANGE_UNRECOGNIZED` envelope. The caller's explicit `[start, end]` array keeps its bounds and its inclusive upper reading (#16179), bare-day widening (#3777) included. The face is registered in the shared conformance kit's FACES list, so it is now held to the same rules as the other two — plus four end-to-end cases proving `evaluateAnalyticsQueryOverRows` really applies the window it reports, since this face emits no filter to read the window out of. Also points two driver-memory test headers at the conformance kit's real path (`packages/core/src/utils/analytics-date-range-conformance.ts`); they named `packages/runtime/src/analytics-daterange-driver-conformance.test.ts`, a file that does not exist — the kit went to core under the #6664 census. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XTBcV7zZHmokdyQgXjbyEU
Contract review at
|
| id | required | addressed | evidence (head) |
|---|---|---|---|
| D1 | Wire the draft-preview face (preview-evaluator.ts:293 [range, range] fallback) to the shared lowering + refusal, and register it in the kit's FACES |
yes | packages/services/service-analytics/src/preview-evaluator.ts:329-341 lowerPreviewDateRange → resolveAnalyticsDateRangeString, :362-363 called from evaluateAnalyticsQueryOverRows, :373-380 endExclusive honoured; FACES at src/__tests__/analytics-date-range-conformance.test.ts:133-137; 4 end-to-end cases :200-255 (refusal asserts code + status, :241-242; explicit array keeps $lte reading, :245-253) |
| D2 | Two driver-memory test headers cite non-existent packages/runtime/src/analytics-daterange-driver-conformance.test.ts |
yes | memory-analytics-date-range-timezone.test.ts:395, memory-analytics-date-range-utc-window.test.ts:278 now name packages/core/src/utils/analytics-date-range-conformance.ts (exists on head) |
| D3 | Pair not clause-② legible (checker read the older claim) | yes | Claim 5595582582 carries Branch: on its own line; my run node scripts/pm/check-clause2-carriers.mjs --pair 17015 → exit 0 ("both carriers agree", token path served 3 reads) |
| landing re-reads | it.todo still 0; today window unchanged |
yes | git grep -c it.todo in driver-memory: 4 on main → 0 on head; memory-analytics-date-range-token-end-exclusive.test.ts not in diff; core pin analytics-date-range.test.ts:144 |
Clause-② reading
yes — matches the claim. Mechanical floor: packages/core/src/index.ts:84,90 put two new modules on the published index (10 symbols: resolveAnalyticsDateRangePreset, resolveAnalyticsDateRangeString, analyticsDateRangeUnrecognizedError, analyticsDateRangeConformanceFindings, ANALYTICS_DATE_RANGE_REFUSED_SPELLINGS, ANALYTICS_DATE_RANGE_EXPLICIT_WINDOW + 4 types), plus a PROVENANCE_WAIVERS registration at packages/spec/src/api/error-code-ledger.zod.ts:1590-1603 (no new ErrorCode member). Runtime direction is a narrowing (refusal replaces silent widening). PR body line 3 and governing claim both say yes; pair check exit 0.
Governed surface / protocol label
none. GOVERNED_SURFACES = docs/adr/** · .claude/** · skills/** · AGENTS.md · CLAUDE.md; no changed file hits any. Protocol label owed: no. .github/labeler.yml:13-34 maps protocol:data|ui|system|ai to packages/spec/src/{data,ui,system,ai}/** only; the touched spec file is packages/spec/src/api/error-code-ledger.zod.ts (api/ has no rule). Auto Label ran green on head and applied none — the current label set is correct. No merge semantics attach to protocol:* anywhere (AGENTS.md, scripts/pm/*, workflows, pm-dispatch skill all silent; only docs/adr/0118 mentions a "v17 protocol:breaking 清单" as prose). A protocol:* label would be informational; the maintainer-only merge floor is Prime Directive #14's governed surfaces, not these labels.
CI on head
34 check runs, all on 070eb30e…: 31 success, 3 skipped (Console Pin Gate, Build Docs, Packed-tarball smoke opt-in — path/opt-in conditional), 0 failed, 0 in progress at review time. Lint & Repo Gates = success, Type Check · workspace = success, Temporal Conformance (live PG + MySQL) = success. Nothing red.
Contract review on head
Published-face delta. (a) core index above. (b) spec: one waiver row + comment correction (:326-332); ships via dist and src/**/*.zod.ts. (c) Behaviour: driver-memory/src/memory-analytics.ts:1531-1534 — string arm delegated to the shared resolver (12 presets that matched all rows now real windows; garbage → 400); native-sql-strategy.ts:509-512,535-543 — resolved window bound, </<= from endExclusive; objectql-strategy.ts:1711-1724 — $gte/$lt or $gte/$lte; preview-evaluator.ts:329-380. (d) service-analytics gains module-level PreviewDateRangeWindow / lowerPreviewDateRange (preview-evaluator.ts:291,329) — not published: src/index.ts exports nothing from preview-evaluator, package.json exports has only ".". (e) No protocol key, no IAnalyticsService change, no content/docs change needed — content/docs/references/api/analytics.mdx:100 already states the schema refusal; no stale last 7 days dialect anywhere in content/docs. (f) ADR-0112 envelope: code + status: 400 + spec wording (analytics-date-range.ts:209-217).
Scope vs card. Memory ✓, SQL analytics path ✓ (lives in the two strategies; driver-sql/src/sql-driver.ts has only comment hits at :4865/:13401 — the fence claim is true), shared fixture ✓ (kit + 3 runners over 4 faces), changeset ✓. The preview face was ruled in by D1; the core location follows the card's "derive, don't hand-copy" and the #6664 census gate. No narrowing of the card's faces (see F2 for a non-face consumer).
Changeset. Present; four packages all published (private unset; spec ships the edited source); minor×3 + spec patch per the launch-window convention (scripts/check-changeset-no-major.mjs header: breaking ships as minor); ADR-0087 marker not-required (already-registered …) is a valid category (check-adr-0087-registration.mjs:469) naming an existing entry (packages/spec/src/migrations/registry.ts:5654). Body accurate on the refusal and code; stale by one face (F1).
Tests. Kit iterates DATE_RANGE_PRESETS (accept set), compares emitted bounds + operator to the shared resolver, refuses 5 real spellings asserting code and status (analytics-date-range-conformance.ts:197-202), case-sensitivity, explicit-window preservation; every runner carries a live-harness control. Memory face read off result.sql; both strategies driven through queryDataset (the un-parsed door); preview via helper + 4 e2e cases. All 15 retired pins replaced by live tests; the 5 removed it( titles in the DST file are re-authored equivalents (rolling/calendar legs, 14 cells incl. last_quarter).
Findings
- F1 (non-blocking; continues D1's documentation half) —
.changeset/analytics-daterange-driver-alignment.md:55("driver-memoryand both SQL strategies call it") and:63("holds all three faces"). The patch commit did not touch the changeset, so the release input under-reports the draft-preview face — a user-visible change (Live Canvas draft preview with a preset window: zero rows → real window; garbage → refused). Fix: one sentence naming the draft-preview evaluator, "four faces". - F2 (non-blocking; new) —
packages/services/service-analytics/src/dataset-executor.ts:1240-1242(runCompare) still spells[dateRange, dateRange]for a string; a preset name then hitsparseUTC(:476-487) and is refused400 DATASET_INVALID("invalid date in dateRange"), neither lowered-then-shifted norANALYTICS_DATE_RANGE_UNRECOGNIZED. Pre-existing (file untouched, identical onorigin/main), loud not silent, and only a rawqueryDatasetcaller sendingcompareTo+ a preset string reaches it — objectui'sDatasetWidget.tsx:246-249derives the shiftable window from the filter's$gte/$lteISO days. Outside the card's driver-face scope: file a follow-up card, do not widen this PR. - F3 (non-blocking) — PR body is stale after the patch round: "thin runner per face (
driver-memory, and both strategies)", "three faces", no mention of the preview wiring or thatlowerPreviewDateRangestays off the published index. Not the release input; one paragraph makes the body self-contained for the landing seat. - F4 (non-blocking; continues prior 验收备注 ✨ Set up Copilot instructions #2) —
preview-evaluator.ts:373-380compares ISO instants lexicographically againstString(row[field]); a seed row storing a bareYYYY-MM-DDon the boundary day sorts below the instant and is excluded. Not a regression (every preset selected zero rows there before); record for whoever next touches preview date lowering.
Acceptance notes
Fixes #16322is correct: all four acceptance boxes delivered; the only closing keyword sits beside Analytics drivers (memory, SQL) aligndateRangestring handling to the closed preset vocabulary (driver half of #16041) #16322.- Refusal propagation through
POST /analytics/dataset/queryis by construction: no catch aroundevaluateAnalyticsQueryOverRows(analytics-service.ts:1124-1127), andrest-server.ts:10960-10962serves any thrown{status: 4xx, code}. No REST test pins the new code at that door (it appears in tests only underpackages/runtime); optional pin when the seat-owned "dataset door never Zod-parsesselection" card is filed — that card is still unfiled per the latest seat comment 5600088244. - Landing: F1 is a one-line changeset edit worth taking before merge but not a hold; then clear
needs:contract-reviewon both carriers with a provenance line citing this verdict, ready, auto-merge — after the seat's own transcript-verified tier check, as its comment commits to.
Generated by Claude Code
…e fixture holds The body said "both SQL strategies" and "all three faces" while the headline already claimed EVERY analytics face. Wiring the fourth face makes the headline true; these two enumerations were the half still under-counting it. ⛔ No level moved: the same four packages ship at the same levels (core / driver-memory / service-analytics `minor`, spec `patch`) — this diff adds no package and changes no published surface, so the levelling the contract review passed stands untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XTBcV7zZHmokdyQgXjbyEU
Scoped re-verification of the patch round — PASS WITH FINDINGS, all non-blocking · head
|
Fixes #16322
Clause-②: yes
The driver half of #16041. The spec closed
timeDimensions[].dateRange's string arm to the thirteen dashboard preset names; the faces behind that door never aligned, so the defect #16041 abolished at the contract simply moved onto the newly-blessed vocabulary.⭐ The measurement, re-taken on this card, naming the tree
Driving the built
driver-memorydist atb834b48e7a(this branch's merge base) with all 13 preset names over 5 probe rows (2020, 2026-08-31, 2026-09-05, now, 2099):b834b48e7a)todayyesterday,this_week,last_week,this_month,last_month,this_quarter,last_quarter,this_year,last_year,last_7_days,last_30_days,last_90_daysnot a range at all,Last 7 Days,last 7 daysANALYTICS_DATE_RANGE_UNRECOGNIZED/ 400So a valid preset such as
last_30_dayswas accepted by the schema and then silently widened to all of history. Reproduced, not inherited from the card.The card warns that the two backends fail oppositely on an uninterpretable value and that the dataset path's direction was unmeasured. Measured through
POST /analytics/dataset/query's own executor (AnalyticsService.queryDataset, the door that does not Zod-parse its selection), the answer is neither: both SQL strategies lowered the string to a point window bound with the literal name —— and did so identically for
last_30_days, fornot a range at all, and fortoday. The final row count is therefore delegated to whatever the dialect decides a vocabulary word compares as, which is exactly how one input came to have two opposite wrong answers. No refusal at any layer.⭐ The reading on
packages/drivers/driver-sql/src/sql-driver.ts— the fenced fileThe card's scope names "the SQL drivers' analytics path", and the order fences
sql-driver.ts(held by #16887) with stop and report if the repair has to reach it.It does not, and the file is not narrowed away either.
git grep -n 'timeDimensions\|dateRange\|parseDateRange'inpackages/drivers/driver-sql/src/sql-driver.tsreturns two hits, both comments (lines 4865 and 13401) and no code. The SQL analyticsdateRangepath ispackages/services/service-analytics/src/strategies/objectql-strategy.ts(dateRangeBounds) and.../native-sql-strategy.ts(thetimeDimensionsloop ingenerateSql) — not fenced, and repaired here.sql-driver.tsis untouched by this PR.What this does
@objectstack/core(utils/analytics-date-range.ts). Every preset resolves to a pair of{date-macro}tokens handed to the existingresolveFilterToken, then to that zone's midnight instant viazonedDateStartToUtcMs. Consequences rather than re-implementations:dateRange: 'this_month'and a{month_start}filter token cannot disagree;driver-memoryanalytics resolves a stringdateRangewithout ever reading the declaredAnalyticsQuery.timezone#16042's timezone anchoring anddriver-memoryanalyticsparseDateRangeString()builds its window on the LOCAL calendar and renders it as UTC — 'today' and 'last N days' are offset by the process timezone in every non-UTC zone #15825's one-calendar arithmetic come from the macro resolver, not from each face; and the preset LIST is aRecordkeyed byDateRangePreset, so a name added toDATE_RANGE_PRESETSfails core's type-check rather than a dashboard. ⛔ No hand copy.analyticsDateRangeUnrecognizedErrorstampsANALYTICS_DATE_RANGE_UNRECOGNIZED/ 400 with the spec's ownanalyticsDateRangeRefusalMessagewording — the same sentence the schema door answers with ({ field: {} }(零个操作符的字段约束)在同仓有三个答案:driver-sql 组合子内 TRUE、顶层抛 INVALID_FILTER、formula/driver-memory FALSE #5240: one condition, one wording).driver-memoryand both strategies call it.driver-memoryanalyticsdateRangeupper bound is INCLUSIVE for a full-timestamp end, so the first instant of tomorrow counts as today #16179's separation survives, and is now pinned across faces. Only a window a face RESOLVED is compared with a strict upper bound — for the ten calendar presets. The three rollinglast_N_daysend at NOW, an instant they reach, and stay inclusive. An explicit[a, b]a caller wrote never reaches the resolver at all and keeps$lte, bound for bound. ⛔ The bare-YYYY-MM-DDroad the code records as closed is not taken: every bound emitted here is an instant.last_week/last_month/last_quarter/last_yearare the PREVIOUS period, so their live instants answer a different question from the oldlast N unitsones. The zone/direction coverage of the retired table is carried over intact, andlast_quarteris now covered too — 14 cells for 13. The timezone case returns aslast_7_dayswith its measured window (Asia/Shanghai opening at2026-08-30T16:00:00.000Zagainst UTC's2026-08-30T00:00:00.000Z). The fallback fence returns as the driver-side refusal pin the card specified.analyticsDateRangeConformanceFindingsin core, with a thin runner per face (driver-memory, and both strategies inservice-analytics). Each runner also drives a deliberately broken face and asserts the kit finds it, so a green is never ambiguous with "the kit looked at nothing".The first shape was one test file in
packages/runtime— the only package that can import all three faces.pnpm check:driver-memory-censusrefused it: a new static consumer of@objectstack/driver-memoryis a maintainer ruling under the #6664 census (RULED_CEILING = 2), not a test-authoring decision. ⛔ Not silenced with a ledger entry. Migrated to the repo's existing cross-driver shape instead (*-conformance.tsin a shared package, a thin runner per driver — thememory-aggregation-conformance.test.tspattern), which keeps the assertion body single-sourced and leaves the census at 2.⛔ Not a restatement of the carrier; reported for the reviewer to weigh.
@objectstack/coregainsresolveAnalyticsDateRangePreset,resolveAnalyticsDateRangeString,analyticsDateRangeUnrecognizedError,analyticsDateRangeConformanceFindings,ANALYTICS_DATE_RANGE_REFUSED_SPELLINGS,ANALYTICS_DATE_RANGE_EXPLICIT_WINDOWand four types.@objectstack/specgains no export. Its only change is onePROVENANCE_WAIVERSrow plus a comment correction: the refusal's code stays registered under@objectstack/runtime(the door that names the wire vocabulary), and the waiver records the shared-constructor shape one package over — theUPDATE_ID_MISMATCH/VALIDATION_FAILEDprecedent. ⛔ Deliberately one waiver, not a row per driver: with one constructor there is one stamp site, and rows for packages that stamp nothing are the dead weight that gate rejects.driver-memoryanalytics silently accepts an unparseabledateRangeand matches EVERY row — and the platform's own documented spelling'Last 7 days'is one of them #16041's, unchanged.Changeset — routed on a measurement of what ships, not on the diff's kind
patchfor@objectstack/speclooked like a comment-only diff. It is not: the newPROVENANCE_WAIVERSrow is a value in an exported const and lands inpackages/spec/dist/api/index.jsandindex.mjs(grep -c= 1 in each), andpackages/spec'sfilespacksdistandsrc/**/*.zod.ts, so the edited source ships too. The other three packages publish behaviour changes. ⇒ a changeset,minorfor the three that carry the narrowing and the new surface,patchfor spec.skip-changesetwould have been wrong, and the diff's apparent kind was not what decided it.COVERAGE
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon the merged head. 83 derived, 83 run, 0 NOT-MEASURED, 0 UNRUN, reconciled with--ran. The first derivation flagged a STALE TREE (8 commits behind);origin/mainwas merged in and the derivation re-run — the command list came back byte-identical.pnpm --filter ... testfor@objectstack/core,@objectstack/driver-memory,@objectstack/service-analytics,@objectstack/spec;typecheckfor all four; plus the door-side consumersruntime/analytics-daterange-refusal-envelopeandrest/analytics-dataset-*.eslint . --no-inline-configrun over the whole repository atcd14a59b4e, not narrowed: 6397 files linted (count read from--format json, not estimated).PREREQUISITE NOT METresults (unbuilt workspace packages) are recorded as NOT MEASURED and were re-run to a real verdict after a fullpnpm build; ⛔ neither is counted as a pass. Every exit code was captured by redirect-then-$?, never across a pipe.VERDICTS
check:driver-memory-censuswent from exit 1 (the finding above) to exit 0 after the migration;check:error-code-provenancefrom exit 1 to exit 0 after the waiver.core1243/1243 ·driver-memory1169/1169 ·service-analytics2166/2166 ·spec13100/13100 — all passed, all four typechecks Done.runtime6/6,rest24/24.eslint: 0 errors, 0 warnings, exit 0.pnpm --filter @objectstack/spec check:generated: all 15 generated artifacts up to date — the ledger edit moved none of them.Out of scope, filed
yesterdayentry ofDATE_RANGE_PRESET_MACRO_WINDOWSprescribes a TWO-day window, so the refusal an author is handed selects today as well #17014 —DATE_RANGE_PRESET_MACRO_WINDOWS.yesterdayprescribes$between: ['{yesterday}', '{today}'], and a bare-day$betweenupper bound widens to the next day (driver-memory / driver-mongodb:裸日期$lte上界在 datetime 值上同样丢当天数据(#3777 的非 SQL 驱动对齐) #4042 / dashboard 的日期区间上界打在datetime列上丢失当天数据 —— 默认配置即命中 #3777), so the window a refused author is told to write spans two days for a one-day preset. Reproduced onorigin/main. Not fixed here: it edits the vocabulary module, whichdriver-memoryanalytics silently accepts an unparseabledateRangeand matches EVERY row — and the platform's own documented spelling'Last 7 days'is one of them #16041 owns and this card's order fences.验收备注
todaywindow is byte-identical before and after (2026-09-09T00:00:00.000Zto2026-09-10T00:00:00.000Z, exclusive), somemory-analytics-date-range-token-end-exclusive.test.tsis untouched and still green — thedriver-memoryanalyticsdateRangeupper bound is INCLUSIVE for a full-timestamp end, so the first instant of tomorrow counts as today #16179 reading was preserved by construction, not by patching around it.last_month: 366/366, and by a whole month). ⛔ Not papered over into one claim.driver-sqlwas left completely untouched, and the fixture does not reach it; the SQL half runs against bothservice-analyticsstrategies, which is where that lowering lives.⭐ PATCH ROUND —
aaf953b49, answering the contract review's D1 and D2Everything above is the record of the round that ended at
cd14a59b4e(sessionsession_01ADLdAs2pVcH17h9tZKWMBg) and is left as it was written. This section is the patch round that follows the contract-tier review on #16322, and its numbers supersede the COVERAGE / VERDICTS above.The review passed the contract question and held landing for two items. Both are discharged; D3 (the pair gate) was already closed by the restated claim and was NOT touched.
D1 — the fourth analytics face is now wired AND registered
packages/services/service-analytics/src/preview-evaluator.ts— the Live Canvas draft-preview evaluator (ADR-0037 P3) — still carried the[range, range]fallback the rest of this branch abolished. Re-measured oncd14a59b4ebefore touching anything:resolveAnalyticsDateRangeStringhad 0 call sites in that file against 2 innative-sql-strategy.tsand 3 inobjectql-strategy.ts, and the kit'sFACESlist held two entries. The premise held.It now calls the same shared lowering and raises the same ADR-0112 envelope, through one seam (
lowerPreviewDateRange) with exactly one call site inevaluateAnalyticsQueryOverRows. The caller's explicit[a, b]array is untouched — same bounds, same inclusive upper reading (#16179), same bare-day widening (#3777); only a window this face RESOLVED gets the vocabulary's own upper reading.FACESnow registers three entries in this package, so the changeset headline's every analytics face is a measurement rather than a claim.⭐ The wiring is proved by ablation, not asserted
This face emits no filter to read a window out of, so its kit registration goes through the lowering rather than through an emitted artifact — and an exported helper the evaluator ignored would satisfy the kit while the live path kept its old fallback. Four end-to-end cases driving
evaluateAnalyticsQueryOverRowsclose that gap, and the ablation shows they are the ones doing it.Ablation: keep
lowerPreviewDateRangecorrect and cut only its call site back to[range, range]. On-disk proof taken before the run — target blob5ea20bba→8695b60f, injected markergrep -c= 1, deleted callgrep -c= 0 — and the restore leg proved by blob hash back to5ea20bbawithgit diff HEADempty. No rebuild sits between the edit and the run: the subject resolves in-package from source (src/__tests__/…imports../preview-evaluator.js), never through a packageexports→dist.[a, b]case⭐ The asymmetry IS the result: registering the face in the kit was not on its own sufficient, and the three end-to-end cases are load-bearing rather than decorative.
D2 — the two dead header paths are corrected
memory-analytics-date-range-timezone.test.tsand-utc-window.test.tsnamedpackages/runtime/src/analytics-daterange-driver-conformance.test.ts, a file that does not exist. Both now namepackages/core/src/utils/analytics-date-range-conformance.ts, which does (control taken on the branch before editing). Repository-widegrepfor the dead path: 0 hits.Changeset — the same four packages at the same levels
⛔ No level moved and no package joined or left: core / driver-memory / service-analytics
minor, specpatch, exactly as levelled by the review. This diff adds no export to any published entry point (lowerPreviewDateRangelives in a moduleservice-analytics'sexportsmap does not expose — it maps.only). Two enumerations in the changeset BODY were corrected because the wiring made them under-count the headline: "both SQL strategies" → the preview evaluator too, and "all three faces" → all four, plus one bullet naming the fourth face and its measured consequence.COVERAGE — on
aaf953b49node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no path argument — the change set comes from git off the merge base419facdde, three-dot). 83 derived, 83 run, 0 NOT-MEASURED, 0 UNRUN, reconciled with--ran. Every exit code captured by redirect-then-$?, ⛔ never across a pipe.origin/mainand 26 files the families are derived FROM changed across that range. The 83 are therefore this tree's families, notorigin/main's. ⛔ Recorded, not silently absorbed: re-deriving after a merge ofmainis a landing act and belongs to the seat that lands this.pnpm --filter @objectstack/service-analytics --filter @objectstack/driver-memory testand… typecheckon the final head.tsc --noEmit --listFilesconfirms both edited files are inside the type-check program (1 hit each), so the typecheck is a measurement of them and not a vacuous pass.eslint . --no-inline-configover the whole repository on the final head, ⛔ not narrowed — 82s, exit 0.PREREQUISITE NOT METrecurred (check:type-check-debt, the outer node process OOMs at the default 4 GB heap while the gate pins tsc at its CI-shaped 6144 MB ceiling). Recorded as NOT MEASURED and re-run to a real verdict at--max-old-space-size=8192; ⛔ the exit 3 is not counted as a pass.VERDICTS — on
aaf953b49service-analytics2171/2171 (97 files) ·driver-memory1169/1169 (47 files) — all passed; both typechecks Done.eslint: exit 0.node scripts/pm/check-clause2-carriers.mjs --pair 17015→ exit 0, "the clause-② declaration is readable in the fixed spelling and both carriers agree." The script's blob on this head is0f78a245, byte-identical toorigin/main's.The four landing-sequence points, each answered on
aaf953b49preview-evaluator.tswired and registered — oneresolveAnalyticsDateRangeStringcall reached throughlowerPreviewDateRange, whose sole call site is inevaluateAnalyticsQueryOverRows;FACEScarriesservice-analytics (draft-preview evaluator)as its third entry.it.todostill 0 indriver-memory—git grep -o 'it\.todo' -- packages/drivers/driver-memory/src/ | wc -l= 0, and the vitest summary reports 1169 passed with no todo line. ⛔ Nothing was skipped, quarantined or.todo-ed to reach green.todaywindow unchanged — resolved at the frozen2026-09-09T12:34:56.789Zin three zones before and after this round: byte-identical (diffempty). UTC stays2026-09-09T00:00:00.000Z→2026-09-10T00:00:00.000Z, exclusive.packages/coreis untouched by this round (git diff --name-onlyagainstcd14a59b4enames 0 files under it).验收备注 (patch round — recorded, not filed)
dateRangearray diverges between faces, and this round deliberately preserved it rather than widening scope:AnalyticsDateRangeSchema's array arm is a plainz.array(z.string()), so['2026-01-01']is schema-valid;ObjectQLStrategy.dateRangeBoundsdegenerates it to the point[start, start], while this face leaves the upper bound unwritten and admits everything fromstartonward. Read from source on this head, ⛔ not reproduced with a probe, and out of the two deliverables this round was held for. 承接者: whoever next touchespreview-evaluator.ts's orobjectql-strategy.ts'sdateRangelowering — the divergence is named in a comment at the seam so it cannot be met cold.Generated by Claude Code
Generated by Claude Code