Repository navigation
fix(spec,core): the rolling last_N_days presets close on {today} at the spec table, the save door's prescription and core's analytics preset arm - #23208
Conversation
…he spec table, the save door's prescription and core's analytics preset arm The three rolling windows were the table's open arm ($gte-only), so the prescribed window counted every future-dated row; core's preset arm ended at NOW. Both now select [N_days_ago, end of today], the extent the console's lowering already uses; core keeps its own stops-before spelling (tomorrow). Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 6 package(s): ⛔ 1 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 146 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 d94062790db8a4c37826a764690b39ae4be49908 && git checkout d94062790db8a4c37826a764690b39ae4be49908
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 06dbc4e59d1a37011114441317b6f918386cd690 8927fbc43cbdb646f0578cc8f0967a3672b569dd && git checkout -B drift-repro 06dbc4e59d1a37011114441317b6f918386cd690 && git merge --no-ff 8927fbc43cbdb646f0578cc8f0967a3672b569dd
node scripts/docs-audit/affected-docs.mjs --json 06dbc4e59d1a37011114441317b6f918386cd690
|
…nclusive {today} end, in core's stops-before spelling
Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ
Co-authored-by: Claude <noreply@anthropic.com>
…lling-preset-upper-bound
…lling preset end as NOW, open or $gte-only now read the closed {today} window; the unreachable end === null verdict branch is removed
Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ
Co-authored-by: Claude <noreply@anthropic.com>
|
Contract reviewServed-tier: Inputs read. Card #23170: body and all 7 comments (6139344728 triage, 6139488818 claim, 6141817295 and 6142517145 amendments, 6142485762 and 6143940540 Check-runs on the head (read 2026-10-13, every run ① Derived judgments(a) The table and its type — right. (b) One extent at every face — right. Spec: (c) Rows selected — judged in ②. Neither an accept-set narrowing nor a widening; a bug-class correction of query semantics toward the one ruled extent. (d) Shipped sentences — one FALSE sentence remains on the head. Verified TRUE in context on the head: every sentence of (e) The (f) DELIBERATE CORRECTION — CONFIRMED. On the head ② Semver levelChangeset:
③ Boundary flagsReport 6142485762: (1) stale PR body — rewritten in round 2; the head body carries both amendment sections and the designed-red section: answered. (2) model-named trailer amended before push — the four branch commits carry only Report 6143940540: (1) three sweep edits beyond the six ( Escalated by this record (not a dev flag): the ADR-0087 semantic entry in ①(d), outside every amended surface, so the seat amends the claim surface for it. Why FAIL. One shipped sentence in the contract path leg still teaches the open window: Implemented-by: VERDICT: FAIL |
…acement names the closed last_30_days window; the changeset carries lint and verify Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ Co-authored-by: Claude <noreply@anthropic.com>
|
Contract reviewServed-tier: Inputs read. Card #23170: body and all 9 comments (6139344728 triage, 6139488818 claim, 6141817295 / 6142517145 / 6143978957 / 6144366518 seat amendments and notes, 6142485762 / 6143940540 / 6145883266 Check-runs on the head (every one of the 48 ① Derived judgments(1) The round-1 FAIL ground is closed. (2) The changeset. (3) The double-quote sweep, re-run by my own (4) Carry-over. The round delta (5) DELIBERATE CORRECTION on (6) (7) CI on the head. As read above: every check green except ② Semver levelCarried unchanged from round 1 on a round that touched no accept set: ③ Boundary flagsReport 6145883266, Implemented-by: VERDICT: PASS |
Fixes #23170
Clause-②: no (no accept set widens: the three rolling windows gain the inclusive
{today}end every calendar preset already has)What this does
The three rolling presets (
last_7_days,last_30_days,last_90_days) were the open arm ofDATE_RANGE_PRESET_MACRO_WINDOWS:end: null, prescribed to authors as{ $gte: '{N_days_ago}' }. A$gte-only window has no upper bound, so on a column that carries future dates it counted every later day as well. Triage's direction (comment 6139344728), applied here: the three rows become['{N_days_ago}', '{today}']and theend: nullarm is retired. The refusal's prescription names the window with both its bounds, and core's analytics preset arm selects the same extent, keeping its own end spelling.One extent at all three faces: from the start of the day N days ago through the end of today, in the reference timezone.
DATE_RANGE_PRESET_MACRO_WINDOWS['{30_days_ago}', null]['{30_days_ago}', '{today}']bareDateRangePresetComparandMessagee.g. { $gte: '{30_days_ago}' }e.g. { $between: ['{30_days_ago}', '{today}'] }resolveAnalyticsDateRangePresetendExclusive: falseendExclusive: true(tokentomorrow, the same endtodayalready uses)Files in the claimed surface:
packages/spec/src/data/date-range-presets.ts: the three rolling rows, the table type (end: string), the docblock (the open-arm paragraph is replaced by "every entry closes"), and the prescription (theend === nullbranch is gone, so the message always uses the$betweenpair).packages/spec/src/data/date-range-presets.test.ts: the rolling rows are pinned to their{today}end. The extent test now covers the rolling rows: their token days and expected windows on the reference day, and day counts of 8, 31 and 91. Also new: the card's measurement as a pin, and a pin that the prescription is the closed$between.packages/core/src/utils/analytics-date-range.ts: the rolling rows ofPRESET_WINDOW_TOKENSbecome['N_days_ago', 'tomorrow'], the type becomesendBefore: string, the NOW arm is removed, and the docblocks change to match.packages/core/src/utils/analytics-date-range.test.ts: the NOW pins are retired. New pins: every preset isendExclusive; a rolling window ends wheretodayends in every sweep zone; the card's measurement at this face; a row later today than the reference instant is inside. The#17341sweep now iterates all thirteen presets, the rolling ones included.packages/lint/src/validate-preset-comparands.test.ts: the verbatim verdict pin now readsits window is { $between: ['{30_days_ago}', '{today}'] }..changeset/23170-rolling-presets-close-on-today.md:@objectstack/specpatch and@objectstack/corepatch.Claim amendment 6141817295: two out-of-surface pins of the old extent (test expectations only, no source change)
packages/services/service-analytics/src/__tests__/analytics-date-range-conformance.test.ts: the preview test now assertsendExclusivetrue, with the last instant of today inside and the first instant of tomorrow outside. The executor test pins[2026-08-10T00:00:00.000Z, 2026-09-10T00:00:00.000Z, true], andpreviousPeriodis unchanged at2026-07-10to2026-08-09.packages/verify/src/dashboard-window.test.ts:190: the pin now reads{ $gte: '{7_days_ago}', $lte: '{today}' }.Claim amendment 6142517145: text this change made false, corrected in this round (no behaviour change except item g's dead branch)
packages/lint/src/rule-explanations.ts, theos explain filter-preset-comparandparagraph.{ $gte: START }, a calendar one{ $between: [START, END] }— or an ISO date."{ $between: [START, END] }, its first day through its last, and a rolling preset's last day is{today}— or an ISO date."packages/core/src/utils/analytics-date-range-conformance.ts, the finding sentences.ROLLINGdocblock. BEFORE: "The three presets whose upper bound is NOW rather than a calendar boundary." AFTER: "The three rolling presets: their window runs through today and stops before tomorrow begins."@param optionsnote. BEFORE: "the rolling presets (whose bound is NOW)". AFTER: "every preset, each anchored on that instant's calendar day".packages/drivers/driver-memory/src/memory-analytics.ts. BEFORE: "The three rollinglast_N_dayspresets end at NOW and stay INCLUSIVE." AFTER: "…stop where'today'stops, at tomorrow's midnight, withendExclusive: trueas well."packages/services/service-analytics/src/dataset-executor.ts. BEFORE: "the ten calendar presets report the first instant AFTER the window … The three rolling presets end at NOW, a moment they REACH, so their bound is already the last day." AFTER: "every preset reports the first instant AFTER the window (this_monthends at the next month's start, and a rollinglast_N_dayswindow at tomorrow's start) …".packages/services/service-analytics/src/strategies/native-sql-strategy.ts. BEFORE: "the three rolling ones end at NOW and reach it ($lte)". AFTER: "every preset stops BEFORE its end instant ($lt), the three rolling ones before tomorrow begins, astodaydoes".packages/services/service-analytics/src/strategies/objectql-strategy.ts. BEFORE: "while the three rolling ones end at NOW, a moment they reach". AFTER: "every preset stops BEFORE its end instant … the three rolling ones before tomorrow begins, astodaydoes".packages/verify.dashboard-window.ts:22-23. BEFORE: "its first and last day, inclusive, the last open for the three rollinglast_N_dayspresets". AFTER: "its first and last day, inclusive; the three rollinglast_N_dayspresets end on{today}".dashboard-window.ts:554. BEFORE: "first and last day inclusive, the rolling end open". AFTER: "first and last day inclusive (every preset names its last day)".packages/verify/README.md:161, which ships in the package'sfiles. BEFORE: "e.g.{ $gte: '{7_days_ago}' }". AFTER: "e.g.{ $gte: '{7_days_ago}', $lte: '{today}' }".content/docs/deployment/cli.mdx:2035-2036. BEFORE: "({ $gte: '{7_days_ago}' }forlast_7_days, …)". AFTER: "({ $gte: '{7_days_ago}', $lte: '{today}' }forlast_7_days, …)".memory-analytics-date-range-timezone.test.ts. BEFORE: "that leg ends at NOW … (endExclusiveis false for the rolling family …)". AFTER: "that leg stops before tomorrow begins, astodaydoes … (endExclusiveis true for every preset; …)".memory-analytics-date-range-conformance.test.ts:35. BEFORE: "Frozen so the three rolling presets, whose bound is NOW, are comparable." AFTER: "Frozen so the presets, every one anchored on NOW's calendar day, are comparable."packages/lint/src/validate-preset-comparands.ts:301, the dead branch.end === null ? '{ $gte: …}' : '{ $between: […] }'. AFTER:'{ $between: […] }'.null extends (typeof DATE_RANGE_PRESET_MACRO_WINDOWS)[DateRangePreset][1]resolves tounreachable, andtscexits 0. The control, assertingreachable, fails with TS2322 (exit 2).nullends, 13stringends.presetComparandVerdicttakes itsendonly from the table, its one call site isfinding(), and no site in the tree assigns into the table..changeset/pre/15953-verify-dashboard-window-family.md:15. This is an in-place correction of the quoted window only, and the 63-byte frontmatter is byte-identical. BEFORE: "as written ({ $gte: '{7_days_ago}' }, …)". AFTER: "as written ({ $gte: '{7_days_ago}', $lte: '{today}' }, …)".Sweep.
git grepwas run over the tree (CHANGELOGs andcontent/docs/releases/**excluded) for sentences that state the rolling end as NOW, open or$gte-only. Every hit is either edited (above) or true as written:.changeset/23170-*lines describe the before-state.{ $gte: '{30_days_ago}' }in filter examples and tests is an authored macro filter, not a preset's window:date-macros.zod.ts,skills/objectstack-query/rules/filters.md, the lint, objectql and spec test fixtures, and the measured positive control invalidate-preset-comparands.ts:91.open armin the component-type, flow-template and messaging files is an unrelated sense of the phrase.analytics-date-range.ts:64("An end at NOW would drop …") is a counterfactual that is true.Why
check-empty-changesetis red, by designnode scripts/check-empty-changeset.mjs --base origin/mainrefuses this PR by name under the foreign changeset rule (ruling D on #17712), because the PR changes.changeset/pre/15953-verify-dashboard-window-family.md, a note it did not add. The rule names two classes with opposite remedies, and this PR is the DELIBERATE CORRECTION class: "your change may have made this PENDING release note false, and you rewrote it in the same stroke. Remedy: do NOT restore it -- say so on the PR and get it confirmed; restoring it from the base would put the false sentence back." The note is pending: the18.0.0-next.0release consumed it into.changeset/pre/, and it is aggregated into the stable18.0.0notes at pre exit. Its sentence quoted the rolling window as{ $gte: '{7_days_ago}' }, which this PR makes false, so it is corrected in place and the red is left standing. The confirmation is the PR comment that names the gate.Verification (merged head
da95e4e8e)origin/main(75cada0ea) was merged with a merge commit byscripts/pm/os-regen-merge.sh(no conflicts, no os-regen debt). Thenpnpm install --frozen-lockfile, and a whole-workspace build (turbo run build --filter=!@objectstack/docs, 74/74 tasks).pnpm --filter @objectstack/spec check:generated: all 14 artifacts are up to date.dispatch-gates --commandson the merged head gives 113 commands, plus the 29 commands of CI's type-check source-gates lane that are outside the derived list (pnpm check:future-spec-majorincluded), 142 in all.check-empty-changesetexits 1 (the deliberate correction above).check:dual-build-cjs-loadsis measured this time (exit 0 after the full build).--ran: 113 derived, 113 run, 0 NOT-MEASURED, 0 UNRUN.os explaintests included);dashboard-window.test.ts10/10 (boots the stack);cli.mdxreaders (docs-cli-enumeration-parity,create-example-retired-docs-parity,init), 3 files / 87 tests;date-range-presetsandfilter-preset-comparand, 24/24.7ca2abfba, before the merge: spec's full suite, 661 files / 20110 tests.typecheckscripts, each exit 0 (lint and verify includecheck:test-typecheck).7ca2abfba, viascripts/ablation-replace.mjs: the rolling end was reverted to the reference instant. 5 red, 44 green:stops where today stops,keeps the rest of today, and the#17341sweep for the three rolling presets. The today−5 / today+5 pin stayed green, which is the expected direction because core's face already counted 1. Restored: blob == HEAD, andgit diff HEADis empty..d.tsnarrows toend: string, and the api-surface snapshot does not move.PRESET_RANGESalready closes on{today}.Patch round (seat note 6144366518)
This answers contract review 6144351693 (FAIL on
da95e4e8e, one shipped sentence). Head:8927fbc43. No merge oforigin/mainwas needed:git merge-treeagainst06dbc4e59is clean, and this round touches no generated artifact.packages/spec/src/migrations/entries/semantic/18.filter-preset-ordering-comparand-refused.ts,replacementonly, in the entry's own double-quote spelling.{ $gte: "{30_days_ago}" } for last_30_days. AFTER:{ $between: ["{30_days_ago}", "{today}"] } for last_30_days.spec-changes.jsonand the protocol-upgrade pages are gitignored, andcheck:spec-changes/check:upgrade-guideare green..changeset/23170-rolling-presets-close-on-today.md.'@objectstack/lint': patch(the changedos explain filter-preset-comparandparagraph) and'@objectstack/verify': patch(the changed README in itsfiles).packages/andcontent/docs/for"{7_days_ago}","{30_days_ago}"and"{90_days_ago}":$gte-only.packages/core/src/utils/temporal-comparand.ts:23,packages/lint/src/validate-preset-comparands.ts:29,packages/objectql/src/temporal-comparand-door.ts:20,packages/spec/src/data/filter.zod.ts:1718andpackages/spec/src/data/filter-preset-comparand.test.ts:11.$gte:packages/objectql/src/temporal-comparand-door.ts:478,481andpackages/objectql/src/engine-aggregate-having-temporal-door.test.ts:297.packages/spec/src/data/analytics.zod.ts:833(["{7_days_ago}", "{today}"]).packages/lint/CHANGELOG.md:8950,packages/objectql/CHANGELOG.md:3850andpackages/spec/CHANGELOG.md:36696.content/docs/: 0 hits.8927fbc43:pnpm --filter @objectstack/spec check:generated, after a real spec build: all 14 up to date.dispatch-gates --commandsderives 115 (it addscheck:spec-changesandcheck:upgrade-guide, both exit 0).check-changeset-no-major --base origin/mainexits 0,check-adr-0087-registration --base origin/mainexits 0, andcheck-empty-changeset --base origin/mainexits 1, the designed red (PR comment for this head).PREREQUISITE NOT METin this fresh worktree (check:skill-examples,check:dual-build-cjs-loads,check:lean-entry-closure). After a whole-workspace build they were re-run, each exiting 0, together withcheck:sourcemap-no-sources-content,check:dts-closureandcheck:published-files, also each exiting 0.--ran: 115 derived, 115 run, 0 NOT-MEASURED, 0 UNRUN.rule-explanationsplusvalidate-preset-comparands: 35/35.pnpm --filter @objectstack/spec typecheckexits 0.Acceptance notes
ResolvedAnalyticsDateRange.endExclusiveis nowtruefor every value the preset resolver returns. The field stays, because each face's array arm still producesendExclusive: falsefor a caller's explicit window.last_7_dayscovers 8), which is the extent the console's lowering already selects. That was triage's ruling, and it is pinned as such.packages/verify/src/dashboard-window.tsstill carries$lte-absent arms (selects,nestedIn, and theto ?spread at the condition build). The closed table no longer feeds them. They are left as is because the amendment covers comments only in that file.test/verify-dashboard-windows.test.tsis declared to CI.origin/mainmoved 12 commits after this merge, and 7 gate-input files changed there. None of them is a family that reads a path this diff touches. The queue rebuilds on currentmain.Generated by Claude Code