Skip to content

fix(driver-sql): bucket a Field.date as its calendar day on PostgreSQL and MySQL - #21611

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-21485-date-bucket-calendar-day
Oct 3, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-21485-date-bucket-calendar-day

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #21485
Clause-②: no

What changes

SqlDriver.buildDateBucketExpr now reads the column's declared type from the object it already receives. A declared Field.date is bucketed as its own calendar day with no zone conversion. A Field.datetime, or a column with no declaration, keeps the UTC-instant expression byte for byte.

dialect Field.date (new) Field.datetime / undeclared (unchanged)
PostgreSQL to_char((col)::date::timestamp, FMT) to_char((col)::timestamptz AT TIME ZONE 'UTC', FMT)
MySQL date_format(col, FMT) date_format(convert_tz(col, @@session.time_zone, '+00:00'), FMT)
SQLite unchanged (strftime on TEXT, no zone) unchanged

Both callers render this one function, so both follow without a second edit: aggregate()'s GROUP BY (buildDateBucketExpr(g.field, g.dateGranularity, table), with table = coercionKey(builder)), and the public dateBucketSql(objectName, …) member that the analytics SQL echo reads. No service-analytics source is touched.

The mechanism, measured

PostgreSQL 16.14 with the server's TimeZone at Asia/Shanghai, at the expression:

date value old day old week old month new (all five granularities)
2026-06-01 2026-05-31 2026-W22 2026-05 2026-06-01 / 2026-W23 / 2026-06 / 2026-Q2 / 2026
2026-01-01 2025-12-31 2026-W01 2025-12 2026-01-01 / 2026-W01 / 2026-01 / 2026-Q1 / 2026
2024-12-30 2024-12-29 2024-W52 2024-12 2024-12-30 / 2025-W01 / 2024-12 / 2024-Q4 / 2024

Casting a date to timestamptz makes midnight in the session zone; AT TIME ZONE 'UTC' then reads the previous UTC day on any session east of UTC. A session west of UTC does not shift (midnight local is later the same UTC day), which is why only east-of-UTC servers show it.

Why ::date::timestamp and not the bare ::date the dispatch suggested. EXPLAIN VERBOSE shows to_char((d)::date, …) resolves to to_char((d)::timestamp with time zone, …) through the implicit cast, so the bare form still round-trips the session zone. Measured over every day 1900..2100 in ten zones, that round trip differs from the zone-free form on exactly one day each in Pacific/Apia (2011-12-30 prints 2011-12-31) and Pacific/Kiritimati (1994-12-31 prints 1995-01-01), the days those zones skipped. ::date::timestamp resolves to_char(timestamp without time zone, …) and consults no zone.

MySQL (H2), measured on MySQL 8.0.46 (the Ubuntu mysql-server-core-8.0 binary, run from a private datadir in this container) at a global +08:00. At the expression, convert_tz(d, '+08:00', '+00:00') shifts a DATE exactly like PostgreSQL: 2026-06-01 → day 2026-05-31, month 2026-05; 2024-12-30 → 2024-W52. Through the driver's default composition it does not, because the driver pins its own session to +00:00 (withUtcSession), which makes convert_tz the identity. A host pool.afterCreate that sets the session zone (the driver chains it after its own hook) brings the shift back. Per triage's ruling ("gets the same split if a DATE shifts"; "No server-timezone dependence for a date on any dialect"), the MySQL arm gets the same split.

Pins

  • packages/drivers/driver-sql/src/sql-driver-21485-date-bucket-calendar-day.test.ts (new). One matrix over DIALECT_CELLS through declareDialectCell: date and datetime × every granularity the dialect buckets in SQL (declared per dialect and asserted equal to supports.queryDateGranularity, so the iterated set cannot shrink silently) × session zone. Each cell asserts both doors: aggregate() and the dateBucketSql() text run as SQL. The rows sit on month, quarter, year and ISO-week boundaries, plus the empty bucket.
    • sqlite: the zone-free control.
    • live postgres / live mysql, each twice: as provisioned (both axes, after assertThreeWayZoneSkew) and session at +08:00 through a host pool.afterCreate (the date axis, after asserting the session really is at +08:00). The second run is red-capable on any server, and it is the only red-capable route on MySQL.
  • sql-driver-temporal-dialect.test.ts: a no-server pin (runs in Test Core too). On pg and mysql2, a declared Field.date expression names no timestamptz, time zone, convert_tz or time_zone; on pg it carries ::date::timestamp; the undeclared control keeps the UTC-instant arm.

