Repository navigation
[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
Description
Activity
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsTriage: first grade —
bug·priority:p3·domain:engine·area:api·pm:blockedon #20280. Direction: a resolved token is judged by the same range function as a literalTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-30T11:54Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: the resolution step is objectql's (
resolveFilterTokensis called frompackages/objectql/src/engine.ts) ⇒domain:engine.Why p3. It is a silent wrong answer (every row matches), but only for a token whose resolved instant falls outside 0001..9999, such as
{8000_years_from_now}. No measured author writes such a token, and a literal of the same instant is already refused.Direction (triage's call between the card's two arms).
- Resolution refuses a result outside the range, calling core's one range function,
isOutsideTemporalYearRange(per-kind after PR fix(core,objectql)!: a datetime names a year from 1000 to 9999 at both engine doors; a date keeps 0001..9999 (#20280) #20843). The refusal isINVALID_FILTER/400, naming the token and the year it resolved to, in the comparand door's words. - ⛔ Not a second pass of the whole door after resolution: the other door checks already ran on the literal parts. ⛔ No second copy of the range.
- Pins, on memory and SQLite:
- both tokens in the card's table are refused
400; - a token that resolves inside the range still answers the right rows (the control);
- the
datetimefloor of 1000 applies to a resolved token, as it does to a literal.
- both tokens in the card's table are refused
Serial. After PR #20843 (#20280, in flight), so the range asked is the per-kind one.
State
Blocked-by: #20280
- Resolution refuses a result outside the range, calling core's one range function,
- 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 Sep 30, 2026 objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsReleased: blocker #20280 closed
completed·pm:blocked→pm:queue· 2026-09-30T14:51ZActing on the maintainer's instruction, verbatim 「第 2 条 你处理」. It approves this session's proposal to return this card and #20859 to the queue. The chat was with session
session_013RWUA7bNq5bRhehLPqXwMg, 2026-09-30.- The blocker has cleared. The triage comment blocked this card on driver-sql on MySQL reads a year 0..99 back a century late — REST create stores
placed_on: "0009-03-04"correctly, and…/queryreturns"1909-03-04"; adatetime0009-03-04T10:00Zreturns2004-09-03T10:00Z#20280 in prose only, with noBlocked-by:line, so the unlock scan could not see it. driver-sql on MySQL reads a year 0..99 back a century late — REST create storesplaced_on: "0009-03-04"correctly, and…/queryreturns"1909-03-04"; adatetime0009-03-04T10:00Zreturns2004-09-03T10:00Z#20280 closedcompletedat 2026-09-30T12:20Z, landed by PR fix(core,objectql)!: a datetime names a year from 1000 to 9999 at both engine doors; a date keeps 0001..9999 (#20280) #20843 (merged). That PR puts a datetime literal at years 1000..9999 and a date at 0001..9999 at both engine doors. That range function is the one the triage direction names: "a resolved token is judged by the same range function as a literal". - Release double-check. The latest conversion on this card is the triage comment of 2026-09-30T11:54Z. PR fix(core,objectql)!: a datetime names a year from 1000 to 9999 at both engine doors; a date keeps 0001..9999 (#20280) #20843 was cross-referenced at 2026-09-30T11:47Z, before that comment, so no merged PR is newer than the conversion. No PR has landed for this card itself.
- State.
pm:blocked→pm:queue.priority:p3,domain:engine,bugandarea:apistay. Unassigned.
Generated by Claude Code
- The blocker has cleared. The triage comment blocked this card on driver-sql on MySQL reads a year 0..99 back a century late — REST create stores
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-20844-resolved-token-year-range
Worktree:objectstack-issue-20844
Domain:domain:engine
Seat:domain:engine#2
File surface: the engine's token-resolution stage and the temporal-comparand door, where a resolved{placeholder}meets the field's declared kind:packages/objectql/src/engine.ts: the resolve-then-lower stage only (resolveWhereFilterTokens/resolveRelateThenLowerWhere,withResolvedWhere), not the aggregate door and notvalidate;packages/objectql/src/temporal-comparand-door.ts;packages/core/src/utils/temporal-storage-form.tsread-only (isOutsideTemporalYearRange, the one range per kind: ⛔ no second copy).packages/core/src/utils/filter-tokens.tsstays field-agnostic unless measurement shows the per-kind check cannot live in the engine (then stop and report);- pins beside the door's suites on memory and SQLite;
.changeset/20844-*.md.
Stop on breach and explain in the report.
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)
Clause-②: no (narrowing)
Thread-read: 5913754836
Serial constraints cleared: read at 2026-10-01T02:22Z againstorigin/main2f2fa11d7.- The card's serial predecessor, PR fix(core,objectql)!: a datetime names a year from 1000 to 9999 at both engine doors; a date keeps 0001..9999 (#20280) #20843 (driver-sql on MySQL reads a year 0..99 back a century late — REST create stores
placed_on: "0009-03-04"correctly, and…/queryreturns"1909-03-04"; adatetime0009-03-04T10:00Zreturns2004-09-03T10:00Z#20280), merged as05a7547c9.f6ccca4a4(the kind-named year refusal) is onmaintoo. - In flight in
engine.ts, in other regions: [finding]max/minover a JSON-stored field answers per driver at the engine (memory an object, SQLite a string, PostgreSQL 500): the compatibility table refuses them, but the engine aggregate door enforces only itscount_distinctrow #20914 (aggregate door), engine: validateData forwards engine.validate's onFieldsDropped and answers the drops, and insertMany reports drops per outcome through insertManyData: #20701 item 1, engine half #20922 (validate), picklist metadata kind — runtime: resolvepicklist→ options when serving field metadata, validate writes against the resolved set, apply package-level extensions (phase 1 of objectstack#18164) #19519 (METADATA_ARRAY_KEYS). No open PR or claim touchestemporal-comparand-door.tsor the resolve-then-lower stage. Clause-②: no (narrowing), as05a7547c9andf6ccca4a4were for literals: a query answered200today is refused400. No new export and no new code (INVALID_FILTERis reused). The contract review judges it.
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 20844,
"status": "done",
"branch": "claude/issue-20844-resolved-token-year-range",
"pr": "#21065",
"session": "session_01Ujdtvqs7ree7WyQmEDwEnG (subagent of the PM's session; the branch is the identity, per claim 5923455068)",
"premise_still_valid": true,
"summary": "The engine's resolution stage (resolveWhereFilterTokens, the one stage function that execution on every verb and judgeFilter both call) now judges what a date macro resolved to. If core's isOutsideTemporalYearRange puts the resolved value outside its column's years, the stage refuses INVALID_FILTER / 400, naming the placeholder and the year, in the temporal-comparand door's year-class words; this covers where, the per-aggregation filter and having (by the aggregated column's class). H2 was partly falsified: core spelled a day in year 0 or earlier as -1-10-01 / 0-10-01, and the range read those as 2001 / 2000. So core's field-agnostic resolver now spells a day outside 0001..9999 in the expanded ISO form (+010026-10-01, -000001-10-01). A time column takes the door's [#20480] class as a bounded in-place extension. The datetime floor of 1000 applies to resolved placeholders, and in-range placeholders answer as before.",
"tests": "H1 on 2f2fa11 (scratch probe; engine.find on InMemoryDriver and POST /api/v1/data/:object/query on SqlDriver/SQLite; two rows, 2026 and 1500): opened_at $gt {8000_years_from_now} returned 200 with both rows; $lt {2027_years_ago} returned 200 with the 1500 row (the card said both rows; base resolved it to -1-10-01, which reads as 2001-01-10); $lt {1977_years_ago} returned 200 with no row; having max(opened_at) $gt kept both groups; judgeFilter returned ok:true. After the fix each is 400 INVALID_FILTER on both drivers, and the control {100_years_ago} still answers the 1500 / 2026 row. Package runs, tested at fc3fc97 (final head 4c5f259 adds only a merge touching no package source): core vitest --project local passed 62 files / 1822 tests, and typecheck passed. objectql (dist rebuilt) vitest --project local passed 350 files / 6860 tests, --project repo passed 1 file / 5 tests, and typecheck plus check:test-typecheck passed. rest typecheck passed; the new pin plus 3 related suites gave 21 passed and 26 named live-DB skips; earlier, 27 token/temporal rest suites at 574bcbb gave 755 passed and 65 skipped; the full rest suite is declared to CI. Ablations ran through scripts/ablation-replace.mjs (wrap, trap restore; blob equals HEAD and git diff HEAD empty) and scripts/ablation-dist-preflight.mjs, with a rebuild on every dist-resolved leg; all three went red. (1) Stage judge never called (engine.ts, d5a8e10): the objectql pin went 7/9 red and the REST pin 1/2 red, with the marker in objectql dist (4 files); restored, 9/9 and 2/2 green and the marker absent. (2) Core expanded-year spelling reverted (4dcb910): the core pin went 12/24 red and the objectql pin 6/8 red through the rebuilt core dist, with the original marker absent from dist; restored, 24/24 and 8/8 green, the marker present (2 files) and the tree clean. (3) Time branch forced to 'inside' (d5a8e10): the first attempt was a no-op that ablation-replace refused (the replacement contained the anchor, so the anchor count did not drop); redone with a disjoint replacement, the objectql pin went 1/9 red; restored, 9/9. Driver conformance ledger: 50 covered, 0 DEBT, 0 exempt both before and after.",
"mcp_calls": "0",
"api_writes": "3 writes landed, all through the fleet-write relay (each is POST /repos/objectstack-ai/objectstack/dispatches, executed by objectstack-fleet[bot]): (1) pr_create, i.e. POST /repos/objectstack-ai/objectstack/pulls, opening draft #21065; the read-back was byte-identical (16288 bytes). (2) label-write.mjs --assign os-litant, i.e. POST /repos//issues/21065/assignees, with labels untouched; the read-back MATCHES. (3) post-stamped.mjs --comment=20844, i.e. POST /repos//issues/20844/comments (this report). One earlier pr_create attempt was refused locally by write-pace (exit 10, budget 40/40) and wrote nothing. Plain git pushes of the branch are not REST writes.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: POST /api/v1/data/:object/query on InMemoryDriver and SqlDriver/SQLite (measured on this branch, unchanged by it). opened_at $lt {300000_years_ago} answers 200 with both rows (right answer: none), and {99999999999999999999_minutes_ago} answers 500 INTERNAL_ERROR. evidence: a date macro whose offset lands past the instants a JS Date holds is mishandled two ways. A day-or-coarser macro resolves to the text 'Invalid Date' (core filter-tokens.ts asYmd over an invalid Date), which no range or door reads. A sub-day macro throws an uncoded RangeError (toISOString of an invalid Date) inside resolveFilterTokens. Same family as #20844 (a token resolving outside 0001..9999), but a different mechanism and no pinned refusal shape, so it was not fixed in place. #20844 is in flight, so this is for the seat to file or fold. dedupe words: 'date macro offset past Date range Invalid Date' · 'relative date placeholder RangeError Invalid time value 500' · 'years_ago overflow filter token resolver'"
],
"gates": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
"node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
"node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
"node scripts/check-ci-filter-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-adoption.mjs :: exit 0",
"node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-corpus.mjs :: exit 0",
"node scripts/check-dev-prereqs.mjs --self-test :: exit 0",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
"node scripts/check-empty-changeset.mjs --self-test :: exit 0",
"node scripts/check-engine-split-ratio.mjs --days 90 :: exit 0",
"node scripts/check-engine-split-ratio.mjs --self-test :: exit 0",
"node scripts/check-issue-citations.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
"node scripts/check-registry-log-declared.mjs :: exit 0",
"node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
"node scripts/check-system-context-census.mjs :: exit 0",
"node scripts/check-system-context-census.mjs --self-test :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
"node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
"node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
"node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
"pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
"pnpm check:changeset-gate-self-tests :: exit 0",
"pnpm check:cross-package-test-inputs :: exit 0",
"pnpm check:dispatcher-error-vocabulary :: exit 0",
"pnpm check:doc-authoring :: exit 0",
"pnpm check:driver-memory-census :: exit 0",
"pnpm check:dts-closure :: exit 0",
"pnpm check:dual-build-cjs-loads :: exit 3",
"pnpm check:durability-log-level :: exit 0",
"pnpm check:engine-double-contract :: exit 0",
"pnpm check:gitlink-declared :: exit 0",
"pnpm check:issue-citations :: exit 0",
"pnpm check:kernel-hook-pairs :: exit 0",
"pnpm check:lean-entry-closure :: exit 0",
"pnpm check:logger-receiver-detach :: exit 0",
"pnpm check:nul-bytes :: exit 0",
"pnpm check:objectql-double-limit :: exit 0",
"pnpm check:objectui-changeset :: exit 0",
"pnpm check:org-identifier :: exit 0",
"pnpm check:page-declaration-shape :: exit 0",
"pnpm check:pm-changeset-deadline-census :: exit 0",
"pnpm check:published-files :: exit 0",
"pnpm check:query-options-erasure :: exit 0",
"pnpm check:refd-timer-probe :: exit 0",
"pnpm check:slot-lookup :: exit 0",
"pnpm check:sourcemap-no-sources-content :: exit 0",
"pnpm check:stack-collection-maps :: exit 0",
"pnpm check:swallow-census-controls :: exit 0",
"pnpm check:test-source-alias :: exit 0",
"pnpm check:tier-file-adoption :: exit 0",
"pnpm check:type-check-coverage :: exit 0",
"pnpm check:type-check-debt :: exit 3",
"pnpm check:watch-hint-literal :: exit 0",
"pnpm check:where-matcher :: exit 0"
],
"gates_not_measured": [
"pnpm check:dual-build-cjs-loads :: exit 3, reason: PREREQUISITE NOT MET, it needs every workspace package's dist and only the core/objectql/rest closure was built; declared to CI",
"pnpm check:type-check-debt :: exit 3, reason: PREREQUISITE NOT MET, the --re-measure needs the whole-workspace build (lint.yml builds it first); declared to CI"
],
"gates_reconcile": "node scripts/pm/dispatch-gates.mjs --ran (exit-coded record) at 4c5f259: 67 derived, 65 run, 2 NOT-MEASURED (derived from recorded exit 3), 0 UNRUN",
"ci_state": "in_progress at report time (head 4c5f259: 14 check runs completed with no failure, 17 in progress); not awaited, per the contract",
"deviations": [
"H2 route changed: packages/core/src/utils/filter-tokens.ts was edited (the spelling of a day outside 0001..9999). The claim lists it as staying field-agnostic, not read-only, and it stays field-agnostic. Without this edit the engine could not read the year of a token resolving to year 0 or earlier (isOutsideTemporalYearRange of '-1-10-01' is false for both kinds, measured in UTC, New_York and Shanghai). A consumer-side parse would be the lenient fallback that AGENTS.md rules out.",
"engine.ts aggregate region touched in 2 places (the per-aggregation resolution call passes its judge; one comment made false is corrected). This is the resolve-then-lower stage's call site, not the aggregate door.",
"The time column (the door's [#20480] class) was added as a bounded in-place fix: same defect class, same file, the door's pinned words, same gates. Measured at REST: opens_at $gt {8000_years_from_now} answered both rows.",
"The memory cell's refusal is pinned by the objectql recording-driver suite (lane convention; objectql and rest do not depend on driver-memory). Memory rows were measured by scratch probe, with no committed memory+engine pin.",
"The PM's write-budget instruction was followed: the background until-loop on write-pace --status, then one write. The first two until-loops matched nothing because the middle-dot in the grep pattern was transcoded; I stopped them with TaskStop and re-armed with an ASCII pattern.",
"Harness attribution trailer: commits carry the model-free pair Claude-Session / Co-authored-by: Claude, per AGENTS.md (pre-push accepted them). The PR body uses the AGENTS.md session-URL footer, not the harness's own footer form."
],
"files_changed": [
".changeset/20844-resolved-token-year-range.md",
"packages/core/src/utils/filter-tokens.ts",
"packages/core/src/utils/filter-tokens-year-outside-range.test.ts",
"packages/objectql/src/engine.ts",
"packages/objectql/src/temporal-comparand-door.ts",
"packages/objectql/src/engine-resolved-token-year-range.test.ts",
"packages/rest/src/data-resolved-token-year-range.test.ts"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsACCEPT — PR #21065 @
4c5f259e(a date macro that resolves outside its column's years is refused at the engine's resolution stage,INVALID_FILTER/ 400)domain:engine#2(seat post #20966) ·session_01Ujdtvqs7ree7WyQmEDwEnG· 2026-10-01T04:25Z. Judged against GitHub, not the report (5924544832).-
Form: draft PR on
main. The body opensFixes #20844/Clause-②: no (narrowing), its only closing keyword. -
Scope: 7 files, inside claim 5923455068:
@objectstack/core:filter-tokens.ts(the out-of-range day is now spelled in expanded ISO form; the resolver stays field-agnostic) and its pin.@objectstack/objectql:engine.ts, the resolve-then-lower stage's own sites only (the aggregate door block is untouched);temporal-comparand-door.ts; the new suite.packages/rest: one REST pin.- The changeset: core
minor, objectqlminor, BREAKING, one ADR-0087not-required (no-migration-prescription)marker.
Not governed.
-
Contract review: at tier, PASS on this head (5924711927).
- Every accept-set change is a pre-driver narrowing through core's one range function, with no second copy, across:
whereon all six verbs, the per-aggregationfilter,having,judgeFilter, thedatetimefloor of 1000, and thetimeclass (a bounded in-place fix; the four conditions hold). - In-range, non-temporal and context placeholders are untouched.
- The core spelling change is the contract-first fix of a producer whose old text no reader read correctly (
-1-10-01parsed as 2001). Clause-②: no (narrowing)withminor+ BREAKING matches the serial predecessor05a7547c9.- The coupling with PR fix(objectql)!: engine aggregate asks the field-type table for every row — min / max / avg over a refused type answer INVALID_FIELD / 400 on every driver #21037 (queued) is textually disjoint, with no shared symbol.
- Every accept-set change is a pre-driver narrowing through core's one range function, with no second copy, across:
-
CI on
4c5f259e: 34 check-runs: 31 success and 3 skipped, all on the roster (check-expected-skips --pr 21065: OK, exit 0). It merges cleanly ontomain(git merge-tree). -
Tests (dev's evidence):
- H1 reproduced on
2f2fa11d7:{8000_years_from_now}returned both rows.{2027_years_ago}returned the 1500 row, because the base resolved it to-1-10-01, read as 2001. The card's table now answers400on memory and SQLite, and the{100_years_ago}control still answers. - core: 1822 passed. objectql: 6860 passed.
- Three ablations: the stage judge, the core spelling and the
timebranch each turned red and were restored clean. - Driver conformance: 50 / 0 / 0.
- Gates: 65 of 67 run, all exit 0; 2 not measured (prerequisite).
- H1 reproduced on
-
Findings: the date-macro offset past the JS
Daterange (Invalid Datetext, or an uncodedRangeError500) → filed [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. That card is serial after this PR, which edits the same resolver. -
Landing: this seat readies and arms the PR through the queue.
-
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsLanded — PR #21065 as
dcd3309f2(the card closes)domain:engine#2(seat post #20966) ·session_01Ujdtvqs7ree7WyQmEDwEnG· 2026-10-01T05:18Z.- Merged through the merge queue at 2026-10-01T05:17Z as squash
dcd3309f2onmain. Two readings agree: the PR object andorigin/main's log. The squash carries the PR's 7 files (912 insertions, 52 deletions). - Records it landed on: contract review PASS 5924711927 @
4c5f259e, and the ACCEPT on this card. - What changed for callers:
- A date macro whose resolved value falls outside its column's years answers
INVALID_FILTER/400, naming the placeholder and the year. That coversdate0001..9999,datetime1000..9999, and thetimefour-digit class. It applies towhereon every verb, the per-aggregationfilter,having, andjudgeFilter. @objectstack/core's resolver spells a day outside 0001..9999 in expanded ISO form.- In-range placeholders answer as before.
- A date macro whose resolved value falls outside its column's years answers
- The card closes
completedby the PR'sFixes #20844.pm:dispatchedis removed in the same act as this record. - Follow-up: [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, the macro offset past the JSDaterange, was serial after this PR. It is now unblocked on that count and is with triage for its first grade.
- Merged through the merge queue at 2026-10-01T05:17Z as squash
- added a commit that references this issue
on Oct 7, 2026
Filing gate: ① a defect with a named landing site. Finding class (a).
reach:measured at the public REST doorPOST /api/v1/data/:object/queryoverSqlDriver(better-sqlite3), and onengine.findover the in-memory driver.The measurement is the #20280 dev's (
os-dev-report5910296005 on #20280,out_of_scope_findings[0]), at PR #20843's heada3afb404b6with a scratch REST probe. That diff does not touch this path, somainanswers the same.Filed by the
domain:engineexecution seat 1 (session_01DEvba2nBuD4tWzfq8r8NFY,os-support-ai). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.What happens
The object has a
datetimefieldopened_atand two rows, one in 2026 and one in 1500.whereopened_at$gt{8000_years_from_now}opened_at$lt{2027_years_ago}A literal comparand outside 0001..9999 is refused
INVALID_FILTER/ 400 at the temporal-comparand door (#20264). A{placeholder}is exempt from that door, because it is not a readable instant until it is resolved. It is resolved after the door (resolveFilterTokens,packages/core/src/utils/filter-tokens.ts, called frompackages/objectql/src/engine.ts). So a token whose resolved instant falls past 9999 or before 0001 reaches the driver as extended-year text (+010026-…,-000001-…), which compares as text and matches every row. A token that resolves into 0001..9999 compares right ({1977_years_ago}, #20599).Scope for whoever takes it (⛔ not a ruling)
isOutsideTemporalYearRange, which PR fix(core,objectql)!: a datetime names a year from 1000 to 9999 at both engine doors; a date keeps 0001..9999 (#20280) #20843 (driver-sql on MySQL reads a year 0..99 back a century late — REST create storesplaced_on: "0009-03-04"correctly, and…/queryreturns"1909-03-04"; adatetime0009-03-04T10:00Zreturns2004-09-03T10:00Z#20280) makes per-kind (datetimefrom 1000). A resolved token is judged by the same range as a literal: either the door runs again on the resolved value, or resolution refuses a result outside the range. ⛔ No second copy of the range.Dedupe
mcp__github__search_issues, repo-scoped, open and closed, in the act that filed this card:2026-02-30) is rolled over as a datetime comparand and is a 500 on PostgreSQL as a date comparand, and a non-ISO datetime comparand is read in the host zone #20549, temporal values outside the years a four-digit text or a backend holds: adatetimecomparand for year 10000 or −1 misorders on memory/SQLite and 500s on PostgreSQL; adatein year 0000 500s on PostgreSQL; adatewrite stores+010000-…verbatim #20264, specnextUtcCalendarDayanswers null for every day in years 0001..0099 (Date.UTC reads them as 1900..1999), so a datetime$lte/$betweenmax on such a day skips whole-day widening and misses that day's rows #20550 and An unparseable date comparand on a datetime filter is passed through and compares false — HTTP 200, zero rows, no diagnostic — while an unknown{placeholder}is correctly rejected 400 (17.0.0 GA) #8690 are all closed. temporal values outside the years a four-digit text or a backend holds: adatetimecomparand for year 10000 or −1 misorders on memory/SQLite and 500s on PostgreSQL; adatein year 0000 500s on PostgreSQL; adatewrite stores+010000-…verbatim #20264 refused literal comparands outside the range, and [finding] outside calendar-day.ts, a year from 0001 to 0099 is still read as 1900..1999: Date.UTC's two-digit-year remap in core's datetime and bucket helpers, filter-tokens and the REST import's datetime cell #20599 fixed tokens in 0001..0099. None covers a token resolving outside the range.Dedupe words:
relative date placeholder resolves outside year range·years_from_now token extended year datetime where·filter token year 10000 comparand door bypassGenerated by Claude Code