feat(spec)!: close timeDimensions[].dateRange's string arm to the date-range preset vocabulary (#16041) - #16618
Conversation
…-range preset vocabulary (wip) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
…ons for the closed dateRange vocabulary (wip 2) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
…and reference docs for AnalyticsDateRangePreset (wip 3) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
…ompile the schema example (wip 4) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
📓 Docs Drift CheckThis PR changes 2 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 135 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 84a972c9831cfe979252bbcfc3f5638d657a8df3 && git checkout 84a972c9831cfe979252bbcfc3f5638d657a8df3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4d728680c0cd5fd1660aa1a57271bc7f084df559 080052ce832fbe175b6fdf74bcd7fde22d8ac6cc && git checkout -B drift-repro 4d728680c0cd5fd1660aa1a57271bc7f084df559 && git merge --no-ff 080052ce832fbe175b6fdf74bcd7fde22d8ac6cc
node scripts/docs-audit/affected-docs.mjs --json 4d728680c0cd5fd1660aa1a57271bc7f084df559
|
… the array form The closed string vocabulary for timeDimensions[].dateRange retires the bare-date spelling this fixture authored, so the package's required TypeScript Type Check went red on one line (TS2322 at line 187). The fixture now writes the same single-day window as ['2026-01-20', '2026-01-20']; the strategy lowers both spellings to the identical $gte/$lte bounds, so the expected values are untouched. The test's title still names the retired dialect on purpose: a comment above it records that retiring the test belongs to the driver-alignment card, together with the strategy's degeneration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
Contract review at
|
…alytics-daterange-closed-vocabulary
…ed tree Regeneration commit for the forward merge of origin/main (554a160) into this branch, kept apart from the merge commit so the two can be read separately. `gen:schema` + `gen:docs` on the merged tree changed exactly one file: the references index, whose counts are now the union of both sides — main's ResumeFailureDetails (API 436 -> 437) plus this branch's AnalyticsDateRange / AnalyticsDateRangePreset (Data 166 -> 168), total 1575 -> 1577. No other generated artifact moved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
Forward merge landed (2026-09-07 landing round) — and one required red this branch still owesTwo commits, kept apart on purpose (
Gates on
PR left draft; nothing armed. Generated by Claude Code |
⛔ HELD — not arming. The forward merge is done; B1 is not fixable inside this card's fence.The contract is verified and correct (review 5571543935, all five questions PASS by measurement). This is a landing block, and it is deeper than it looked. What landedForward merge complete. Merge commit ⭐ Why B1 stops here — my own premise was falsified, and that is the findingI authorised a narrow fence lift on the guess that "these tests call the driver directly, not through the schema door, so the narrowed type may be incidental." That is false, and it was measured. Each of the three helpers builds an asQuery = (input: AnalyticsQuery): AnalyticsQuery => AnalyticsQuerySchema.parse(input)before ⇒ The three files are runtime-red, not merely tsc-red: 15 failed / 37 passed (52), every failure a So a commented cast would green And the cost of doing it anyway is not neutral: the ⭐ The real finding, and it is about the ruling, not this PRThe ruling's spec-first ordering is not executable as written. Batch #57 put the spec half first and made #16322 That is a sequencing decision for the maintainer, not something to resolve by widening a fence. The options, with their costs, are on card #16041 (report comment 5571833149) and summarised in my note there. CI on the merged head — first real CI this PR has ever had
On the last one, so it is not misreported: the only substantive line in the job log is a MySQL InnoDB error — Still running at this writing: StatePR stays draft. Card #16041 keeps Generated by Claude Code |
|
| input | result |
|---|---|
today |
a real window — matched 1/5 |
| all twelve other presets | parseDateRangeString returns [name, name] — the fallback — matched 5/5, including 2020 and 2099 |
last 7 days / last 3 days (retired dialect) |
real windows — 3/5, 2/5 |
⇒ 0 of the 15 cases can be re-spelled, so retirement is option B's only executable form.
last_30_days is accepted by the schema and then silently widens in driver-memory — matching every row, 2099 included. That is the same defect class card #16041 exists to abolish, relocated onto the newly-blessed vocabulary until #16322 aligns the parser.
It does not block this landing — triage established the parser is driver-memory-local, absent from driver-sql and service-analytics, so it is not on the production SQL path, which is why this card is p2 and not p1. But it makes #16322 materially more urgent than "cleanup", and I am recording it there.
Coverage this PR gives up, stated loudly
Retiring the 15 cases takes the last N UNIT arithmetic leg's DST coverage in driver-memory to zero until #16322 — all 13 driver-facing DST cells (day/week/month/year legs across America/New_York, Los_Angeles, St_Johns, Europe/London, Berlin, Australia/Sydney, Pacific/Auckland, Chatham, America/Santiago), plus the timezone case pinning that last N anchors to the supplied zone's midnight.
What survives: the today leg's DST and zone coverage — 7 timezone cells including the 23-hour America/New_York spring-forward day, and 11 utc-window cells — plus the DST cell table's 8 controls.
Each retired case becomes it.todo(...) keeping its original title, so every run's summary still names it. ⛔ This is retirement of pins whose input the contract no longer admits, under ruling batch #57 and the maintainer's option-B decision of 2026-09-07 — not a test disabled to get green.
Generated by Claude Code
…osed dateRange contract no longer admits Not a test disabled to get green. #16041 closed the string arm of timeDimensions[].dateRange to the date-range-presets.ts vocabulary (maintainer ruling, decision batch #57), and this retirement is the maintainer's option-B decision of 2026-09-07, chosen over routing the fixtures around the schema door because that would have kept a live pin on the silent-widening fallback #16041 exists to abolish. The three memory-analytics-date-range-{dst,timezone,utc-window} files fed the retired relative dialect ('last 3 days', 'last 7 days', 'last 1 week', 'last 2 weeks', 'last 1 month', 'last 3 months', 'last 1 year') and one garbage string through asQuery = AnalyticsQuerySchema.parse, so on this branch they read 15 failed | 37 passed (52) plus three TS2322, on input production can no longer deliver through any door. Re-spelling was measured, not assumed: driving the built dist directly, only 'today' produces a window; every other preset (last_7_days, last_week, ...) falls to parseDateRangeString's [range, range] fallback and matches every row, because the parser keys on startsWith('last '). So 0 of the 15 cases are expressible until #16322 aligns the parser. Form: it.todo per retired case, original title kept and suffixed with the reason; a site comment at each naming #16041 and #16322 and stating the coverage lost; the two surviving helpers' `range` parameter retyped from string to AnalyticsDateRange (the closed contract), which is what clears the TS2322. No surviving assertion changed. The dst file's driver harness (CUBE / asQuery / probesSelected / probesFor) had no surviving caller and is removed; its cell table and all eight controls survive. COVERAGE LOST until #16322 reinstates it in preset form: - dst: all 13 driver cells, i.e. the ENTIRE `last N UNIT` arithmetic-leg DST coverage in driver-memory goes to zero: spring-forward and fall-back, the day / week / month / year legs, both hemispheres, the two non-whole-hour zones (St_Johns -03:30, Chatham +12:45). - timezone: the one `last N` case anchoring on the SUPPLIED zone's calendar day and midnight (Asia/Shanghai, 'last 7 days'). - utc-window: the fallback fence on 'not a range at all'; its zone-independence was a property of an answer that matched every row, and #16322 owes the driver-side refusal pin in its place. What survives: the 'today' leg's DST and zone coverage, i.e. the 7 timezone cells including the 23-hour America/New_York spring-forward day and the 11 utc-window cells, plus every control on the dst cell table. Reinstatement note for #16322: last_7_days / last_30_days / last_90_days are rolling day-leg windows; last_week / last_month / last_quarter / last_year are calendar windows, not n units back, so the week / month / year cells need re-measured transition instants under preset semantics. Measured: driver-memory typecheck exit 2 (three TS2322) before, exit 0 after; the three files 15 failed | 37 passed before, 37 passed | 15 todo after, also under TZ=America/New_York; full package 1095 passed | 15 todo (1110); spec and runtime pins unmoved (235/235, 37/37); dist byte-identical under a forced rebuild, so the 16 dependents cannot observe a test-only diff. The same 15 failures are what reddened Temporal Conformance on d8a3fd2 (its non-SQL leg runs driver-memory under TZ=America/New_York); the InnoDB line in that job's tail is the MySQL container's echo of a passing negative test and appears in main's green run too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
Driver-memory date-range pins retired on this branch —
|
Green and armed — the four reds were one defect, and one commit proved itAll 38 check runs read on
⭐ That last row is the one that settles the diagnosis. I had twice called What landed
Each retired case is ⛔ This is retirement of pins whose input the contract no longer admits — under ruling batch #57 and the maintainer's option-B decision of 2026-09-07 — not a test disabled to get green. The commit message carries that distinction and the coverage loss, so git history holds it without this thread. Re-spelling was refused by measurement, not argument: driving the built dist directly, only Coverage given up, and where it comes backGone until #16322: the entire Surviving: the The concrete reinstatement requirement is on #16322 (comment 5572379368), including the trap that
Generated by Claude Code |
Fixes #16041
Clause-②: yes
What this PR does — the spec half of the #16041 ruling (decision batch #57, option A)
Maintainer ruling, verbatim: 「本项目以协议为基准。所以开发应该对其协议,协议有问题应该立卡修改协议」 (reply 「同意」).
AnalyticsQuerySchema.timeDimensions[].dateRangehad a barez.string()arm whose only documented example,"Last 7 days", was a value no driver could parse: it fell through driver-memory's[range, range]fallback and matched everyDate-typed row (aDatecompares above aStringunder BSON cross-type ordering), while the SQL strategies read the same string as a single ISO day. Same input, opposite wrong answers, HTTP 200 on both.AnalyticsDateRangePresetSchema = z.enum(DATE_RANGE_PRESETS)inpackages/spec/src/data/analytics.zod.ts, built offdata/date-range-presets.ts— the module whose header records the three drifting copies spec: dashboard date filter 的 defaultValue 没有作者时校验 —— 拼错的预设名要到浏览器控制台才被发现 #4614 retired.todayis that tuple's first member, so the ruling's "presets plus today" is this enum with no second source. The pin assertsAnalyticsDateRangePresetSchema.optionsequals[...DATE_RANGE_PRESETS]; the refusal text and the field's.describe()are both built fromDATE_RANGE_PRESETS.join, so neither the message nor the generated reference page can become a fourth copy.AnalyticsDateRangeSchema = z.union([presets, z.array(z.string())], { error })raises ONEinvalid_unionissue attimeDimensions.N.dateRangecarryinganalyticsDateRangeRefusalMessage(input)— the value, the vocabulary, the array spelling for an explicit window. Spec exportsisAnalyticsDateRangeRefusalIssue, structural (the union's own issue code at the path the schema declares), never message sniffing. The runtime door (packages/runtime/src/domains/analytics.ts, serving/analytics/queryand/analytics/sql) lifts it into the ADR-0112 envelope400 ANALYTICS_DATE_RANGE_UNRECOGNIZED— a newERROR_CODE_LEDGERmember registered under@objectstack/runtime, the door being the emitter (theFLOW_*"the door names the wire vocabulary" precedent). Lifted only when every issue is that refusal; a body wrong elsewhere too stays the generic400 VALIDATION_FAILED+details.fields[]. The analytics service is never reached.@example(markedos:check, socheck:skill-examplestype-checks it against the built package) now showdateRange: 'last_7_days'.'Last 7 days'is not in the vocabulary — checked, not assumed:isDateRangePresetName('Last 7 Days')is pinned false indate-range-presets.test.ts, and the list is snake_case and case-sensitive. The three in-repo authors of'Last 7 days'— all spec tests — are re-spelled. The wire code is written into the field's.describe()(so into the generatedreferences/data/analytics.mdxandreferences/api/analytics.mdxtables) and into the JSDoc.Type narrowing:
AnalyticsQuery['timeDimensions'][n]['dateRange']is now the preset union| string[] | undefined, pinned with a@ts-expect-errorin a file the spec test-layer tsc program compiles.Base and ancestry
Branched from
origin/mainatffca0df9cb(#16575).git merge-base --is-ancestor 90e7e6de114af2586da89dd417101b24db716d39 HEADexits 0, so the api-surface generator's one-row-per-kind format is what this branch regenerated (api-surface/data.json: +6 rows, 0 breaking).Changeset grade — measured on this diff, not inferred
major: a committed probe changeset declaring"@objectstack/spec": majormadecheck-changeset-no-major.mjs --base origin/mainexit 1 with⛔ This PR introduces changeset(s) that declare a major bump(.changeset/pre.jsonabsent, enforce path live). The probe commit was reset away and never pushed. An uncommitted probe readclean— the gate diffs committed HEAD, which is why the probe had to be committed to count.minor(spec + runtime) with the**BREAKING**banner and the explicitadr-0087: registered analytics-time-dimension-date-range-vocabulary-closeddisposition (an HTML-comment line inside the changeset file — the form that gate reads).check-adr-0087-registration.mjs --base origin/mainexit 0:1 declared-breaking changeset(s), each carrying an ADR-0087 disposition … [BREAKING+bang] registered analytics-time-dimension-date-range-vocabulary-closed (new here: …)— a non-vacuous read: the gate saw the declaration and verified the id is new in this diff.check-changeset-no-major→✓ This diff introduces no major bump(its LEVEL AXIS reads theClause-②:line above from the PR payload in CI; locally it printed NOT MEASURED, no payload).check-empty-changeset→✓ … 1 declaring changeset(s) added.18.analytics-time-dimension-date-range-vocabulary-closed(FROM → TO for the display spelling, driver-memory'slast N UNITdialect and SQL's bare-ISO single day, each to a preset name or a[start, end]array).registry.tsregenerated (check:migration-registry✓).spec-changes.jsonandprotocol-upgrade-guide.mddo not project step 18 yet — the Refuse the declared relative-date preset vocabulary as a bare temporal comparand at publish time — the ruled C half of #8690, carved out for the spec seat #8793 precedent entry is absent from both as well — and both gates are ✓.Evidence
Pins with negative controls (source-level ablation, restored with blob-hash proof and
git diff HEADempty):packages/spec/src/data/analytics-date-range-closed-vocabulary.test.ts(11 cases): refusal of"Last 7 days"with exactly one issue at['timeDimensions', 0, 'dateRange'], every retired spelling (driver-memory's, SQL's single-day, case and near-miss variants, thecustomsentinel, a macro token), both request schemas, non-arm values described by type, the predicate's structure, ledger membership, type narrowing. Leg A restored the barez.string()arm (anchor 1→0, blobb1e84ba4…≠ HEADe573905a…): 4 refusal cases red, 7 acceptance cases green — the pin would have been red on the unfixed schema.packages/runtime/src/analytics-daterange-refusal-envelope.test.ts(6 cases):400 ANALYTICS_DATE_RANGE_UNRECOGNIZEDon/analytics/queryand/analytics/sql, service not called,declaredCodeabsent, the body parses againstApiErrorSchema, a preset passes the door with the original body forwarded untouched, a multi-field failure staysVALIDATION_FAILED. Leg B restored BASE's door (literal count 2→0, blob equals the BASE blob): 3 code assertions red ('VALIDATION_FAILED'observed), 3 pass-through cases green.Runs at
bf059cf478(underos-verify-lock,VERDICT command-exit 0unless stated):date-range-presets,src/migrations);pnpm --filter @objectstack/spec typecheckexit 0 (tsc + scripts + test layer; the 54-file / 261-error held debt is unchanged).dispatcher-validation-error,dispatcher-plugin.error-envelope);pnpm --filter @objectstack/runtime typecheckexit 0.pnpm --filter @objectstack/service-analytics typecheckexit 2 atbf059cf478— one error,src/__tests__/objectql-daterange.test.ts(187,53): error TS2322: Type '"2026-01-20"' is not assignable to type 'string[] | "today" | … | undefined'— the fixture authored the bare-date spelling the narrowing retires. Repaired at396e627911under a one-line fence lift (PM authorisation, option A of the report): the fixture value is nowdateRange: ['2026-01-20', '2026-01-20'], plus a comment above the test recording that its title names the bare-string dialect this PR retires at the schema and that retiring the test belongs to Analytics drivers (memory, SQL) aligndateRangestring handling to the closed preset vocabulary (driver half of #16041) #16322. Nothing else inpackages/services,driver-memoryordriver-sqlmoved —git diff --name-only bf059cf478 396e627911lists exactly that one file. Reproduce-then-green: the same command exit 2 on the unrepaired tree (the line above), exit 0 after (VERDICT command-exit 0). The test file: 15/15 pass.ObjectQLStrategy.dateRangeBoundslowers both spellings throughconst [start, end = start] = range, so the expected$gte/$ltebounds are byte-identical — no changed line mentionsgte,lte,toEqualorrows. The package's fullpnpm test: 96 files / 2163 tests pass; the spec pins (235/235) and runtime pins (29/29) re-ran unchanged.check:generated✓ 16/16 (api-surface, authorable-surface, docs, export-origins, declaration-map, migration-registry, spec-changes, upgrade-guide, strictness-ledger, liveness, skill-docs/refs, react-blocks, meta-url-spelling, test-typecheck);error-code-provenance✓;error-code-casing✓;dispatcher-error-vocabulary✓;nul-bytes✓ (8170 files);spec-parsed-alias✓ (815 pinned isomorphic — two new pinsIso869/Iso870);skill-examples✓ (258 marked examples; 10 spec-source TSDoc blocks compiled, this one included);quick-reference-counts✓;doc-authoring✓;docs-single-h1✓;cross-package-test-inputs✓;test-source-alias✓;type-check-coverage✓;route-envelope✓;closing-keyword-parity✓;changeset-gate-self-tests✓;ci-filter-parity✓;corpus-claim-drift✓;keyed-text-bounds✓;published-files✓;merge-driver✓;docs-redirects✓ — 59 of the 103 familiesdispatch-gates.mjsderives for this diff.check:type-check-debtexit 3 = PREREQUISITE NOT MET (full closure not built) → NOT MEASURED, not a finding. The 44 unrun derived families (driver/plugin census and shape checks, sourcemaps, vendor stamps, CJS loads, release rehearsal, self-tests of scripts this diff does not edit) touch nothing here and are CI's half.json-schema.manifest/data.json(+2 ids),api-surface/data.json(+6 rows, 0 breaking),export-origins/declaration-map(+6 / +4),registry.ts(+58 lines, the one entry), the reference pages (the enum in bothtimeDimensionsrows, two nested-shape tables,AnalyticsDateRange/AnalyticsDateRangePresetsections, ledger doc +1, index counts 1574 → 1576). No JSDoc detached; no page rewritten beyond those.Consumer sweep
dateRangestrings ontimeDimensions: 3 ×'Last 7 days'atffca0df9cb, allpackages/spectests — triage counted 2 on932acc3d; the third ispackages/spec/src/data/analytics.test.ts:354, same class, same conclusion — re-spelled here; plus theservice-analytics'2026-01-20'fixture, re-spelled in the array form at396e627911(see Evidence). No published dashboard, example app, skill or doc page authors the string arm.51a402f: everytimeDimensions[].dateRangeauthor uses the array form (DatasetWidget.tsxtypes it[string, string];queryDataset.test.ts:473;DatasetWidget.compareTo.test.tsx:122)./analytics/dataset/query'sselection.timeDimensionsis typed fromAnalyticsQuerybut not Zod-parsed at that door, so a string there still reachesdataset-executor(fenced) — noted for Analytics drivers (memory, SQL) aligndateRangestring handling to the closed preset vocabulary (driver half of #16041) #16322.Not in this PR
dateRangestring handling to the closed preset vocabulary (driver half of #16041) #16322 remains open: the drivers' own refusal, the shared conformance fixture, and the retirement of theservice-analyticsbare-string test (its title and the strategy's degeneration alike) are that card's — this PR only re-spelled that test's fixture value.content/docs/releases/edit; no governed surface touched.