Where the live pins run in CI (H4). Job Temporal Conformance (live PG + MySQL) (required), step Run driver-sql suite against both live servers. It runs the whole driver-sql package with OS_TEST_POSTGRES_URL, OS_TEST_MYSQL_URL, OS_EXPECT_LIVE_DIALECT_MATRIX=1 and TZ=America/New_York, with PostgreSQL at Asia/Shanghai and MySQL at a global +08:00. No workflow edit is needed. PR #21577 (#21564) wires the separate service-analytics step and is not part of this PR.

Reverse verification and ablation

All runs: PostgreSQL 16.14 at Asia/Shanghai, MySQL 8.0.46 at +08:00, process TZ=America/New_York, OS_EXPECT_LIVE_DIALECT_MATRIX=1; the new file plus sql-driver-temporal-dialect.test.ts.

  1. Before the fix was written (the test committed alone, 26b8310fb): the new file 15 failed / 32 passed of 47. The date axis was red at all five granularities on live postgres as provisioned, live postgres at +08:00, and live mysql at +08:00. Live mysql as provisioned (the driver's UTC pin), every datetime cell and sqlite were green.
  2. Fix committed (aa061685b), then sql-driver.ts reverted to the base text (git restore --source=f6b752083; on-disk anchors calendarDay 3 → 0, the old PostgreSQL month text 0 → 1): 16 failed / 45 passed of 61. That is the same 15 live cells, plus the no-server pin. Restored with git checkout HEAD -- packages/drivers/driver-sql/src/sql-driver.ts. The hash-object was be60fd469…, equal to the HEAD blob; git diff HEAD and git status --porcelain were empty.
  3. Fix in place: 61 of 61 green.
  4. Ablation, the ::timestamp hop (scripts/ablation-replace.mjs, anchor 1 → 0): (??)::date::timestamp replaced by the bare (??)::date. Predicted: the live pins stay green at Asia/Shanghai (the round trip is the identity there) and only the no-server pin reds. Observed: 1 failed / 60 passed, the no-server pin. Restored blob = HEAD, git diff HEAD empty.
  5. The four service-analytics cells the card measured (objectql-face-order-limit.test.ts, which resolves @objectstack/driver-sql through dist/): the fix was disabled in dist/ (const calendarDay = false, rebuilt, ablation-dist-preflight marker present in 2 built files). Result: 4 failed / 22 passed of 26, exactly the card's four. After restore + rebuild + --absent preflight: the file plus objectql-echo-date-bucket.test.ts gave 42 of 42 green against live PostgreSQL at Asia/Shanghai. No assertion change is needed in objectql-face-order-limit.test.ts, so this PR does not touch it.

Dispatch hypotheses

  • H1, holds. Both call sites pass the object name as the coercion key (aggregate: coercionKey(builder); dateBucketSql: objectName, which the analytics echo fills from extractObjectName(cube)). temporalFieldKind(table, field) answers 'date' from dateFields, which only type: 'date' populates. No new parameter.
  • H2, measured (above). PostgreSQL: the card's mechanism, confirmed. MySQL: the DATE shifts under any east-of-UTC session; through the driver only with a host-set session zone.
  • H3. Only type: 'date' feeds dateFields. SQLite stores a date as TEXT, and strftime reads it with no zone, so the SQLite arm has no instance of this class; its sqlite cells are the control. The SQLite week arm is untouched (analytics on SQLite: a week-bucketed (or non-UTC zone) dimension still echoes date_trunc, which SQLite refuses; driver-sql has no SQLite week expression #21595).
  • H4, answered above.

Driver conformance ledger

pnpm check:driver-conformance, before (f6b752083) and after (c20518cfd7): 50 covered, 0 DEBT, 0 exempt, both times. The dialect axis was 8 suites (7 matrix, 1 named cell), 0 DIALECT ledger, both times. The new file imports no shared spec/data case-set, so it adds no cell. A spec-level shared case-set would have made every one of the five drivers consume it or carry DEBT. That is outside this card's file surface and against the no-new-DEBT commitment, so the matrix is driver-local.

Local verification

  • pnpm --filter @objectstack/driver-sql exec vitest run --maxWorkers=2, with all three dialects live as in CI, at eab3f3bde (after merging origin/main b610eabf7): 228 files, 5521 passed, 1 skipped (the skip is pre-existing, in schema-drift.base-type-mismatch.test.ts). The run printed "all 3 dialects were exercised".
  • At the final head c20518cfd7, whose last commit only removes the as any casts in the new test: pnpm --filter @objectstack/driver-sql typecheck was green (--listFiles lists all 228 test files). The new file, sql-driver-temporal-dialect.test.ts and live-dialect-matrix.isolation.test.ts gave 81 of 81 green with all three dialects live.
  • Gates at c20518cfd7: node scripts/pm/dispatch-gates.mjs --commands derived 65, and all 65 were run with exit 0. --ran printed: "65 derived famil(ies) accounted for, 65 run, 0 NOT-MEASURED". One gate was red on the way: check:query-options-erasure, because the new test's as any raised the test surface 236 → 237. It was answered by typing the query and the config, not by raising the number.
  • ESLint, a narrowed run: the three changed .ts files, all in the lint population (--print-config resolves a config for each), 3 files / 0 errors / 0 warnings in the --format json output at c20518cfd7. eslint.config.mjs enables no type-aware linting (no parserOptions.project), so this diff cannot move any untouched file's verdict. The full pnpm lint is CI's.

Acceptance notes

  • MySQL datetime under a host-set session zone (outside this card's ruling, not changed). Measured through the driver on MySQL 8.0.46 with a host pool.afterCreate setting time_zone = '+08:00': 2026-06-01T03:00:00.000Z stores 2026-06-01 03:00:00.000 in DATETIME(3), and find() reads it back correctly, but aggregate() by day answers 2026-05-31. convert_tz(…, @@session.time_zone, '+00:00') is right for a legacy TIMESTAMP column and wrong for a DATETIME(3) that already holds the UTC wall clock. Under the driver's default composition it is the identity. No in-repo host sets the session zone, so this is noted here, not filed.
  • An external (federated) object that declares type: 'date' over a remote timestamptz column would now bucket by the session-local day (::date on a timestamptz). That is an inference only, not measured, and no producer is named.
  • service-analytics comments (strategies/objectql-strategy.ts, strategies/types.ts) and the objectql-echo-date-bucket.test.ts docblock quote the PostgreSQL … AT TIME ZONE 'UTC' … text. That is still the datetime expression; they are not edited, because a service-analytics source edit is outside this PR's surface.
  • Raise rule: no hosted or shipped deployment was measured running PostgreSQL at a non-UTC TimeZone. The non-UTC readings here are a private server in this container.
  • Merged origin/main b610eabf7 (two commits, packages/spec and docs only, disjoint from this diff) before the full-suite run.

Generated by Claude Code

claude added 5 commits October 3, 2026 15:42
…ect and session zone

Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
Co-authored-by: Claude <noreply@anthropic.com>
…L and MySQL

A date has no instant. The PostgreSQL arm cast it to timestamptz and the
MySQL arm handed it to convert_tz, both of which invent midnight in the
session's zone, so on a session east of UTC a calendar day bucketed into
the previous day, month, quarter or year. The declared type now picks the
arm: a Field.date buckets its own day with no zone conversion, a
Field.datetime and an undeclared column keep the UTC-instant arm unchanged.

Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
Co-authored-by: Claude <noreply@anthropic.com>
…calendar-day bucket

Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
Co-authored-by: Claude <noreply@anthropic.com>
…d of erasing them

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

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/driver-sql, touching 2 documentable anchor(s).

9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/drivers.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/data-modeling/index.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/data-modeling/queries.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/permissions/tenant-audit-census.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/plugins/packages.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/protocol/kernel/index.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/protocol/kernel/lifecycle.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/protocol/objectql/query-syntax.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/protocol/objectql/types.mdx (via SqlDriver (symbol, a top-level class))

⛔ 3 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/releases/v17/17-5.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/releases/v17/17-6.mdx (via SqlDriver (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

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 — 11 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 9a4182a752fd53b24a14bbcd2e1b4270e1174e7a → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 1fed374c53c3c6e38de207195f7333b5dd6669b4 — the merge of head c20518cfd7787f8ba3f82d74fc85ca4254e416e8 into base 9a4182a752fd53b24a14bbcd2e1b4270e1174e7a, 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 1fed374c53c3c6e38de207195f7333b5dd6669b4 && git checkout 1fed374c53c3c6e38de207195f7333b5dd6669b4
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9a4182a752fd53b24a14bbcd2e1b4270e1174e7a c20518cfd7787f8ba3f82d74fc85ca4254e416e8 && git checkout -B drift-repro 9a4182a752fd53b24a14bbcd2e1b4270e1174e7a && git merge --no-ff c20518cfd7787f8ba3f82d74fc85ca4254e416e8

node scripts/docs-audit/affected-docs.mjs --json 9a4182a752fd53b24a14bbcd2e1b4270e1174e7a

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 9a4182a752fd53b24a14bbcd2e1b4270e1174e7a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 3, 2026 17:21
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 3, 2026 17:21
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 3, 2026
Merged via the queue into main with commit 440cd32 Oct 3, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21485-date-bucket-calendar-day branch October 3, 2026 17:57
os-project-manager pushed a commit that referenced this pull request Oct 3, 2026
…mporal-pg-url

Brings in PR #21611 (440cd32), which buckets a Field.date as its calendar day on PostgreSQL and MySQL -- the fix for the four live-PostgreSQL objectql-face-order-limit cells this branch's wiring exposed.

Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…eSQL URL (objectstack-ai#21577)

Fixes objectstack-ai#21564
Clause-②: no

## What this changes

The `Temporal Conformance (live PG + MySQL)` job's step "Run the non-SQL
temporal backends under the skewed process zone" now sets
`OS_TEST_POSTGRES_URL` in its `env:`, with the same literal the job's
three SQL steps use
(`postgres://postgres:postgres@127.0.0.1:5432/postgres`), for the
postgres service the job already provisions. Until now that step set
only `TZ`, so every `service-analytics` live-PostgreSQL cell
(`describe.skipIf(!config)`) was a named skip there while the job read
green.

The header clause "no CI step provisions that variable for this package"
in 15 `service-analytics` test files becomes false with this wiring. It
is corrected, comment-only, to name the step that now sets it. No other
line of those files changes: every changed line in them is a ` * `
comment line. The 15th file, `objectql-echo-date-bucket.test.ts`, landed
on `main` after this PR was opened, and the takeover added it (claim
amendment `5973803595`).

No other job, no other step, and no timeout changes. No cell is
re-skipped.

## What the step needed, measured (A2)

Only the one `env:` line:

- **Build closure.** `turbo run build
--filter=@objectstack/service-analytics... --dry=json` selects 16
packages, including `@objectstack/driver-sql`, `@objectstack/objectql`
and `@objectstack/driver-sqlite-wasm` (dev dependencies of
`service-analytics`). The step's existing build already builds what the
cells import.
- **PostgreSQL client.** `pg` is an optional peer of `driver-sql`,
resolved by pnpm into `driver-sql`'s `knex` instance;
`require.resolve('pg')` from that `knex` answers `pg@8.22.0`. The cells
reach `pg` through `SqlDriver`, so nothing is added to
`service-analytics`.
- **Database and tables.** Every cell connects to the default `postgres`
database and creates and drops its own tables. The table names are
distinct across the 17 files that read the URL (`os21316_deal`,
`os21505_coercion`, `os21441_bucket_deal`, ...), so parallel vitest
workers do not collide.
- **Server zone.** The job's existing step "Point both servers at a
non-UTC timezone" sets the server to `Asia/Shanghai` before any test
step.

## Readings

- **A3.** No `service-analytics` file reads
`OS_EXPECT_LIVE_DIALECT_MATRIX` (`git grep` exit 1), and no code in the
step's other four packages reads it. So the flag is not added to the
step.
- **A4.** No `service-analytics` file reads `OS_TEST_MYSQL_URL` or any
other server variable the job provides. Its only `process.env` reads are
`OS_TEST_POSTGRES_URL` (34, in 17 files), `NODE_ENV` (10) and `TZ` (4).
Nothing is listed for MySQL.
- **A5.** `core`, `formula`, `driver-memory` and `driver-mongodb` have
zero `OS_TEST_POSTGRES_URL` hits (`git grep` exit 1 for each). Adding
the variable to the step starts no test in them.

## Acceptance

- **CI, on this head `6e12d6934a`:** `Temporal Conformance (live PG +
MySQL)` (job `111301243023`) is success, and step 14, the step this PR
wires, is success (3m38s). All 34 check runs are complete: 32 success, 2
skipped (`Check Changeset` under `skip-changeset`, and the opt-in
packed-tarball smoke).
- **The cells run in CI, by contrast with the head before the fix:**
- At `a3fb6c0334`, with this same wiring, step 14 failed. Four
`objectql-face-order-limit` live-PostgreSQL cells went red on objectstack-ai#21485's
defect: a `date` month bucket landed a day early on a non-UTC server.
- objectstack-ai#21485 was fixed by PR objectstack-ai#21611 (`440cd329a9`), which this branch now
carries. With the same wiring, step 14 passes at `25fb56f1b7` and at
`6e12d6934a`.
- That red-then-green on the same step shows the cells run there and now
pass.
- **NOT MEASURED: a quoted step-14 log line.** The job-log host answers
Forbidden from the seat containers, and the API's log tail does not
reach step 14. A seat whose egress reaches the log host can read step
14's per-file vitest lines for `objectql-face-order-limit` and
`objectql-echo-date-bucket`.
- **Local reproduction of the job's two zones** (PostgreSQL 16.14 with
`timezone=Asia/Shanghai`, process `TZ=America/New_York`, the full
`service-analytics` suite on the merged tree `25fb56f1b7`, from vitest's
JSON reporter):
  - With the URL: 4419 passed, 0 failed, 2 skipped of 4421.
  - Without it: 4160 passed, 261 skipped.
  - So 259 cells move from skipped to run.
- `objectql-face-order-limit`: 26 / 0 / 0, including the four formerly
red cells.
  - `objectql-echo-date-bucket`: 16 / 0 / 0.
- The two remaining skips are cell-scoped by design: the SQLite cell's
"the server is not on UTC" check in `read-scope-temporal-coercion`, and
a SQLite-only case in `analytics-faces-one-lowering`. Their PostgreSQL
twin, "(pg) the server is not on UTC", passed, so the run was not
vacuous.

## Acceptance notes

- The ruling says each of these cells "asserts a non-UTC server". At
this tree, 1 of the 17 files does
(`read-scope-temporal-coercion.test.ts`, `show timezone`). The other 16
rely on the job's provisioning step for the server's zone. Noted, not
changed.
-
`packages/plugins/plugin-security/src/rls-boolean-comparand-door.test.ts:40`
carries the same "no CI step provisions" clause about its own package.
This PR's step does not run that package, so the clause there stays true
and is not touched.
- **Stall guard.** `check:stall-guard-budget` reads this step's static
budget as window 10m, cap 20m, job budget 30m, slack 10m. The step ran
3m38s on this head.
- **Release.** Nothing publishes: `.github/workflows/ci.yml`, plus
comments in test files outside `service-analytics`'s `files[]`. No
changeset; `skip-changeset`.
- **Governed.** `check-governed-merges.mjs`: 0 of 16 paths hit the
register, NOT governed; 118 changed lines (+80 / −38).

## Gates

Run on head `6e12d6934a`: the merge `1866c0185c` of `origin/main`
`5b5e83f446`, plus the header commit. Each exit code was recorded before
any pipe.

- `dispatch-gates.mjs --commands` derived 87 commands. All 87 ran, and
all exited 0. `--ran` reconciled them as 87 derived, 87 run, 0
NOT-MEASURED.
- `check:changeset-gate-self-tests` was re-run once, alone, after the
container's commit-signing hop answered 503 inside its scratch
repository on the first pass. The re-run exited 0.
- `service-analytics`: its build closure built, and
`objectql-echo-date-bucket.test.ts` passed without the URL (10 passed, 8
PostgreSQL cells skipped locally). Its full suite with the URL is in
Acceptance above.

Opened by `domain:spec` seat 2 (`session_01YDt3PzwfrkuFzUBF89WPmM`);
taken over by `domain:spec` seat 1 (`session_01T9u38rswFp5Rw8DswRUReJ`,
claim `5973375306`).

---------

Co-authored-by: Claude <noreply@anthropic.com>
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 size/m tests tooling

Projects

None yet

2 participants