Skip to content

fix(spec): nextUtcCalendarDay and utcInstantMs read years 0001..0099 as written, not as 1900..1999 (#20550) - #20591

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-20550-calendar-day-years-below-100
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-20550-calendar-day-years-below-100

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #20550

Clause-②: no

nextUtcCalendarDay (packages/spec/src/data/calendar-day.ts) proves a bare YYYY-MM-DD is a real day by building it and reading it back. It built the date with Date.UTC(y, mo - 1, d), and Date.UTC reads a year from 0 to 99 as 1900 + year, so 0050-01-01 was built as 1950-01-01, the round trip failed, and the helper answered null for every day of the years 0001..0099. utcInstantMs asks the same helper about a bare day, so it answered null for them too. A datetime $lte or $between maximum on such a day then skipped whole-day widening (ADR-0053 D-D) and stopped at the day's first instant.

The accept set is unchanged: a query on years 0001..0099 now answers what the contract already says (the claim's Clause-②: no).

The change

  • packages/spec/src/data/calendar-day.ts: a private utcMidnight(year, monthIndex, day) builds the date as new Date(0) plus setUTCFullYear(year, monthIndex, day), which takes the year as written and rolls the month and day over exactly as Date.UTC does. nextUtcCalendarDay uses it for both the round trip and the next day. The impossible-day refusal is unchanged in kind: 2026-02-30, 0050-02-30 and 0100-02-29 still roll over and are refused by the round trip.
  • utcInstantMs is byte-identical. Its bare-day arm already delegated the "is this a real day" question to nextUtcCalendarDay and then read the instant with Date.parse of ISO text, which reads year 0050 correctly; its timestamp arm never touched Date.UTC. So the one construction fixes both helpers. No other helper in the file builds a date.
  • No edit under packages/core/** or packages/drivers/**: every caller imports these helpers from @objectstack/spec (core re-exports them).

Verification (final head 6a5dce7378, base f11b5f20a2)

Premise, measured before editing:

  1. Helper, on the base. Instrument: tsx importing packages/spec/src/data/calendar-day.ts at f11b5f20a2. nextUtcCalendarDay('0050-01-01') = null, utcInstantMs('0050-01-01') = null; the same for 0001-01-01 and 0099-12-31. Control: '0100-01-01' answers '0100-01-02' and -59011459200000.
  2. Exhaustive, base against head. Instrument: every string YYYY-MM-DD with year 0000..9999, month 00..13 and day 00..32 (4,620,000 strings, 3,652,425 real days), compared with a pure-arithmetic proleptic Gregorian oracle that uses no Date. Base: 36,525 mismatches for each helper, exactly every real day of the years 0000..0099. Head: 0 mismatches for either helper.
  3. Over REST, on the base semantics. Instrument: the new packages/rest/src/data-query-calendar-day-year-below-100.test.ts through POST /api/v1/data/:object/query, process in America/New_York, with calendar-day.ts's construction put back to Date.UTC (reverse verification below). SQLite and PostgreSQL 16 answered the same:
where opened_at base (SQLite, PostgreSQL) head (SQLite, PostgreSQL)
$lte '0050-01-01' y49 y49, y50, y50_last
$between ['0050-01-01', '0050-01-01'] none y50, y50_last
$lte '2026-07-15' (control) c26, y49, y50, y50_last, y50_next the same
$between ['2026-07-15', '2026-07-15'] (control) c26 the same

Rows: y49 = 0049-12-31T10:00Z, y50 = 0050-01-01T10:00Z (the card's row), y50_last = 0050-01-01T23:59:59.999Z, y50_next = 0050-01-02T00:00Z, c26 = 2026-07-15T14:00Z (the card's control), c26_next = 2026-07-16T00:00Z. The next day's midnight stays out in every cell.

Reverse verification (fix committed first, mutation through scripts/ablation-replace.mjs, trap restore to the HEAD blob): the utcMidnight body was replaced by return new Date(Date.UTC(year, monthIndex, day));; the anchor fell 1 to 0 and the replacement rose 0 to 1 on disk; pnpm --filter @objectstack/spec build; scripts/ablation-dist-preflight.mjs found the mutation in 4 built files and the fix's line in none. Then calendar-day.test.ts: 3 failed / 7 passed (0001-01-01: expected null to be '0001-01-02'); the REST file: 2 failed / 2 passed, the table's base column, identical on both cells. Restore: blob f03557d5bb equals HEAD, whole-tree git status --porcelain empty, a full spec rebuild, the preflight found the fix in 4 built files and the mutation in none, and both files went green again (10/10, 4/4).

Tests at 6a5dce7378:

  • pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: 574 files, 16,883 passed, 1 todo.
  • @objectstack/rest, the new file with the existing temporal suites (data-temporal-write-real-day-iso, data-temporal-year-range, data-query-date-year-range, data-date-read-year-below-100, data-date-write-iso-only, data-query-epoch-ms-date-comparand, data-query-having-temporal-door, aggregation-filter-temporal-storage-rule, rest-14078-invalid-date-total-arm), with a private PostgreSQL 16 server (zone Asia/Shanghai) as OS_TEST_POSTGRES_URL: 10 files, 89 passed, 5 skipped. The 5 skips are MySQL cells (OS_TEST_MYSQL_URL unset). The new file ran on SQLite and PostgreSQL, the two drivers the PR fix(objectql)!: a temporal string is written on a real calendar day, and a datetime string in an ISO 8601 spelling, or refused with VALIDATION_FAILED / invalid_date (#20525) #20547 harness runs.
  • driver-sql sql-driver-calendar-day-upper-bound.test.ts (SQLite and PostgreSQL ran, MySQL not run): 15 passed. driver-memory: the six memory-analytics-date-range-* suites and memory-driver-calendar-day-upper-bound.test.ts (run, not edited): 7 files, 103 passed.
  • pnpm --filter @objectstack/spec typecheck and pnpm --filter @objectstack/rest typecheck: exit 0; tsc --listFiles over packages/rest/tsconfig.test.json includes the new test file.
  • Lint, narrowed: eslint --no-inline-config --format json over the three touched .ts files: 3 files, 0 errors, 0 warnings. eslint --print-config resolves a config for each of them, and eslint.config.mjs enables no type-aware linting (no parserOptions.project, no projectService), so the diff cannot move any untouched file's verdict.
  • Gates from node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 83 commands, 81 exit 0. Two were NOT MEASURED, PREREQUISITE NOT MET (exit 3), because they need the whole workspace built: pnpm check:dual-build-cjs-loads (44 packages without dist/) and pnpm check:type-check-debt (7 dependencies without a built type entry). CI runs both.

Full-repo pin sweep: no test asserts null from either helper for a year below 100 (git grep for both names applied to a 00NN- literal: 0 hits), and no suite asserts a $lte / $between / date-range upper bound on a datetime field in those years. So no pin had to change.

Acceptance notes

  • Year 0000. The new construction also answers for year 0000 (0000-12-31 is followed by 0001-01-01), where the base answered null. It is not pinned: 0001..9999 is the supported range, and @objectstack/core's isOutsideTemporalYearRange refuses year 0000 at the comparand and write doors before either helper is asked. The helper does not keep a second copy of that range.
  • Memory driver, through the engine at head (not REST; @objectstack/driver-memory has no binding in packages/rest): $lte '0050-01-01' answers y49, y50; the $between maximum answers y50; the 2026 controls are unchanged.
  • Out of scope, reported to the seat and not changed here: other Date.UTC constructions read an author-given year below 100 as 1900 + year, in packages/core/src/utils/datetime.ts and packages/rest/src/import-coerce.ts. For example, the import door stores a CSV datetime cell 0050-01-01 10:00 as 1950-01-01T10:00:00.000Z. Separately, nextUtcCalendarDay('9999-12-31') answers '10000-01-01', so on SQLite a datetime $lte '9999-12-31' answers no rows. That is unchanged by this PR and measured in the report.

Generated by Claude Code

…as written, not as 1900..1999

Date.UTC reads a year from 0 to 99 as 1900 + year, so the round trip that
proves a bare day real failed for every day of those years and both helpers
answered null: a datetime $lte or $between maximum on such a day skipped
whole-day widening. The date is now built with setUTCFullYear.

Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
Co-authored-by: Claude <noreply@anthropic.com>
…y in year 0050, over REST on SQLite and PostgreSQL

Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:data tests tooling labels Sep 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json c876a7426d930e9e8d310fdffb1f64ae836cdced → packageMentionDocs.

Which tree this was computed on

This run read content/docs from d8ca92ed06496e3d9e03a613dc1b61b4d63515e6 — the merge of head 6a5dce7378eb31968ef97e55a579d87ff1b5cdf8 into base c876a7426d930e9e8d310fdffb1f64ae836cdced, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d8ca92ed06496e3d9e03a613dc1b61b4d63515e6 && git checkout d8ca92ed06496e3d9e03a613dc1b61b4d63515e6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c876a7426d930e9e8d310fdffb1f64ae836cdced 6a5dce7378eb31968ef97e55a579d87ff1b5cdf8 && git checkout -B drift-repro c876a7426d930e9e8d310fdffb1f64ae836cdced && git merge --no-ff 6a5dce7378eb31968ef97e55a579d87ff1b5cdf8

node scripts/docs-audit/affected-docs.mjs --json c876a7426d930e9e8d310fdffb1f64ae836cdced

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 6a5dce7378eb31968ef97e55a579d87ff1b5cdf8
Local-runs: none

① Derived judgments

Scope read first. Head confirmed two ways: refs/os-seat2/pr20591 and the PR's head.sha from the API both name it. Base main at the merge base f11b5f20a2; origin/main has moved to c876a7426d since, and git diff of the two over the PR's four paths is empty. Diff read as the net diff against main: 4 files, +256 / -5, exactly the claim's file surface (calendar-day.ts, its test, one new test-only file under packages/rest/src/, one @objectstack/spec patch changeset). No path under packages/core/** or packages/drivers/**; not the driver-memory test PR #20458 edits; no governed surface (Governed Surface Queue Guard: success). Head repo equals base repo. First body line Fixes #20550; the card's newest Claim: (5883782403) names this branch. Commits carry the model-free trailer pair only.

  1. nextUtcCalendarDay now answers for every real day of 0000-01-01..0099-12-31 (base: null). RIGHT. Read from ECMA-262, not the dev's prose: setUTCFullYear(y, m, d) computes MakeDay(y, m, d) with y as given, while the 0..99 → 1900+y remap (MakeFullYear) is applied only by Date.UTC and the Date(y, m, ...) constructor. new Date(0) supplies a zero time-of-day, so the result is UTC midnight. The helper's own doc already promised null only for "anything that is not a valid bare calendar day", and temporal values outside the years a four-digit text or a backend holds: a datetime comparand for year 10000 or −1 misorders on memory/SQLite and 500s on PostgreSQL; a date in year 0000 500s on PostgreSQL; a date write stores +010000-… verbatim #20264 set 0001..9999 as the supported range (triage 5858474998, landing record 5875779441), so 0001..0099 answering is the contract, not a widening.
  2. Rollover and leap behaviour inside those years. RIGHT. MakeDay rolls month index 12 into the next year and day 32 into the next month exactly as Date.UTC did, so the d + 1 call crosses a month end (0050-01-31 → 0050-02-01) and a year and century end (0099-12-31 → 0100-01-01, then fmtUtcDay pads year 100 to 0100). The leap rule is DaysInYear (divisible by 4, except centuries not divisible by 400): 0004, 0400, 2000 leap; 0100, 2100 not. The test pins 0004-02-29 as a day and 0100-02-29 as refused.
  3. Impossible-day refusal intact. RIGHT. 2026-02-30, 0050-02-30, 0100-02-29, 0001-13-01, 0099-04-31 still roll over in the construction and fail the fmtUtcDay(start) !== day round trip, which is unchanged. fmtUtcDay is byte-identical to main and pads the year to four digits, so both sides of the comparison are the same shape for years 0..999.
  4. Years 0100..9999 unchanged, including the top of the range. RIGHT. The only arithmetic removed is the 0..99 remap; for 9999-12-31 both constructions give year 10000 and fmtUtcDay's padStart(4, '0') does not truncate, so the answer 10000-01-01 is byte-identical at base and head (see ③, finding c).
  5. utcInstantMs answers bare days in 0000..0099 (base: null) with no edit of its own. RIGHT. Its body is byte-identical to main (diffed function by function): the bare-day arm asks nextUtcCalendarDay and then Date.parse of the ISO text, which reads a four-digit year as written; its timestamp arm never touched Date.UTC. The claim's mechanism for :87 was wrong and the dev's falsification is confirmed from the code. No Date.UTC( call remains anywhere in calendar-day.ts; the two remaining mentions are inside the new doc comment.
  6. Public surface. UNCHANGED, RIGHT. No export added, removed or renamed; utcMidnight is module-private (not exported, so export * from './calendar-day' in data/index.ts does not carry it); packages/spec/api-surface/ has no delta; signatures of both exports are the same. The changeset's patch is consistent with this.
  7. Reach, every caller, all through @objectstack/spec/data or core's re-export at core/src/utils/datetime.ts:196. RIGHT, and no narrowing anywhere:
    • driver-sql calendarDayExclusiveUpperBound / calendarDayUpperBoundRewrite / the between companion (sql-driver.ts:15405): $lte and a $between max on a datetime column in 0001..0099 now compile less-than next-day midnight through storageDatetimeValue → canonicalUtcDatetime (ISO text, no Date.UTC), where they compiled less-than-or-equal midnight. Widens the answer set to the whole day. Measured by the dev on SQLite and PostgreSQL 16 through POST /api/v1/data/:object/query.
    • driver-memory where arms (memory-driver.ts:1399, :1457, :1704, :1716) and driver-mongodb (mongodb-filter.ts:1161, :1252): the same direction, via toStorageForm / the mongo temporal converter, neither of which re-derives the year.
    • objectql having-filter.ts wholeDayUpperBound (:1317, behind the having door) and instantsOf (:1280); formula matches-filter.ts lteBound (:853) and order (:886, the RLS check evaluator); service-analytics preview-evaluator.ts (:80, :97, :674), native-sql-strategy.ts (:545, :1237), objectql-strategy.ts (:548, display SQL); driver-memory memory-analytics.ts (:270, :462, :994). Two effects only: an upper bound in 0001..0099 reads as the whole day, and a Date-versus-bare-day pair in those years is lifted to instants where JS relational operators previously answered false unconditionally. No input that was accepted is now refused, and the correction direction is the contract's (ADR-0053 D-D) at every site.
  8. Year 0000 now answers too. RIGHT to leave unpinned, with one residual named. Verified from the code: the door is isOutsideTemporalYearRange (core/src/utils/temporal-storage-form.ts:183, FIRST_SUPPORTED_YEAR..LAST_SUPPORTED_YEAR), reached through isUninterpretableTemporalComparand (temporal-comparand.ts:187, :195, :196). It runs at the engine's single filter collection point (engine.ts:1016, :1107), inside aggregate for the per-aggregation filter (:16353) and having (:16488), and at the record validator's date / datetime arm (record-validator.ts:1316) for every write. So where, filter, having and every stored row are behind it, on every driver. It does NOT sit in front of two helper callers: the analytics explicit dateRange end (native-sql-strategy.ts:545, preview-evaluator.ts:674; the native-SQL decline at :297 judges the lowered where members, and I did not establish that it folds dateRange in) and the formula RLS check bound (matches-filter.ts:853, an authored policy comparand). On both, the only change is that a 0000-MM-DD end now reads as that whole day exactly as every other year's does; since the write door refuses year 0000, no stored row can be moved by it. temporal values outside the years a four-digit text or a backend holds: a datetime comparand for year 10000 or −1 misorders on memory/SQLite and 500s on PostgreSQL; a date in year 0000 500s on PostgreSQL; a date write stores +010000-… verbatim #20264's own claim (5872518067) carried ⛔ Not packages/spec, so the helper not holding a second copy of the range is that card's design, not this PR's omission. Not a FAIL reason; not escalated.
  9. Pins assert the substance, and the ablation counts reproduce from the test text. RIGHT. calendar-day.test.ts head: 6 pre-existing its plus 4 new; on the base construction the three that assert an answer for 0001 / 0050 / 0099 / 0004 fail (nextUtcCalendarDay DAYS, utcInstantMs DAYS, the leap edge) and the impossible-day pin passes on both, which is the dev's 3 red / 7 green. The new REST file: two its per cell; the read-back pin passes on the base and the four-bound pin fails on the two 0050 rows, giving 2 red / 2 green over the SQLite and PostgreSQL cells. The four-bound pin compares one map so a red shows every cell; it asserts the exact id set per bound, that the next day's midnight (y50_next, c26_next) stays out, and that the 2026 controls answer the same rows; the read-back pin guards the storage path against a remap on write. The precedent harness data-temporal-write-real-day-iso.test.ts (PR fix(objectql)!: a temporal string is written on a real calendar day, and a datetime string in an ISO 8601 spelling, or refused with VALIDATION_FAILED / invalid_date (#20525) #20547) runs SQLite always and PostgreSQL under OS_TEST_POSTGRES_URL with no memory cell (no driver-memory binding in packages/rest), so SQLite + PostgreSQL is exactly "every driver that harness already runs".

② Semver level

  • @objectstack/spec: patch. RIGHT: a bug fix in a released package (AGENTS.md Post-Task Checklist §3), no export change, no accept-set relaxation at the public door (the comparand 0050-01-01 was accepted before and after; only the rows it answers move, to what ADR-0053 D-D already states). The changeset prose states the mechanism, the before / after at POST /api/v1/data/:object/query, the unchanged refusals and the reached callers; every sentence checks against the code and the diff.
  • @objectstack/rest: no changeset. RIGHT: the package's only change is a new test file, which publishes nothing (tsconfig.json excludes **/*.test.ts from the artifact; Check Changeset: success).
  • Clause-②: no on the claim, the PR body and the changeset agree. RIGHT: the diff enlarges no public surface and relaxes no accept set at any public door; the helper's documented accept set ("a valid bare calendar day") is unchanged and the code now honours it. The path limb (packages/spec/src/**, non-test) is hit, which is what makes this record owed before enqueue; the declaration limb is not.

③ Boundary flags

open_questions is empty. Each dev flag, answered:

The three out-of-scope findings, each verified read-only and judged:

  • (a) Date.UTC two-digit-year remap outside this file. Confirmed: core/src/utils/datetime.ts zonedWallClockToUtcMs (Date.UTC at :143 and :174), bucketKeyToCalendarRange (:370, :378, :379, :389, :390, :399, :401, :410), the ISO-week label (:310, :313); rest/src/import-coerce.ts parseDateCell (:388); plus formula/src/stdlib.ts:59, service-analytics/src/preview-evaluator.ts:358, trigger-schedule/src/time-relative-trigger.ts:118 / :123, core/src/utils/filter-tokens.ts:198 / :243. None is on the whole-day widening path this PR corrects (the drivers consume the helper's next-day text through canonicalUtcDatetime / toStorageForm, which parse ISO text), so this PR neither introduces nor worsens any of them. Correctly outside the claim, which said to report and not fix. The seat routes it as a family card.
  • (b) Unpadded year in parseDateCell's date arm. Confirmed at import-coerce.ts:362 and :400 (getUTCFullYear() written without padding). The year-padding family of core temporalStorageForm: the date arm leaves a year outside 1000..9999 unpadded — over REST the epoch-ms number for 0999-06-15 counts $gt 0 / $lt 7 on InMemoryDriver and SQLite (correct 6 / 0); its ISO string counts 6 / 0 #20240, not the remap; untouched by this diff. Correctly outside.
  • (c) nextUtcCalendarDay('9999-12-31') = 10000-01-01, so SQLite $lte '9999-12-31' answers no rows. Confirmed and unchanged: both constructions produce year 10000 for (9999, 11, 32) and fmtUtcDay does not truncate; the driver then compiles a text bound less-than '10000-01-01T00:00:00.000Z', which sorts below every four-digit year as text. Pre-existing at the top of the supported range; whether the helper answers null there or the drivers take an extended-year bound is a decision, so leaving it is right. Not a defect this PR introduces or worsens.

Check-runs on this head, one read at 2026-09-29T05:46Z, 32 runs, no failure: 13 success (Check Changeset, Check PR Size, Governed Surface Queue Guard, the three claim guards, Spec property liveness, Type Check · source gates, docs link and drift checks, Auto Label, filter), 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke), 16 in progress and NOT concluded: Lint & Repo Gates (every check:* repo gate), Type Check · workspace / · debt ledger / · consumer gates (the last hosts check:api-surface and check:exported-any), Build Core, Test Core 1..6, Temporal Conformance, Dogfood Regression Gate 1..3, Dogfood Verify CLI. Of the seven required contexts, one had concluded (Governed Surface Queue Guard, success), three were in progress (Lint & Repo Gates, Build Core, Temporal Conformance) and three had no check-run yet at the read (TypeScript Type Check, Test Core, Dogfood Regression Gate, the gate jobs that follow their matrices). Families the runs answer: the spec generated-artifact gates (source gates, success); repo gates, api-surface, both NOT-MEASURED gates, unit tests on every package including the new file's SQLite cell, and the driver-sql live PG + MySQL suites (all in progress). Nothing red is attributable to this diff because nothing is red. The landing waits for every required context to conclude green; this record does not presume it.

Implemented-by: claude/issue-20550-calendar-day-years-below-100
Reviewed-by: session_014EJ1ED8X4MMrT18BhVx4tx

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 29, 2026 06:01
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 3a89d45 Sep 29, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20550-calendar-day-years-below-100 branch September 29, 2026 06:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/m tests tooling

Projects

None yet

2 participants