Repository navigation
fix(driver-sql): a MySQL NOW() datetime default carries its column's precision - #21252
Conversation
…precision A declared Field.datetime with defaultValue 'NOW()' is built DATETIME(3) on MySQL, but nowColumnDefault's datetime arm fell through to a bare CURRENT_TIMESTAMP (precision 0). MySQL refuses a CURRENT_TIMESTAMP default whose precision differs from its column's, so the whole CREATE TABLE failed and sys_activity / sys_presence were never created. One source for the precision, MYSQL_DATETIME_PRECISION, now read by the declared column, the builtin audit columns (whose default routes through nowColumnDefault, as on SQLite), the NOW() default, the UPDATE stamp and the legacy TIMESTAMP widening. Postgres and SQLite emit unchanged SQL. Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp Co-authored-by: Claude <noreply@anthropic.com>
…lumn on every dialect Section 1 compiles the DDL with no server: a declared NOW() datetime column is byte-identical to a builtin audit column on mysql2, pg and better-sqlite3, and on mysql2 the default names the column's own precision. Section 2 runs through declareDialectCell: the table syncs (create and add-column), an insert omitting the field answers the instant the column DEFAULT stored, and the server catalogue reports the declared column's type and default equal to created_at's. Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp Co-authored-by: Claude <noreply@anthropic.com>
…efault Widening a legacy TIMESTAMP column to DATETIME(3) with ALTER ... MODIFY restated the audit columns' default and dropped a declared NOW() field's, so an insert omitting the field stored NULL afterwards. The TIME twin already restated it. Both now take nowColumnDefault's expression. Adds the changeset and a MySQL-only pin for the widening. Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 11 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 79d9b22c215da0644acf4761b0f01e01d201d9ef && git checkout 79d9b22c215da0644acf4761b0f01e01d201d9ef
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4727fcb22a7e6a28da1ad20ef74469fbabb8dd0e d334fe314b451d84ae64f59697391df55dd3aff2 && git checkout -B drift-repro 4727fcb22a7e6a28da1ad20ef74469fbabb8dd0e && git merge --no-ff d334fe314b451d84ae64f59697391df55dd3aff2
node scripts/docs-audit/affected-docs.mjs --json 4727fcb22a7e6a28da1ad20ef74469fbabb8dd0e
|
…sql-now-default-precision
Contract reviewServed-tier: Inputs, and nothing else: card #21241 (body; comments 5940340798, 5941097310, 5941379243, 5942461506, 5942490638), PR #21252 (body as the seat edited it, file list of 3, the net diff against merge base ① Derived judgmentsOne precision source — right. At the base (
Builtin audit columns' MySQL DDL bytes unchanged — right. PostgreSQL and SQLite DDL unchanged — right. No PostgreSQL or SQLite arm is touched in The widening restating a declared Pins — they prove what they claim. §1 compiles DDL offline on No migration for fresh MySQL tables — right. A Inheritors. ② Semver level
③ Boundary flags
Check-runs on the head, read at 2026-10-01T23:19:36Z: 41 runs, all Implemented-by: VERDICT: PASS Rendered 2026-10-01T23:23Z by a subagent of the seat session; the seat adopts the verdict. Generated by Claude Code |
…nd ADRs that decided them (stage 3 of objectstack-ai#20595) (objectstack-ai#21268) Part of objectstack-ai#20595 Clause-②: no ## What changed Stage 3 of the `domain:engine` lane of the dead-citation sweep: `packages/objectql/**`, comment and docblock prose only, per the claim (`5941871762`). Stages 1 and 2 (`packages/metadata-protocol`) landed as `a7d9768ec` and `d150c3039`; objectstack-ai#20595 stays open for the next stage (`driver-sql`). Every comment or docblock site in the package that cited a tracker number answering 404 is rewritten in ruling C+D's form C (record `5749154545` on objectstack-ai#19123): the ADR when one records the decision, otherwise the commit in this repository's history that made it. That is **279 sites on 275 lines in 67 files, covering 70 numbers**: - **136 census sites** (136 lines, 17 files under `src/`): the whole `allocated-but-absent` population of the gate's own census in this package at the base, the slash-joined `plugin.ts` `objectstack-ai#10629` from the post-landing census (`5923084795`, now at `:1543`) included; - **1 site in `vitest.config.ts`** (`:61`, `objectstack-ai#17853`): outside the census glob, inside the claimed surface; - **142 test-comment sites** (138 lines, 49 test files), which the census defers. They carry 51 numbers: the census itself reads 36 of them as dead elsewhere in the repository, and never judges the other 15 (they stand only in test files here), which the board and a single read each settle. **Anchors: 66 numbers by commit, 4 by ADR, 0 by words alone.** 45 numbers reuse the anchor another lane or stage already measured for them, 23 were measured here, and 2 are split between a reused and a measured commit (see the table). Two depart from another lane's anchor for a stated reason (`objectstack-ai#10629`, `objectstack-ai#10243`, under Wordings to check). Only comments changed. Every file keeps its line count (275 lines out, 275 in, plus the changeset), so no line citation into any of them moves. No code token moves (the guard below). **No citation number is added**: on every changed line, the numbers on the new text are a subset of those on the old, and the diff-scoped gate judged the 14 citations left on changed lines: 13 resolve and 1 is a declared cross-repo reference. **A `patch` changeset**: 54 of the 137 rewritten non-test lines are in the published `dist` (the `.d.ts` keeps JSDoc on exported members, and esbuild keeps some comments in the JS), and `dist` is not byte-identical with the base text (see Changeset). ## Census: `objectql`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count is its `allocated-but-absent` findings under `packages/objectql/`. | reading | tree | board | whole-repo `allocated-but-absent` | sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `4727fcb22`, run 22:38:04Z to 22:41:42Z | enumerated, 191 pages, frontier objectstack-ai#21252, 19,073 records (newest number read before and after the run: objectstack-ai#21252) | 592 | **136** | 136 | 17 | 50 | | after | `8d6465457`, run 23:19:32Z to 23:22:54Z | enumerated, 191 pages, frontier objectstack-ai#21261, 19,082 records (newest before and after: objectstack-ai#21261) | 456 | **0** | 0 | 0 | 0 | The whole-repo drop is 136, and the two finding sets differ by exactly the 136 rows of this package, removed; none was added. `resolves` (34,638), `resolves-as-pull-request` (2,095) and `cross-repo-unjudged` (1,144) did not move. The card's 135 was taken at `f11b5f20a2` with the older extractor; the base here reads 136, the difference being `plugin.ts` `objectstack-ai#10629`. The same census at the first base `d150c3039` (before a fast-forward to `4727fcb22`, which touched no `objectql` file) read the identical 136 rows. The head's only later commit is a merge of `main` that touches no file under `packages/objectql` (`git diff 8d64654 1e08958 -- packages/objectql` is empty). **Supplementary instrument, the whole package.** The census reads neither test files nor strings nor files outside `src`. A second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `namesThisRepository` over every tracked file in the package (430 `.ts`, 6 `.json`, 2 `.md`, `LICENSE`), and classifies each citation with the gate's `classifyCitation` against one board enumerated by the gate's `enumerateBoard` (191 pages, frontier objectstack-ai#21252, 19,073 records, 22:43:50Z). Every one of the 70 numbers in the population was then read on its own over the issues endpoint: **all 70 answer 404**, and the lit controls `objectstack-ai#5286` and `objectstack-ai#12624` answer 200. | reading | citations | dead | src comment | test comment | test string | changelog | |---|---|---|---|---|---|---| | before, `4727fcb22` | 9,338 | **388** | 137 | 142 | 43 | 66 | | after, head | 9,059 | **109** | 0 | 0 | 43 | 66 | `src comment` includes `vitest.config.ts`. The drop of 279 citations is exactly the rewritten sites, and the live counts did not move (non-test comment: 2,971 resolve, 80 as pull requests; test comment: 2,700 and 122). A third, raw reading (every `#` followed by 2 to 6 digits, whatever surrounds it) counts 9,504 before and 9,225 after: the same drop of 279. **Comment ids.** Six distinct comment-id citations stand on 11 lines in this package (`5237739551`, `5434929046`, `5791803339`, `5805782503`, `5865053231`, `5865693155`). Each was read over the issue-comments endpoint and each answers 200 (control `5941871762`, 200), so none is in the population. ## Per-number table `src` counts census sites (plus `vitest.config.ts` for `objectstack-ai#17853`), `test` counts test-comment sites. Every sha below matches exactly one commit (`git rev-parse --disambiguate`, count 1) and is an ancestor of the base (`git merge-base --is-ancestor`, exit 0 for all 67 shas; the clone is not shallow, 15,432 commits at the base). The message or the diff of each one names the number it replaces, with one exception, `objectstack-ai#10629`, explained under Wordings to check. Where a sentence credits a ruling, a measurement or a note to the number, the anchor's own message or diff carries it (checked per site; the ones that needed a reworded sentence are listed below). `source` says whether another lane or stage already used this anchor for this number (`reused`) or it was measured here (`measured`). | number | src | test | anchor | kind | source | what it decided | |---|---|---|---|---|---|---| | `objectstack-ai#6037` | 2 | 1 | `18189983d` | commit | reused | validate-only data operation — DataProtocol.validateData | | `objectstack-ai#6083` | 3 | 2 | `53068c130` | commit | reused | ADR-0122 phase 2 — flip bare names to parsed semantics | | `objectstack-ai#6241` | 0 | 1 | `83a3b1f2e` | commit | reused | normalize the `:type` segment once per handler so the plural spelling cannot skip the §6.7 audience gate | | `objectstack-ai#6300` | 5 | 4 | `74155c735` | commit | reused | IDataEngine.find/findOne accept the author state — engine fills SortNode.order's declared default | | `objectstack-ai#6311` | 0 | 1 | `59b794f71` | commit | measured | narrow `HookContext.api` from `z.unknown()` to the minimal `IScopedContext` | | `objectstack-ai#6478` | 0 | 1 | `474f131cf` | commit | reused | roll `flow`'s `allowOrgOverride` back to `false` per ADR-0005's original call, the write path refusing loudly | | `objectstack-ai#6483` | 0 | 9 | `ee58392e1` | commit | reused | enforce the ADR-0005 whitelist: nine unratified `allowOrgOverride: true` flags rolled back to `false` (its message records the 2026-08-08 ruling) | | `objectstack-ai#6573` | 5 | 6 | `708431313` | commit | measured | `registerHook` refuses an empty `object` target and a self-cancelling scope | | `objectstack-ai#6723` | 0 | 4 | `8ad609c69` | commit | reused | declare what IMetadataService.getObject answers with | | `objectstack-ai#6725` | 3 | 7 | `1507ba356` | commit | reused | MetadataFacade object writes now reach the map its reads use | | `objectstack-ai#6745` | 0 | 4 | `7a5ef0008` | commit | reused | pin getObject(n) = get('object', n) across all three IMetadataService implementations | | `objectstack-ai#8454` | 1 | 0 | `427344c26` | commit | measured | the object catalog loses to an explicitly-set scalar | | `objectstack-ai#8460` | 7 | 0 | ADR-0029 D9.2a | ADR | reused | ADR-0029 D9.2a, the 2026-08-13 amendment: an extender's scalar yields to a diverged base (it names the number; executed as `01a7337fc`) | | `objectstack-ai#8648` | 1 | 4 | `e5eeb499c` | commit | reused | pin the SEARCH-axis remedy agreement, and correct the three comments that claimed word-identity | | `objectstack-ai#8672` | 6 | 4 | `ff08691e6` | commit | measured | a system-context insert resolves the install's organization, or is refused — the runtime producer of the autonumber fork | | `objectstack-ai#8818` | 0 | 1 | `fd6bdf89f` | commit | reused | saveMetaItem's missing-item refusal declares 400 INVALID_REQUEST instead of answering 500 | | `objectstack-ai#8823` | 10 | 4 | `4dfa369a9` | commit | measured | drop the caller value MySQL inlines in its duplicate-entry diagnostic | | `objectstack-ai#9030` | 2 | 1 | `27a567dd8` | commit | measured | teach the internal-leak predicate MySQL's three error templates | | `objectstack-ai#10062` | 2 | 0 | `fa5d137ab` | commit | reused | gate undeclared workspace imports in published src | | `objectstack-ai#10091` | 1 | 0 | `da891e0ef` | commit | reused | gate sys_attachment beforeUpdate with the uploader-or-parent-editor rule | | `objectstack-ai#10165` | 2 | 1 | `801296050` | commit | reused | lifecycle ttl.onlyWhen row filter with the canonical null predicate | | `objectstack-ai#10194` | 0 | 3 | `2306a765c` | commit | reused | validate theme / analytics_cube at the /meta write door via UNREGISTERED_KIND_SCHEMAS | | `objectstack-ai#10243` | 1 | 1 | ADR-0126 §7.2 | ADR | measured | ADR-0126 §7.2: the durable ledger row replaces the process-local `flowEnabled` map, retiring the env-wide toggle leak's mechanism (it names the number) | | `objectstack-ai#10290` | 4 | 1 | `2570ab05c` | commit | measured | the primary key is never a `__search` companion source | | `objectstack-ai#10347` | 5 | 3 | `530c1df65` | commit | reused | the Archiver honours a declared `ttl` instead of archiving by `created_at` age alone | | `objectstack-ai#10485` | 0 | 2 | `35ad101bc` | commit | reused | retire the `themes` carrier key and ThemeSchema — `app.branding` is the one colour surface | | `objectstack-ai#10527` | 1 | 0 | `5649efbf9` | commit | reused | refuse a diverging retention + ttl + archive lifecycle triple at parse time | | `objectstack-ai#10528` | 4 | 1 | `7d483e1e5` | commit | measured | the Archiver resolves its window through P4 governance | | `objectstack-ai#10629` | 1 | 2 | `199ec4712` | commit | measured | bind federated objects whatever the boot order, and report what could not be bound | | `objectstack-ai#10643` | 1 | 0 | `5649efbf9` | commit | measured | refuse a diverging retention + ttl + archive lifecycle triple at parse time | | `objectstack-ai#10729` | 1 | 1 | `10485009a` | commit | measured | log a contributed kind by its declared `id` | | `objectstack-ai#11065` | 1 | 0 | `20950404c` | commit | reused | count a boolean aggregand as 1/0 in avg and sum | | `objectstack-ai#11311` | 1 | 0 | `1272f0a6b` | commit | reused | promote resolveRecordOrganizationField to the shared platform-row resolver: approvals and automation runs stamp the subject record's organization | | `objectstack-ai#11427` | 5 | 0 | `c3c72a4bc` | commit | reused | hydrate a tombstoned sys_file that still has a live holder | | `objectstack-ai#11674` | 0 | 4 | `9a884c6e4` + `1cba33f16` | commit | reused | seed pass 2 writes back by the internal id captured at insert time, healing keyless datasets / warn at load time when a seed defers a required column, and document the ordering constraint at the four pointer-pair sites | | `objectstack-ai#12194` | 0 | 2 | `311433f6b` | commit | reused | Declare the metadata item-name grammar in spec and refuse it loudly at the publish door | | `objectstack-ai#13178` | 2 | 1 | `f087c376f` + `e49d98896` | commit | reused+measured | scope the sys_file / sys_upload_session update and delete doors to the acting organization / cut the tenant-audit control's scope by the object's tenancy, not the caller's flag | | `objectstack-ai#13197` | 5 | 11 | `56c093c4d` | commit | reused | enforce field-level `unique` so a colliding write is refused, not landed | | `objectstack-ai#13273` | 1 | 4 | `3a86a65e7` | commit | reused | pick the `find` failure log level from the cause — "the table is not provisioned yet" is not "the read failed" | | `objectstack-ai#13644` | 3 | 2 | `34ce8e7db` | commit | reused | declare ctx.referentialFieldClear on HookContextSchema, populate every set_null cleanup write, and carry it across the QuickJS sandbox boundary | | `objectstack-ai#13657` | 4 | 4 | `b003cf2e8` | commit | reused | Refuse an undeclared field a before-hook writes — the post-hook half of the declared-field door, one envelope on every driver | | `objectstack-ai#14163` | 0 | 1 | ADR-0130 D3 + `1dcb995f2` | ADR | measured | ADR-0130 D3: the gate relaxation and the object-name uniqueness check are one change; `1dcb995f2` landed it, and its changeset names the number | | `objectstack-ai#14345` | 0 | 1 | `e89fa9233` | commit | measured | declare aggregate? on IDataDriver with the signature the engine calls | | `objectstack-ai#14390` | 6 | 1 | `9d7f7259f` | commit | reused | `update` answers a driver unique violation with the `DUPLICATE_RECORD` envelope, on every driver | | `objectstack-ai#14399` | 3 | 8 | `3c1bbd2a8` | commit | measured | derive a view container's object through the shared helper, so the row's own `name` is LAST at every SOURCE registrar | | `objectstack-ai#14422` | 0 | 1 | `dc7c226b9` | commit | reused | give the standalone-action owner-key ladder one spelling | | `objectstack-ai#14423` | 11 | 2 | `a56baa2bd` | commit | reused | the action audit reads the store key and asks the plane by name, and listNames gains loadMany fault parity | | `objectstack-ai#14472` | 1 | 1 | `00ff228fe` | commit | measured | decide the insert-side runtime-owned strip by hook-write provenance | | `objectstack-ai#14474` | 1 | 3 | `df657d9df` | commit | reused | carry an ADR-0112 envelope on the install-time namespace conflict refusal | | `objectstack-ai#14484` | 1 | 0 | `3f64fe6c6` | commit | reused | stamp organization_id on every sys_record_share write, backfill the stranded rows, admit the object to the tenancy ledger | | `objectstack-ai#14535` | 0 | 1 | `1aba3159a` | commit | measured | declare the recorded-by fixture's lookup with the canonical `reference` key | | `objectstack-ai#14666` | 1 | 6 | `d0ee598e6` | commit | measured | refuse a view container whose `name` disagrees with its derived object key | | `objectstack-ai#14667` | 0 | 1 | `dc7c226b9` | commit | reused | give the standalone-action owner-key ladder one spelling | | `objectstack-ai#14680` | 1 | 1 | `3bd9b3498` | commit | measured | a leaf `/view-container` subpath keeps objectql's lean ADR-0076 closure free of the manager, chokidar, glob and js-yaml | | `objectstack-ai#14683` | 0 | 4 | `96326040f` | commit | reused | apply the allowOrgOverride read gate inside getMetaItems, so multi-type sweeps are scoped per type | | `objectstack-ai#14723` | 1 | 0 | `65846bc46` | commit | reused | a batch/import ROW reports a unique-constraint refusal as `UNIQUE_VIOLATION`, the route's one wire spelling | | `objectstack-ai#14770` | 0 | 6 | `d5cbb44f3` | commit | reused | gate `getMetaItem`'s overlay read on the metadata registry | | `objectstack-ai#14878` | 0 | 2 | `29db3cd2a` | commit | reused | widen the deleted-member absence pin from one file to the tree | | `objectstack-ai#14957` | 1 | 0 | `26144c204` | commit | measured | Derive and gate the platform-object tenancy census | | `objectstack-ai#15041` | 2 | 1 | ADR-0104, 2026-09-05 addendum | ADR | reused | ADR-0104's 2026-09-05 addendum: the media column holds the bare `sys_file` id; its execution order puts the driver card at step 2 (it names the number) | | `objectstack-ai#15094` | 1 | 0 | `901773b21` | commit | measured | check-react-page-adapter-contract names its class by shape, not by the records spelling, and re-anchors its citation | | `objectstack-ai#16608` | 4 | 0 | `a016f08b8` | commit | reused | evaluate the insert-side RLS `check` on the row that will be stored, after `beforeInsert` | | `objectstack-ai#16711` | 1 | 0 | `7862fb711` | commit | measured | object-definition parameters declare the keys they are read for, plus a gate that sees subclass overrides | | `objectstack-ai#16729` | 1 | 0 | `0f38ab084` | commit | measured | an explicit tenancy opt-out survives a partial `syncSchema` re-registration | | `objectstack-ai#16783` | 1 | 1 | `854639b31` | commit | reused | `findOne`, `update` and `delete` declare what they answer, and their hook seams are guarded | | `objectstack-ai#16786` | 3 | 2 | `5c8f5af50` + `6059b29c0` | commit | measured+reused | `ObjectRepository` declares the `findOne` / `update` shapes it already published / declare IScopedObjectRepository.updateById's answer — the record or null, not any | | `objectstack-ai#16805` | 2 | 0 | `a016f08b8` | commit | reused | evaluate the insert-side RLS `check` on the row that will be stored, after `beforeInsert` | | `objectstack-ai#17195` | 1 | 0 | `d2c1d1980` | commit | reused | beforeUpdate receives the persist image; the caller submission moves to ctx.submitted | | `objectstack-ai#17219` | 3 | 2 | `706ad0fcc` | commit | reused | name the withheld read-only key when a hook faults reaching through it | | `objectstack-ai#17853` | 1 | 0 | `08f5f0e5a` | commit | reused | make a vitest filter that selects no test file say so | ## Wordings to check Most rewrites swap a tag in place (`[#N]` to `[commit SHA]`, `(#N)` to `(commit SHA)`, `#N's X` to `commit SHA's X`, `PR #N` to its squash commit), the form the landed stages use. These say more than the tag: - **`objectstack-ai#10629`, three sites** (`plugin.ts:1543`, `skip-schema-sync-registers-object-metadata.test.ts:22`, `:153`): 「the same ruling objectstack-ai#7737/objectstack-ai#10629 made for federated objects」 became 「the same ruling objectstack-ai#7737 made for federated objects (commit 199ec47)」. `199ec4712` is objectstack-ai#7737's fix, and its message states the ruling the sites paraphrase: `OS_SKIP_SCHEMA_SYNC` is a DDL flag while the federated binding is DDL-free, and the binding is reconciled on `kernel:ready`. Its message and diff name objectstack-ai#7737, **not objectstack-ai#10629**: the only commit that names objectstack-ai#10629 as its own (`13a6cb4ad`, the runtime lane's anchor for it) is an expected-log-noise capture, a different subject, and `a037f7cbd`, which wrote the 「objectstack-ai#7737/objectstack-ai#10629 ruling」 phrase, records nothing objectstack-ai#10629 added. So the dead number is dropped and the live objectstack-ai#7737 carries the citation, beside the commit that decided it. The same pair stands in `driver-sql` (`sql-driver.ts`), the next stage. - **`objectstack-ai#10243`, two sites** (`action-activation.ts:185`, `action-activation.test.ts:23`): 「the objectstack-ai#10243 mechanism ADR-0126 retires」 became 「the env-wide toggle leak's mechanism ADR-0126 §7.2 retires」. ADR-0126 §7.2 names that mechanism (the process-local `flowEnabled` map) and the number (「the objectstack-ai#10243 leak's mechanism」), so ruling C's ADR rung applies; the dogfood lane anchored a similar sentence to `02b41232d`, the measurement commit. - **`objectstack-ai#8672`, ten sites**: objectstack-ai#8672 was an observation that no commit fixed. `ff08691e6` is the first in-repo record of its reasoning: its diff quotes 「an org-less row is defensible for `sys_permission_set`」 and names objectstack-ai#8672 five times. So 「objectstack-ai#8672's reasoning」 became 「commit ff08691's (recorded) reasoning」, and 「objectstack-ai#8672 measured this primitive」 (`system-write-organization.test.ts:347`) became 「The card commit ff08691 cites measured this primitive」. `system-write-organization.test.ts:117` quotes what the file used to read, 「platform namespace ⇒ deliberately org-less (objectstack-ai#8672)」; the quoted number is elided to 「(…)」 rather than re-spelled, so the quote stays true. - **`objectstack-ai#13178`'s census figure** (`engine.ts:5450`, `tenancy-by-object-classification.test.ts:26`): 「objectstack-ai#13178 census measured … 135 of 175」 became 「census cited in commit e49d988's message measured … 135 of 175」. That message carries the figure; see Acceptance notes for what `4ecafc78b` records about it. `platform-object-tenancy.ts:144` (the `sys_upload_session` update writer) takes `f087c376f`, the service-storage lane's anchor. - **`objectstack-ai#15094`** (`find-hook-result-shape.ts:30`): 「the ~70 … normalizer limbs the objectstack-ai#15094 census counted」 became 「… limbs a census counted (commit 901773b records its band)」. `901773b21` wrote the header of `scripts/check-react-page-adapter-contract.mjs`, which records that census (104 blocks at `ca46f8f12`) and a re-measure band; it does not restate 「~70」, so the sentence points at the band and claims nothing more. - **`objectstack-ai#16805`** (`engine.ts:13014`, `:13218`): 「the contract review of PR objectstack-ai#16805 measured」 became 「the contract review commit a016f08 records measured」. `a016f08b8` is that pull request's squash, and its message records the review's finding. - **`objectstack-ai#16786`**: the `objectql` half (`engine.ts:18230`, `:18264`, two tests) takes `5c8f5af50`, the commit that declared `ObjectRepository`'s `findOne` / `update` shapes; `engine.ts:18272`'s 「stays open on objectstack-ai#16786」 became 「(its spec half: commit 6059b29)」, the spec lane's anchor, since that half has landed. - **`objectstack-ai#14163`** (`registry-ownership-refusal-envelope.test.ts:11`): `(ADR-0130 D3, commit 1dcb995)`. The cited thing is the install-time object-name check; ADR-0130 D3 decides it, and `1dcb995f2` landed it with a changeset naming objectstack-ai#14163. - **`objectstack-ai#8460`** (seven `registry.ts` sites): ADR-0029 D9.2a, the 2026-08-13 amendment, which names objectstack-ai#8460; the dogfood and spec lanes used the same. `:2508`'s 「every shape objectstack-ai#8460 measured」 became 「every shape the ADR-0029 D9.2a amendment records」. - **`objectstack-ai#15041`** (`engine.ts:7164`, `:9860`, one test): 「the ruling on objectstack-ai#15041 step 2」 became 「sequencing step 2 of ADR-0104's 2026-09-05 addendum」, the cli lane's spelling of the same record. - **`objectstack-ai#6241`** (`metadata-service-roundtrip-conformance.test.ts:41`): 「the gate's header carries objectstack-ai#3984/objectstack-ai#5881/objectstack-ai#6241」 became 「… carries objectstack-ai#3984, objectstack-ai#5881 and the third bypass, fixed in commit 83a3b1f」, because that header (`scripts/`, another lane's surface) still carries the number itself. - **Tense, where the anchor is past**: 「the phantom read objectstack-ai#14770 removes」 became 「… commit d5cbb44 removed」 (`protocol-meta.test.ts:100`) and 「the resurrection objectstack-ai#14683 closes」 became 「… commit 9632604 closed」 (`:169`), as stage 1 wrote it. `:181`'s backticked `` `objectstack-ai#14770` `` became plain 「commit d5cbb44」; its message records the four raw-org callers the line says it measured. - **Card antecedents**: 「the whole subject of objectstack-ai#14423」 became 「the whole subject of the card commit a56baa2 closed」 (`action-governance.ts:406`); 「the C4 cell objectstack-ai#14423's ruling left open」 became 「the C4 cell commit a56baa2 left open」 (`plugin-governance-scoped-metadata.test.ts:5`; that commit pins C4 as 「a BOUNDARY, not a defect」); 「route 3 of objectstack-ai#8648」 became 「route 3 of the card commit e5eeb49 fixed」 (`query-expression-conformance.test.ts:1070`, a line that commit wrote); 「filed objectstack-ai#6573」 / 「objectstack-ai#6573's ruling」 became 「filed the card commit 7084313 closed」 / 「The decision commit 7084313 records」 (`hook-exclude-objects.test.ts:501`, `:502`); 「(objectstack-ai#11674, the card's "Second, NOT measured" question)」 became 「(the "Second, NOT measured" question on the card commit 9a884c6 fixed)」; 「measurement (objectstack-ai#13644)」 became 「measurement (on the card commit 34ce8e7 closed)」. - **Other single rewrites**: 「and objectstack-ai#6573 is why」 became 「and commit 7084313 says why」 (`engine.ts:2433`; its message gives the reason); 「question at this line (objectstack-ai#10527), since decided」 became 「question at this line, since decided by that commit」 (`lifecycle-service.ts:1294`, where `:1293` now cites `5649efbf9`); 「objectstack-ai#14680 is what they cost」 became 「the leak commit 3bd9b34 closed is what they cost」 (`core-boundary.ratchet.test.ts:52`); 「The objectstack-ai#10165 acceptance criterion」 became 「The acceptance criterion behind commit 8012960」. - **Reference lists** (`metadata-service-getobject-equivalence.test.ts:51`, `metadata-service-roundtrip-conformance.test.ts:68`, `metadata-facade.test.ts:101`): the dead numbers became their commits, in the spec lane's 「commits SHA (what), …」 form; the live numbers in those lists stay, `PR objectstack-ai#7211` beside its `1507ba356`. - No line was reflowed, so some are longer than their block's wrap (`eslint.config.mjs` declares no line-length rule, and a reflow would move neighbouring lines and every line citation into the file). ## Sites left - **In comments (src, test, `vitest.config.ts`): none.** - **String literals: 43 test-string sites, 25 numbers, 23 files** (describe and `it` titles, assertion arguments): `objectstack-ai#14422` 4, `objectstack-ai#13273` 3, `objectstack-ai#13657` 3, `objectstack-ai#17219` 3, `objectstack-ai#11674` 3, `objectstack-ai#6573` 3, `objectstack-ai#14423` 2, `objectstack-ai#10165` 2, `objectstack-ai#14535` 2, `objectstack-ai#10290` 2, `objectstack-ai#8672` 2, and 14 more once each. Every one of the 25 is in this stage's population, so the table above holds an anchor for each. Non-test strings carry none. Strings are outside this stage's surface. - **Outside `src`:** the release-owned `CHANGELOG.md` names dead numbers on 66 sites (44 numbers); left. `test-typecheck-debt.json`, `tsconfig.test.json` and `tsconfig.scripts.json` cite only live numbers. ## Mechanical guard: no code token moves The guard (stage 2's) compares, base `4727fcb22` against the working tree, over all 67 touched files, with TypeScript 6.0.3: - **Reading 1**: the parser's leaf nodes, from a `forEachChild` walk. Comments are trivia there, and JSDoc is never visited. - **Reading 2**: the full token stream in parser context, from a `getChildren` walk, JSDoc nodes skipped. String, template and numeric literals are compared in full on both readings. Results: - Real run at the head: 298,707 base tokens, **0 files with a token change** (exit 0). - Comment control (「The defaulting」 to 「The DEFAULTING」 on `engine.ts:5`): 0 files changed (exit 0). - Positive control, an identifier (`ARCHIVE_BATCH_SIZE` to `ARCHIVE_BATCH_SIZEX`, `lifecycle-service.ts`): DIFFER on both readings (exit 1). - Positive control, a string literal (`'[value redacted]'` to `'[value redactedX]'`, `driver-fault-redaction.ts`): DIFFER on both readings (exit 1). - Positive control, a numeric literal (`ARCHIVE_MAX_BATCHES_PER_SWEEP = 20` to `21`): DIFFER on both readings (exit 1). Each mutation went through `scripts/ablation-replace.mjs` (wrap mode) under a shell trap that restores by absolute path from `HEAD`. Each landed (anchor 1 to 0, blob changed), and each restore was proven equal to its `HEAD` blob (`71f6c9268aeb`, `34b1dd7989b8`, `9594cdd593c8`), with `git diff HEAD` empty and a clean tree afterwards. ## Changeset: `patch` (`dist` measured) `files[]` is `dist`, `README.md` and `CHANGELOG.md`, and the package is not private. The dependency closure was built first (`turbo run build --filter='@objectstack/objectql^...'`, 13 tasks). Then the package's own `build` (tsup plus `check-dts-emitted`) ran three times in one script under the shared verify lock (VERDICT command-exit 0): - **Leg 1**, at `9d6a0a8d6`: 14 `dist` files hashed. Of the 137 rewritten non-test lines, 54 appear verbatim in `dist`: 25 from `engine.ts`, 7 from `action-governance.ts`, 5 from `platform-object-tenancy.ts`, and 17 from ten other files; in `index.d.ts` / `index.d.mts`, the shared `util-*.d.ts` chunk, and `index.js` / `index.mjs` / `core.js` / `core.mjs`. - **Leg 2**, the base text put back in the 18 non-test files (18 of 18 proven equal to their base blob): 10 of the 14 files differ from leg 1 (`core` and `index` in `.d.ts`, `.d.mts`, `.js`, `.mjs`, and the `util-*` chunk's two `.d` files). - **Leg 3**, after the proven restore (18 of 18 equal to their `HEAD` blob, `git diff HEAD` empty): all 14 files are byte-identical to leg 1, so the build is deterministic and the difference is the rewrite. So the rewrite ships, and `.changeset/20595-objectql-provenance-anchors.md` declares a `patch` for `@objectstack/objectql`, comment text only, with the claim's `Clause-②: no` line. ## Gates (head `1e0895870d`) - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `1e0895870d` (68 paths against merge base `8dea55d31`; no stale-tree warning) derived 68 commands. All 68 ran, each exit code captured before any pipe: 68 exit 0. `--ran` reports 「68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN」 (a derived zero) and exits 0. A full `turbo run build` over `./packages/*` and `./packages/*/*` ran first under the verify lock (71 of 71 tasks, VERDICT command-exit 0), so no gate read an unbuilt workspace. - **Named in the dispatch:** `node scripts/check-issue-citations.mjs` exits 0 (「every citation this change adds resolves (or is a declared cross-repo reference)」, 14 judged across 17 files); `pnpm check:issue-citations` exits 0 (self-test); `pnpm check:doc-authoring` exits 0 (the sibling-package prose-id baseline holds, no growth); `pnpm check:nul-bytes` exits 0, and a raw scan of the 68 changed files for control bytes finds none. - **Tests and typecheck, under the verify lock, at `1e0895870d`:** `pnpm --filter @objectstack/objectql test`: 360 test files and 7,082 tests pass. `pnpm --filter @objectstack/objectql typecheck` exits 0; its `check:test-typecheck` step compiles all 361 tracked test files under `tsconfig.test.json` (`tsc --listFiles`), the ledger holding (40 files, 234 errors, 65 pinned signatures). - **Lint, as a proven narrowing:** eslint with inline config disabled, over the 67 touched `.ts` files plus `dist/index.js` as the control: 68 results, 0 errors and 1 warning, the control's ignore notice; none of the 67 is reported ignored. `eslint.config.mjs` never enables type-aware linting (its lines 327-328 say so), so a comment edit cannot move the verdict on an untouched file. The repo-wide `pnpm lint` is CI's run. ## Acceptance notes - **Base.** The branch was cut at `d150c3039`, fast-forwarded to `4727fcb22` before any edit (both census readings of this package agree), and merged with `main` once at `8dea55d31` before the gates; that merge touched no file under `packages/objectql` and neither `check-issue-citations.mjs` nor `dispatch-gates.mjs`. Tests, typecheck and gates ran on the merged head. - **The `135 of 175` figure.** `engine.ts:5450` and `tenancy-by-object-classification.test.ts:26` state that census figure as measured. `4ecafc78b`, which re-derived the tenant-audit census as an in-tree artifact after objectstack-ai#13178 became unreachable, records that 「the 135/77% "silenced by the isSystem guard" figure has no surviving corroboration and is not reproduced」. This stage moves the citation to the commit whose message carries the figure and leaves the claim as written; whether those two sentences should say so is outside a citation sweep. - **A board enumeration came back short, at exit 0.** One run of the gate's own `enumerateBoard`, between two full ones, returned 91 pages, 9,000 records and frontier objectstack-ai#9389, and raised nothing; the runs either side of it read 191 pages and frontier objectstack-ai#21252. Its only guard is a zero-record check. This stage discarded that reading and re-enumerated; nothing above rests on it. Noted only. - **Wording only:** no line without a number was changed, except the lines whose antecedent was the number itself, listed above. --- _Generated by [Claude Code](https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #21241
Clause-②: no
What was wrong
On MySQL a declared
Field.datetimeis built asDATETIME(3). For itsdefaultValue: 'NOW()'default,SqlDriver.nowColumnDefault('datetime')fell through to a bareknex.fn.now(), which isCURRENT_TIMESTAMPat precision 0. MySQL 8.0 refuses aCURRENT_TIMESTAMPdefault whose precision differs from its column's, so the wholeCREATE TABLEfailed and so didALTER TABLE … ADD. The builtincreated_at/updated_atcolumns beside it carried their own literalnow(3)and were accepted.Measured at the base
be5a83cfagainst a throwaway MySQL 8.0.46 (server zone+08:00):pnpm dev:crm -- --fresh --database mysql://…synced 80, skipped 0, failed 1sys_activity:`timestamp` datetime(3) default CURRENT_TIMESTAMP→Invalid default value for 'timestamp'GET /api/v1/data/sys_activity?limit=1→500 DATABASE_ERRORpnpm dev:showcase -- --fresh --database mysql://…synced 102, skipped 2, failed 2sys_activityas above, andsys_presence:`last_seen` datetime(3) default CURRENT_TIMESTAMP→Invalid default value for 'last_seen'GET /api/v1/data/sys_presence?limit=1→500,sys_activity→500The fix
MYSQL_DATETIME_PRECISIONis a module constant insql-driver.ts. Five sites read it: the declared datetime column (createColumn), the builtin audit columns (createAuditTimestampColumn), theNOW()default (nowColumnDefault), the UPDATE stamp (updatedAtStamp) and the legacyTIMESTAMPwidening (migrateMysqlDatetimeColumns). Before this PR, five literal3s lived at those sites. Now there is one, and no new literal was added.nowColumnDefault('datetime')on MySQL returnsknex.fn.now(MYSQL_DATETIME_PRECISION), which rendersCURRENT_TIMESTAMP(3).nowColumnDefault('datetime'), the way the SQLite branch already is. The declared column and the builtin column now share one definition. The emitted DDL for the audit columns is byte-identical:datetime(3) default CURRENT_TIMESTAMP(3).timestamptz, andCURRENT_TIMESTAMPon it was measured accepted on PostgreSQL 16.14.timestamp(3) default CURRENT_TIMESTAMPis also accepted there, rounding silently. So the mismatch does not exist on that dialect.Bounded in-place fix in the same statement: the
TIMESTAMPwidening dropped a declaredNOW()defaultThe widening's
ALTER … MODIFYline now reads the constant, so this PR touches it. That same statement restated the default ofcreated_at/updated_atand dropped the default of a declaredNOW()column. TheTIMEtwin (migrateMysqlTimeColumns) already restates it.Measured on MySQL 8.0.46 with the driver built at this branch before the change: a legacy
stamped_at timestamp null default current_timestampcolumn came out of schema sync asdatetime(3)withCOLUMN_DEFAULTnull, andcreatewithout the field answeredstamped_at = null. The widening now restatesnowColumnDefault('datetime')for a declaredNOW()column as well. All four conditions for an in-place fix hold:NOW()datetime default that is not honoured);TIMEtwin;The changed lines are
sql-driver.tsmigrateMysqlDatetimeColumns, and a MySQL-only pin, §3.Pins:
packages/drivers/driver-sql/src/sql-driver-21241-mysql-now-default-precision.test.tsmysql2,pgandbetter-sqlite3, the compiled DDL of a declaredNOW()datetime column is byte-identical to the builtin audit column's. A second case reads themysql2DDL of the requiredsys_activity.timestampshape and checks that itsCURRENT_TIMESTAMP(n)names the column's owndatetime(n). This compares against the column, not against a literal.declareDialectCell:createwithout the field answers the instant the columnDEFAULTstored. The check uses a window and confirms that the answer equalsfindOne;DEFAULTalone;information_schema/pragma table_info) reports the declared column's type and default equal tocreated_at's.TIMESTAMPNOW()column keeps a default through the widening, equal tocreated_at's.Verification (HEAD
d334fe314b)Every reading below was re-run at
d334fe314b: the branch plus one merge oforigin/main4727fcb22a, which touches nodriver-sqlfile and no lockfile. The readings match the earlier heade5cab5f20b, except thedriver-tursocount, which moved because #21226 landed onmain. (Seat edit.)The live servers were local: MySQL 8.0.46 at
+08:00and PostgreSQL 16.14 atAsia/Shanghai, withTZ=America/New_York.vitest run --reporter=verbose src/sql-driver-21241-mysql-now-default-precision.test.tswith both URLs set → 17 passed (17).sql-driver.tswas written back to the base blobe65a0f08while the HEAD blob533a790bstayed committed. On-disk hash equal to the base blob,grep -c MYSQL_DATETIME_PRECISION→ 0. Result: 7 failed | 10 passed (17). Every failure is MySQL: §1mysql2×2, §2 live mysql ×4 (Invalid default value for 'stamped_at'at create and at add-column), and §3. The SQLite and PostgreSQL cells stayed green (the control). The restore was checked: hash equal to the HEAD blob andgit diff HEADempty.scripts/ablation-replace.mjs: anchor 1 → 0, blob533a790b→16057202. Only §3 went red,dflt: nullagainstCURRENT_TIMESTAMP(3); the other 16 stayed green. Restored: blob equal to HEAD andgit diff HEADempty.@objectstack/driver-sql, the whole suite against both live servers withOS_EXPECT_LIVE_DIALECT_MATRIX=1: 224 files passed, 5386 passed | 1 skipped. The live-dialect reporter printed "all 3 dialects were exercised".@objectstack/driver-sql, the SQLite tier (pnpm --filter @objectstack/driver-sql test, no URLs, at04b23d2791before the widening commit): 213 files passed | 11 skipped, 3570 passed | 200 skipped.@objectstack/driver-sql,@objectstack/driver-sqlite-wasmand@objectstack/driver-turso(the inheritors): all threeDone.tsc --listFilesincludes the new test file once.driver-sqlite-wasm36 files / 675 passed;driver-turso87 files / 2349 passed | 33 skipped.pnpm check:driver-conformancegave the same reading before the first edit and after the last commit:OK — 50 covered cell(s), 0 in the DEBT ledger, 0 exempt, with 0 in the DIALECT ledger.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderived 63 families. All 63 ran with exit 0.--ranreconciliation:63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN, with every line recording its exit code. The derivation warned that the tree is onemaincommit behind (3dc33b2d13, file-disjoint from this diff, and it touchescheck-route-envelope.mjs/engine-double-contract.pinned.json). CI re-derives on the merge ref.eslint --no-inline-config --format jsonover the two changed.tsfiles: 2 files, 0 errors, 0 warnings.parserOptionscarry noproject/projectService, so linting is not type-aware and this diff cannot move a verdict on an untouched file.pnpm lintis left to CI.After the fix, same boot
pnpm dev:showcase -- --fresh --database mysql://…ate5cab5f20b:Schema sync FAILED;GET /api/v1/data/sys_presence→ 200 andsys_activity→ 200;information_schemareportssys_activity.timestampandsys_presence.last_seenasdatetime(3)/CURRENT_TIMESTAMP(3), equal to theircreated_at;POST/PATCH/DELETEonshowcase_accountleft threesys_activityrows (created,updated,deleted).Raise-rule reading (triage)
At the base on MySQL, with
sys_activityabsent, a missing table does not break a record write.POST /api/v1/data/crm_account→ 201,PATCH→ 200 (a read-back showed the new name),DELETE→ 200 (a read-back answered 404).sys_audit_logholds 3 rows for the record. Each mutation lost its activity row: the server loggedInsert operation failed {object: sys_activity …}atwarnandAudit write FAILED (ER_NO_SUCH_TABLE …)aterror. The API answers carried nothing about it. Applying the rule is the seat's decision.Acceptance notes
TIMESTAMPwidening already left without a default does not get one back from this change. The widening only touches columns that are stilltimestamp. This is a read-only inference beyond the measurement above.client: 'mariadb'is not in the driver's MySQL family, butmysql2pointed at a MariaDB server is. Whether MariaDB accepted the bare default, and so holds tables with a precision-0 default, is NOT MEASURED.Audit write FAILEDline for thesys_activityinsert namessys_audit_logas the row that "never landed" and as the table to check. It printed 4 times despite "reported ONCE". Observation only, nothing filed.sys_packages. Its raw DDL (created_at TEXT DEFAULT CURRENT_TIMESTAMP) is still refused on MySQL in both boots. This was already recorded in PR fix(driver-sql): create and bulkCreate answer the stored row on MySQL (#21227) #21239's acceptance notes; it isdomain:services, and no door was measured.GET /api/v1/auth/jwksandGET /api/v1/auth/tokenanswered 500. An insert intosys_jwksis refused withIncorrect datetime value … for column 'updated_at', and the server logsJWT signing failed with alg "EdDSA".Generated by Claude Code