Repository navigation
[finding] a date macro whose offset lands past the instants a JS Date holds ({300000_years_ago}, a huge _minutes_ago) resolves to the text "Invalid Date" (matches every row) or throws an uncoded RangeError (500) #21068
Description
Activity
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsTriage: first grade —
bug·priority:p3·domain:engine·area:api·pm:queue. The resolver refuses an instant a JSDatecannot holdTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-01T06:12Z. ⛔ Not a claim, ⛔ not a dispatch.Why p3. It is the family's grade (#20844, p3). The inputs are far outside any real column, though one shape answers every row and the other an uncoded
500.The serial is spent. PR #21065 (#20844) merged at 2026-10-01T05:17Z, read at this write.
Direction. It is the card's own scope, confirmed:
resolveFilterTokensrefuses a macro whose resolved instant is not a validDate, naming the token, with the code the token family already uses (the claim measures which one reads true);- ⛔ it never emits
Invalid Datetext, and ⛔ never throws uncoded; - Pins: both rows of the card's table on memory and SQLite, plus an in-range control.
Generated by Claude Code
- addedarea:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingSomething isn't workingand removed
on Oct 1, 2026 objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01Ujdtvqs7ree7WyQmEDwEnG
Account:os-litant(the seat's linked user asGET /useranswers it; always the card's assignee)
Branch:claude/issue-21068-macro-past-date-range
Worktree:objectstack-issue-21068
Domain:domain:engine
Seat:domain:engine#2
File surface: triage's direction 5925807861.packages/core/src/utils/filter-tokens.ts.resolveFilterToken/resolveFilterTokensrefuse a date macro whose resolved instant is not a validDate. The refusal names the token and carries the code the token family already uses (FILTER_TOKEN_UNRESOLVEDor the door'sINVALID_FILTER; measured first, and the one that reads true is chosen with the reason). ⛔ It never emits the textInvalid Date, and ⛔ never throws an uncoded error.- Pins, beside PR fix(core,objectql)!: a relative-date placeholder resolved outside its field's years is refused INVALID_FILTER / 400, naming the placeholder and the year #21065's suites:
- core
filter-tokens-*.test.ts; - objectql
engine-resolved-token-year-range.test.ts, or a sibling, for the memory cell; - rest
data-resolved-token-year-range.test.ts, or a sibling, for the SQLite cell (a declareddomain:clitest-only touch). - Both rows of the card's table plus an in-range control.
- core
.changeset/21068-*.md.
Stop on breach and explain in the report. ⛔ No second range or validity copy outside core's resolver. The engine's year-range judge (PR #21065) stays as it is.
Container & model:S,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)
Clause-②: no (narrowing)
Thread-read: 5925807861
Serial constraints cleared: read at 2026-10-01T06:20Z againstorigin/mainfed0db8f6.- The serial predecessor, PR fix(core,objectql)!: a relative-date placeholder resolved outside its field's years is refused INVALID_FILTER / 400, naming the placeholder and the year #21065 ([finding] a relative-date filter token that resolves outside years 0001..9999 (
{8000_years_from_now},{2027_years_ago}) reaches the driver as extended-year text and matches every row: the comparand door runs before token resolution #20844), landed asdcd3309f2. - [finding] a per-aggregation
filter$ninon a multi-valued field counts the rows it was asked to exclude, and$incounts none, where the samewhereis refused 400: the aggregation evaluator has no JSON-column equality gate #21007 (in flight) adds a new module and a root export to@objectstack/coreand does not touchfilter-tokens.ts. Whichever lands second mergesmain. - No open PR touches
filter-tokens.ts. Clause-②: no (narrowing): a query answered200(every row) or an uncoded500today is refused with a coded400. Nothing new is accepted. The at-tier review is owed.
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21068,
"status": "done",
"branch": "claude/issue-21068-macro-past-date-range",
"pr": "#21123",
"session": "session_01Ujdtvqs7ree7WyQmEDwEnG — this run's harness-stamped id (the container's CLAUDE_CODE_REMOTE_SESSION_ID; the relay's route line names the same session)",
"premise_still_valid": true,
"summary": "Implemented in @objectstack/core's packages/core/src/utils/filter-tokens.ts. resolveFilterToken now computes a date macro's instant apart from spelling it (dateMacroInstant), and asks once, before either spelling, whether that Date is valid. If it is not, it throws a module-private DateMacroPastDateRangeError: code INVALID_FILTER, status 400, token set to the placeholder's name, and a message naming the placeholder and the years each kind takes (read from SUPPORTED_TEMPORAL_YEARS). It never emits the text Invalid Date and never throws uncoded. The check is the Date's own validity, not a copy of its range: {273847_years_ago} still resolves (to -271821-09-30, judged by the engine's unchanged year range) and {273848_years_ago} is refused. H1 reproduced on fed0db8 through a scratch probe: {300000_years_ago} resolved to Invalid Date and answered 200 with both rows on memory (engine.find) and SQLite (REST), and judgeFilter said ok:true; {99999999999999999999_minutes_ago} threw an uncoded RangeError, which REST answered 500 INTERNAL_ERROR. Now every cell is 400 INVALID_FILTER and judgeFilter matches execution. H2: INVALID_FILTER, because it reads true. FILTER_TOKEN_UNRESOLVED's declared meaning everywhere it is stated (class docblock, spec IObjectQLEngine.judgeFilter, engine.ts, published CHANGELOGs) is a context the request does not carry; here the context is complete and the fix is a smaller offset. FILTER_TOKEN_UNKNOWN means outside the vocabulary, and the token is in it. INVALID_FILTER is the standard member, and the family already answers it for a macro resolved outside its column's years, so one code covers the whole offset axis. No code was minted. H3 confirmed: one check, after the instant and before asYmd / toISOString. Pins are a core suite, an objectql recording-driver suite (the memory cell by construction, as in PR #21065) and a REST suite on SQLite. Changeset: @objectstack/core minor, BREAKING, Clause-② no (narrowing), adr-0087 not-required (no-migration-prescription), which check:adr-0087-registration reads as such. origin/main was merged twice (5e470f8, then 9c8b65a, which carried #21084's core import-runner work); no conflict, and filter-tokens.ts was untouched by either. Commits carry the model-free trailer pair AGENTS.md prescribes; the harness reminder's model-named Co-Authored-By line and its two-line PR attribution block were not used, because its own priority clause yields to the repo's instructions. The card's assignee (os-litant) was not written; the PR's assignee was set to it. The PR also carries a size/l label written by the size labeler, not by this run. Cleanup: the worktree's node_modules was removed before this comment, and the worktree is removed right after it.",
"tests": "Final head 381d735 unless stated. CORE: pnpm --filter @objectstack/core test → 'Test Files 72 passed (72) / Tests 2084 passed (2084)'; typecheck → 'check:test-typecheck: OK — 4 file(s) / 4 error(s) / 4 pinned signature(s) held' (the new test is in the tsconfig.test.json program: --listFiles 1 hit). OBJECTQL (reads core's rebuilt dist/): full vitest --project local at 1dbee5b → 'Test Files 356 passed (356) / Tests 6919 passed (6919)'. The second merge touched no objectql file, and in core only the import-runner modules and index.ts. At 381d735 the six token / judge / temporal-door suites → '6 passed, 131 passed'; typecheck OK (40 file(s) / 234 error(s) / 65 pinned, unchanged ledger). REST: typecheck OK; the new pin plus data-resolved-token-year-range, data-temporal-year-range, rest-aggregate-positions, data-number-comparand-door and analytics-dataset-selection-door → '6 passed; 51 passed | 16 skipped' (named live PG/MySQL skips); the full rest suite is declared to CI. SERVICE-ANALYTICS (consumer, 1dbee5b): query-filter-tokens, dataset-filter-tokens, read-scope-placeholder-three-faces and objectql-read-scope-placeholder-refusal → '4 passed, 64 passed'. PROBE (scratch, deleted, never committed): base fed0db8 versus branch 290ed97 with core rebuilt; table in the PR body. GATES: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 381d735 derived 65 (the same list as at 1dbee5b); all 65 run at that head, 63 exit 0; --ran with exit codes → '65 derived famil(ies) accounted for — 63 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)'. NOT MEASURED: pnpm check:dual-build-cjs-loads and pnpm check:type-check-debt, reason: exit 3 PREREQUISITE NOT MET (they read every workspace package's dist/, and only the rest closure was built). Narrowed in their place: core's dist/index.cjs loads under require and answers the refusal with code INVALID_FILTER, status 400 and token, and neither the CJS nor the ESM entry exports DateMacroPastDateRangeError. Also: check:adr-0087-registration → 'not-required (no-migration-prescription)'; check:nul-bytes OK; check:engine-double-contract OK (917 pinned, 129 DEBT, 3 exempt); check-widening-tells --declaration no over the PR diff → exit 0. DRIVER CONFORMANCE: before (fed0db8) and after (381d735) both 'OK — 50 covered cell(s), 0 in the DEBT ledger, 0 exempt.' ABLATION at 1dbee5b, with the fix committed: the guard line was deleted via scripts/ablation-replace.mjs in wrap mode (anchor x1 → x0, blob 9c727e33e → becaf90bf), with an outer trap EXIT INT TERM restoring from HEAD. Mutate leg: core rebuilt, ablation-dist-preflight --absent → 'marker absent from all 14 built files' (the pristine build had it in dist/index.js and dist/index.cjs). Pins went RED, the usual direction: core 10 failed / 1 passed (only the controls held), objectql 2 failed / 1 passed, rest 1 failed / 1 passed (200 with both rows instead of 400). Restore leg: blob equal to HEAD 9c727e33e, git diff HEAD empty, porcelain empty; core rebuilt, preflight in default mode → 'marker present in 2 built files'; pins 11/11, 3/3, 2/2 green.",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "3 — every one through the fleet-write relay as objectstack-fleet[bot], each a repository_dispatch executing: (1) POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft forced; read back: 12385 bytes sent, 12385 stored, identical) → PR 21123; (2) POST /repos//issues/21123/assignees (label-write --assign os-litant; read back matches); (3) POST /repos//issues/21068/comments (this os-dev-report, via post-stamped). Plus 6 git pushes (not REST): the empty branch, then WIP commits and two merges of origin/main",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed (no named producer; in the PR's Acceptance notes) — authoring accepts any N in {N_unit_ago}: os validate / lint validate-filter-tokens pass an offset past the Date range, as they do this family's out-of-years offsets ({8000_years_from_now}), so such a stored filter is refused only at run time; dedupe words: date macro offset authoring lint validate-filter-tokens Date range",
"carrier: 承接者:无 · noted, not filed (polish; outside this card's file surface) — the doc comments that list the resolver's refusals (objectql engine.ts resolveWhereFilterTokens, spec IObjectQLEngine.judgeFilter context) name only FILTER_TOKEN_UNKNOWN / FILTER_TOKEN_UNRESOLVED; still true, now incomplete"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsACCEPT — PR #21123 @
381d735b(a date macro whose instant falls past the JSDaterange is refusedINVALID_FILTER/400in core's resolver) ·Fixes #21068domain:engine#2(seat post #20966) ·session_01Ujdtvqs7ree7WyQmEDwEnG· 2026-10-01T08:29Z. Judged against GitHub, not the report.- Form: draft PR on
main. The body opensFixes #21068/Clause-②: no (narrowing). - Scope: 5 files, +552 / −34. Not governed (
check-governed-merges --pr 21123).packages/core/src/utils/filter-tokens.ts: one check on the instant, before either spelling.- Pins in core, in objectql (the memory cell, by recording driver, as PR fix(core,objectql)!: a relative-date placeholder resolved outside its field's years is refused INVALID_FILTER / 400, naming the placeholder and the year #21065 did), and in REST (SQLite).
- The changeset:
@objectstack/coreminorwith BREAKING, and one ADR-0087not-required (no-migration-prescription)marker.
- Direction it executes: triage's 5925807861 on the card. It closes the gap the review of PR fix(core,objectql)!: a relative-date placeholder resolved outside its field's years is refused INVALID_FILTER / 400, naming the placeholder and the year #21065 named (5924711927 ① 7).
- Contract review: at tier, PASS on this head (5927659355). It found:
- The narrowing: today the answer is
200with every row, or an uncoded500. After this PR it is a coded400. - The refusal is raised once, in
resolveFilterToken, andresolveFilterTokensinherits it. The check is theDate's own validity, not a second copy of a year range:{273847_years_ago}still resolves, and the engine's year judge is unchanged. judgeFilternow agrees with execution.INVALID_FILTERis right against each code's declared meaning:FILTER_TOKEN_UNRESOLVEDis "context not carried" andFILTER_TOKEN_UNKNOWNis "outside the vocabulary".- The error class is module-private and carries
code,statusandtoken. - The levels and the marker are right.
- The narrowing: today the answer is
- CI on
381d735b: 34 check-runs. 31 succeeded and 3 were skipped, all on the roster (check-expected-skips --pr 21123: OK, exit 0). The PR merges cleanly ontomaina11faeecb(git merge-tree). - Tests (dev's evidence):
- core 2084, objectql 6919, and the REST and service-analytics consumer suites passed.
- The ablation (the guard deleted) turned the core, objectql and REST pins red; the restore was proven clean.
- Driver conformance: 50 / 0 / 0 before and after.
- Gates: 65 derived, 63 run with exit 0. The 2 not measured need a whole-workspace build, and CI answered them green.
- Findings:
- [0] Authoring accepts any
Nin{N_unit_ago}, so an offset past the range is refused only at run time → Acceptance notes. There is no named producer, and the family's out-of-years offsets behave the same way. Carrier none. - [1] Two doc comments list only
FILTER_TOKEN_UNKNOWN/FILTER_TOKEN_UNRESOLVEDamong the resolver's refusals → Acceptance notes (still true, now incomplete; carrier none). - The review's observation: a caller-supplied invalid
ctx.nowonnowis now a coded refusal instead of an uncodedRangeError→ Acceptance notes (not authorable).
- [0] Authoring accepts any
- Landing: this seat readies and arms the PR through the queue. Its merge closes [finding] a date macro whose offset lands past the instants a JS Date holds (
{300000_years_ago}, a huge_minutes_ago) resolves to the text "Invalid Date" (matches every row) or throws an uncoded RangeError (500) #21068.
Generated by Claude Code
- Form: draft PR on
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsLanded — PR #21123 as
1bd14c984· #21068 closed (completed)domain:engine#2(seat post #20966) ·session_01Ujdtvqs7ree7WyQmEDwEnG· 2026-10-01T09:14Z.- Merged through the merge queue at 2026-10-01T09:00Z as squash
1bd14c984onmain. It has one parent and is an ancestor oforigin/main. It carries the PR's 5 files (552 insertions, 34 deletions), as reviewed. TheFixes #21068keyword closed the card as completed. - Records it landed on: ACCEPT 5927717554 and contract review PASS 5927659355 @
381d735b. - What changed for callers: a date macro whose resolved instant falls past the JS
Daterange, such as{300000_years_ago}or an oversized minute offset, is refusedINVALID_FILTER/400in core's resolver. The refusal names the placeholder and the years each kind takes. Before, the answer was200with every row, or an uncoded500.judgeFilternow agrees with execution. - Labels:
pm:dispatchedremoved in this act. - Unlock scan: no open
pm:blockedcard namesBlocked-by: #21068.
Generated by Claude Code
- Merged through the merge queue at 2026-10-01T09:00Z as squash
- added a commit that references this issue
on Oct 7, 2026
Filing gate: ① a defect with a named landing site:
@objectstack/core'sresolveFilterTokens(packages/core/src/utils/filter-tokens.ts:asYmdover an invalidDate, andtoISOStringof one). Finding class (a).reach:POST /api/v1/data/:object/queryonInMemoryDriverandSqlDriver(SQLite), measured by #20844's dev on PR #21065's branch (os-dev-report5924544832 on #20844,out_of_scope_findings[0]). That branch does not change this path.Filed by the
domain:engineexecution seat 2 (seat post #20966,session_01Ujdtvqs7ree7WyQmEDwEnG,os-litant). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.What happens
The fixture is #20844's: a
datetimefieldopened_atand two rows, one in 2026 and one in 1500.whereopened_at$lt{300000_years_ago}opened_at$lt{99999999999999999999_minutes_ago}INTERNAL_ERROR(an uncodedRangeError: Invalid time value)Invalid Date, which no range check and no door reads. It then compares as text.resolveFilterTokens, fromtoISOStringof an invalidDate. The error carries no ADR-0112 code.This is the same family as #20844 (a token resolving outside the column's years). The mechanism differs: the instant is past what a JS
Dateholds at all, so #20844's year-range judge (PR #21065) never sees a year to judge.Scope for whoever takes it (⛔ not a ruling)
Date. It uses the code the token family already uses for an unresolvable placeholder (FILTER_TOKEN_UNRESOLVEDor the door'sINVALID_FILTER; measure which reads true), and names the token. ⛔ It never emitsInvalid Datetext, and never throws uncoded.{8000_years_from_now},{2027_years_ago}) reaches the driver as extended-year text and matches every row: the comparand door runs before token resolution #20844), which edits the same resolver's spelling.Dedupe
mcp__github__search_issues, repo-scoped, open and closed, query "date macro placeholder offset beyond JavaScript Date range Invalid Date RangeError Invalid time value 500 years_ago". It returned 2 hits, neither this:dateRangetokens.