Repository navigation
fix(spec): the etl-pipeline and unresolvable-where-column migration entries state what the tree does - #20838
Conversation
…schedule deletion to spec 17, not 18 The protocol-17 D3 entry's replacement text said `syncConfig.schedule` was retired "in 18". The key was deleted in @objectstack/spec 17 under ADR-0049 (first released in 17.5.0), as integration/connector.zod.ts already states. The sentence now reads "the same measurement that deleted `syncConfig.schedule` in @objectstack/spec 17 under ADR-0049". Only the entry file was edited by hand; registry.ts, spec-changes.json and docs/protocol-upgrade-guide.md were regenerated with gen:migration-registry, gen:spec-changes and gen:upgrade-guide. Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude <noreply@anthropic.com>
…es the remote aggregate refusals The protocol-18 D3 entry named only a `where` column on find / findOne / count and INVALID_FILTER / 400. On the remote face of TursoDriver the aggregate() door answered [] for a missing column or a missing table; it now refuses as the local face does (SqlDriver.aggregateBackendFault): INVALID_FILTER / 400 for a where column, INVALID_FIELD / 400 for a groupBy or aggregation column, DATABASE_ERROR / 500 for an absent table, as pinned by turso-local-remote-missing-table-column-parity.test.ts. The entry's surface names that door and those codes, its remedy adds grouping / aggregating and running schema sync so the object's table exists, and its acceptance criterion covers a report or dashboard that groups by or aggregates over a missing column. The reason paragraph is unchanged. registry.ts was regenerated with gen:migration-registry; spec-changes.json and the upgrade guide project protocol 17 only, so they do not carry this step-18 entry and are unchanged. The shared changeset now describes both entries. Claude-Session: https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 137 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 3a03685c69af49d4f4f5b3770c88445d01e003c3 && git checkout 3a03685c69af49d4f4f5b3770c88445d01e003c3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 15b586dcffa88471afeaba4dbf560223692ad944 f45b0968868f28087f4a1ffd4e509f610514688e && git checkout -B drift-repro 15b586dcffa88471afeaba4dbf560223692ad944 && git merge --no-ff f45b0968868f28087f4a1ffd4e509f610514688e
node scripts/docs-audit/affected-docs.mjs --json 15b586dcffa88471afeaba4dbf560223692ad944 |
Contract reviewServed-tier: Inputs read: card #20773 (body + 3 comments), card #20467 (body + 3 comments), PR #20838 (body, 6-file list, net diff ① Derived judgmentsSurface of the diff. Six files: one new changeset; two hand-edited D3 semantic entries ( Accept set: nothing moves — judged RIGHT. Every changed line is inside a string literal of an entry's Card 20773 — the corrected sentence is TRUE against the tree — RIGHT.
Card 20467 — the three codes and the message fragment are what
Generated copies — RIGHT. The Check-runs on ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
…ackages/observability/src and packages/verify/src to the commits that decided them (objectstack-ai#20842) Part of objectstack-ai#20594 Clause-②: no ## What changed This is stage 13 of the `domain:cli` lane of the dead-citation sweep: `packages/observability/src` and `packages/verify/src` in **one PR**. The maintainer ruled that in the handover `5903477632` on the card (「合成一个 PR (Recommended)」). The ruling changes the one-package-per-PR direction for these two packages only, so each package's before and after census counts are listed separately below. Every comment site in these two trees that cited a tracker number answering 404 now cites the commit in this repository's history that made the decision the line describes. The form is ruling C+D's form C (comment `5749154545` on objectstack-ai#19123), as stages 1 to 12 of this card applied it. The last stage was `plugin-dev`, landed as `f7c6d65f5`. The card stays open for its later stages, so this PR says `Part of`. In total, **4 sites on 4 lines in 3 files, covering 3 numbers**, now cite **3 distinct commits**: - `observability`: **1 site**, `src/semconv.ts:49` (a census site); - `verify`: **3 sites**: - `src/harness.ts:580` (a census site); - `src/erasure-transaction-authorization.test.ts:163` and `:167`. These are test-file comments, which the census defers. Stages 1 to 12 took test comments too. Only comments changed: **4 lines out, 4 in**. Every one of them is a site, with no reflow and no companion line. Every touched file keeps its line count (178 / 955 / 185 at base and head), so no line citation into these files moves. **No citation number is added.** The only tracker numbers on added lines are `objectstack-ai#9650` and `objectstack-ai#9835` at `semconv.ts:49`. The removed line already carried both, and both answer 200. No PR number stands on an added line. No ADR or ruling-record file records any of the three decisions. A grep of `docs/adr/` and `scripts/adr-anchors/` for the 3 numbers, the 3 shas, `afterResponse` and `http_request_duration_ms` reads 0 hits; the control number `7329` reads 1 file in the same tree. So all three anchors are commits. **One `patch` changeset**, for `@objectstack/observability` only (`.changeset/20594-observability-provenance-anchors.md`). Its rewritten `//` line reaches the published `dist`. The `verify` rewrites leave `verify`'s `dist` byte-identical, so `verify` takes no changeset. Both results are measured below. ## Census, before and after, one package at a time **Instrument:** 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 each package's path. Both runs enumerated the whole board (187 pages). | package | before (base `22e584c9db`) | after (head `62e556317c`) | |---|---|---| | `packages/observability` | **1** (`src/semconv.ts:49`, `objectstack-ai#10004`) | **0** | | `packages/verify` | **1** (`src/harness.ts:580`, `objectstack-ai#10943`) | **0** | | whole repository | 752 | 750 | | run | tree | when (UTC) | board | |---|---|---|---| | before | base `22e584c9db` | 2026-09-30 10:33:12 to 10:37:06 | frontier objectstack-ai#20838, 18,665 numbers | | after | head `62e556317c` | 2026-09-30 10:49:43 to 10:53:16 | frontier objectstack-ai#20839, 18,666 numbers | The whole-repo drop of 2 is exactly these two sites. A site-by-site diff of the two JSON outputs has 2 findings gone (`semconv.ts:49`, `harness.ts:580`) and 0 added. The other three tallies are equal in both runs: 33,155 citations that answer 200, 1,985 that answer as pull requests, and 1,018 cross-repo. **Supplementary scan (census-invisible spellings, test files and files outside `src/`).** The scan covers every `#N` token (two to six digits) in the two packages' 53 tracked files, `CHANGELOG.md` excluded, and probes each by REST (2026-09-30 10:34Z, re-probed 10:58Z). - It finds **90 distinct numbers**: 12 in `observability` and 79 in `verify`, with 1 shared. 86 answer 200 and 4 answer 404: `objectstack-ai#10004`, `objectstack-ai#10943`, `objectstack-ai#11477` and `objectstack-ai#15145`. - Dead occurrences in `src/`: 1 in `observability` and 3 in `verify` at base, 0 and 0 at head. None of them was in a string literal. At head all 232 `#N` tokens under the two `src/` trees answer 200. - Spellings the census cannot see: - `#N-word`: 0. - `#A/#B`: 4 sites. Only `semconv.ts:49` held a dead member. - `option #N` or `clause #N`: 0. - `word-#N`: 1, the `pre-objectstack-ai#11477` at `:167`, rewritten. - URL forms (`issues/N`, `pull/N`): 0. The only `github.com` strings are the two `package.json` `repository`/`bugs` URLs. - A re-grep of the four numbers with no word-boundary operator finds only `verify/tsconfig.test.json:1` (see Acceptance notes). A control of the same shape, `objectstack-ai#9835`, reads 2 lines of `semconv.ts`. ## Per-number table `git blame` at the base (on a full, unshallowed history) ties each line to the commit that wrote it. That commit's message and diff were read to decide the anchor. | number | sites (file:line) | anchor | what that commit decided | |---|---|---|---| | `objectstack-ai#10004` | `observability/src/semconv.ts:49` | `1e050a5b1` | `http_request_duration_ms` is emitted from the `IHttpServer.afterResponse` transport seam, so p95 latency sees every inbound surface. It is the squash of the pull request numbered `objectstack-ai#10004`, and the line cites it beside the two live numbers for the same seam move. The line was written by `914c41302` (the `http_request_errors_total` retirement), whose changeset, now the released entry at `observability/CHANGELOG.md:985`, cites `objectstack-ai#10004` for this same seam move. | | `objectstack-ai#10943` | `verify/src/harness.ts:580` | `46d34ab7c` | The host importer's undeclared fallback resolves from the caller's base, not from `@objectstack/types`, and `bootStack` hands in `(s) => import(s)`. The line blames to this commit. Stages 3 and 4 used the same anchor for the same number in `cli` and `types`. | | `objectstack-ai#11477` | `verify/src/erasure-transaction-authorization.test.ts:163`, `:167` | `6dd3e6968` | `/admin/remove-user` gets the raw-mount shading whose `gateAdmin` runs before the break-glass guard (ruled option A, as its message records), so a plain member hears `PERMISSION_DENIED`. Both lines blame to this commit. Its squashed message includes the `test(verify)` step that rewrote this pin. The `plugin-auth` stage used the same anchor for the same number. | All three shas were checked the same way: - Each has exactly 1 match under `rev-parse --disambiguate`. - Each is a commit with one parent. - `merge-base --is-ancestor` of each against base `22e584c9db` exits 0, on a history that is not shallow (`--is-shallow-repository` false). - The control legs: `1e050a5b1^` against the base exits 0, and base-as-ancestor-of-`1e050a5b1` exits 1. ### Wording per site - `semconv.ts:49`: `(objectstack-ai#9650 / objectstack-ai#9835 / objectstack-ai#10004)` becomes `(objectstack-ai#9650 / objectstack-ai#9835 / commit 1e050a5)`. Precedents for a mixed list: `cli/src/commands/lint.ts:915`, `mcp/src/plugin.ts:8`. - `harness.ts:580`: `objectstack-ai#10943:` becomes `Commit 46d34ab:`. The neighbouring `objectstack-ai#4700:`, `objectstack-ai#4719:` and `objectstack-ai#17911:` labels answer 200 and stay. - `erasure-transaction-authorization.test.ts:163`: `objectstack-ai#11477 (maintainer-ruled option A):` becomes `Commit 6dd3e69 (maintainer-ruled option A):`. - `erasure-transaction-authorization.test.ts:167`: `the pre-objectstack-ai#11477 route` becomes `the route before that commit`, which refers back to `:163` four lines up. ## Does the rewrite reach `dist`? Measured per package Both packages were built at base `22e584c9db` (closure plus package, `pnpm --workspace-concurrency=2 --filter '@objectstack/verify...' --filter '@objectstack/observability...' build`, VERDICT 0) and again at head `5a144efc39` (VERDICT 0). All 6 `dist` files of each package were compared byte for byte. - **`observability`: reaches `dist`.** - `index.js` and `index.cjs` differ in exactly 1 line each: the `semconv.ts:49` comment, which esbuild keeps inside the `SEMCONV` object literal. - `index.d.ts`, `index.d.cts` and both maps are equal. - The base `dist` carried `objectstack-ai#10004` in `index.js` and `index.cjs`. - ⇒ a `patch` changeset. - **`verify`: does not reach `dist`.** All 6 files are byte-equal at base and head, and the base `dist` carried none of the three numbers. ⇒ no changeset. - **Code-mutation control for `verify`,** which proves that "equal" was a measurement and not a stale build. - The mutation went through `scripts/ablation-replace.mjs` in wrap mode. The anchor `const organizationsPkg = opts.organizationsPackage` went 1 to 0, and the marker `ABLMARK20594S13` went 0 to 1 in the source. The blob moved `47a921ad0b` to `3f54cdebfe`. - After a rebuild, `ablation-dist-preflight` found the marker in `dist/index.js` and `dist/index.cjs`. `index.js`, `index.cjs` and both maps differ from the head build, and both `.d.ts` files are equal. - The restore leg: the restored blob equals HEAD `47a921ad0b` and `git diff HEAD` is empty. After a rebuild, `preflight --absent` reads the marker absent from all 6 files with a clean tree, and all 6 files are byte-equal to the head build. - The first control attempt used an anchor that was a prefix of its own replacement. The tool refused it (anchor count 1 to 1) and restored the file, so no build ran on it. - The whole workspace build, `pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2`, finished 71/71 (all cache hits). The `dist` of both packages is byte-equal to the head build. ## Token guard The guard compared TypeScript 6.0.3 parser leaf tokens (`getChildren`, JSDoc nodes excluded) of the 3 source files at base `22e584c9db` and at `5a144efc39`, over 3,523 base tokens: - **the real diff: 0 files differ**; - comment-insertion control: 0 differ; - code-insertion control: 3 of 3 differ; - string control (first character of the first string literal): 3 of 3 differ, first differing kind `StringLiteral`. The script exited 0, and its controls ran in memory only. A first attempt with a bare scanner was discarded: it misaligns inside template literals and reported a false difference. ## Tests, typecheck, lint and gates (head `62e556317c`) - **`observability` tests:** `pnpm --filter @objectstack/observability exec vitest run --maxWorkers=2` → Test Files 7 passed (7), Tests 85 passed (85). - **`verify` tests:** `pnpm --filter @objectstack/verify exec vitest run --maxWorkers=2` → Test Files 16 passed (16), Tests 120 passed (120). The package has 16 test files, `erasure-transaction-authorization.test.ts` included. - **`verify` typecheck:** `pnpm --filter @objectstack/verify typecheck` passed, with VERDICT command-exit 0 (`tsc --noEmit`, then `check:test-typecheck` over `tsconfig.test.json`: 0 files / 0 errors). `tsc --listFiles` confirms that `tsconfig.json` reaches `harness.ts` and `tsconfig.test.json` reaches both edited `verify` files. - **`observability` typecheck:** the package has no `typecheck` script; it is a DEBT entry in `check-type-check-coverage.mjs` at 11 errors. `tsc -p packages/observability/tsconfig.json --noEmit` reads 11 errors, all in `src/__tests__/`, none in `semconv.ts`, and the program includes `semconv.ts`. `pnpm check:type-check-coverage` and `pnpm check:type-check-debt` pass (exit 0). - **`pnpm lint`:** the repo-wide `eslint . --no-inline-config` exits 0 (2026-09-30 10:54:08 to 10:57:25 UTC, at `62e556317c`). - **Gates:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (run with no path) derived **60** commands, and all 60 were run with their exit codes recorded before any pipe. - 59 exited 0 on the first pass. - `pnpm check:dual-build-cjs-loads` first exited 3 (PREREQUISITE NOT MET: no `dist` for 33 packages). After the whole-workspace build it was run again and exited 0. - `--ran` reconciliation: 「60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN」, exit 0. - The diff-scoped `node scripts/check-issue-citations.mjs` judged the 2 citations this change adds (`objectstack-ai#9650`, `objectstack-ai#9835`); both answer 200, exit 0. - `pnpm check:nul-bytes` passed, and a control-byte scan of the 4 changed files reads 0. - **Main moved during the work:** `origin/main` moved 8 commits past the base (to `1741c5dcb6`). `git diff --name-only` over the two packages and the changeset path reads 0 files, so this branch was not merged forward. The merge ref CI builds covers the joint tree. ## Acceptance notes - **Left outside `src/**`, listed and not changed (a later stage of the card):** `packages/verify/tsconfig.test.json:1` cites `objectstack-ai#15145`, which answers 404. The other 14 citations outside `src/**` in the two packages answer 200: `observability/vitest.config.ts:11`, `verify/tsconfig.json:5` and `:13`, `verify/tsconfig.test.json:1`, `:2`, `:32`, `:35`, `:43` and `:60`, and `verify/vitest.config.ts:8`, `:14`, `:32`, `:63` and `:70`. `README.md` in either package carries no `#N`. - **Release-owned, not a site:** `packages/observability/CHANGELOG.md:985` carries `objectstack-ai#10004` (with `objectstack-ai#9834`) in a released entry. This PR does not edit it. - **Open-PR overlap** (read 2026-09-30 10:58Z): 13 open PRs. Only the Version Packages PR objectstack-ai#20639 touches either package, and only in `CHANGELOG.md` and `package.json`. - **Not governed:** no path is on the governed-surface register. The diff changes 19 lines, far under 5,000. --- _Generated by [Claude Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #20773
Fixes #20467
Clause-②: no
Two ADR-0087 D3 semantic entries no longer said what the tree does. This PR corrects the text only. It has one commit per card and one shared
patchchangeset for@objectstack/spec. Each entry was edited by hand at its source, and every generated copy was written by its own generator.Owed before enqueue: the at-tier contract review by the owning seat. The review face is
packages/spec/src/**(non-test). This PR does not carry that record.Card 20773:
etl-pipeline-layer-retired(protocol 17), commit27070673The
replacementtext saidsyncConfig.schedulewas retired "in 18 under ADR-0049". Measured on base810d42b6:packages/spec/src/integration/connector.zod.ts:296reads: "syncConfig.schedulewas DELETED here in @objectstack/spec 17 (ADR-0049 enforce-or-remove ...".929d9e3f20("delete the seven cron-typed positions outright").packages/spec/CHANGELOG.mdlists it as929d9e3at line 8699, under## 17.5.0(line 3; the next heading is## 17.4.0at line 14893). The REST compare agrees: the@objectstack/spec@17.5.0tag is 1549 commits ahead of it and 0 behind, and@objectstack/spec@17.4.0is 172 commits behind it, so 17.4.0 does not contain it.syncConfig:git grep syncConfigoversrc/migrations/entriesandsrc/conversionshas 0 hits outside this entry.The sentence now reads "the same measurement that deleted
syncConfig.schedulein @objectstack/spec 17 under ADR-0049", which matches the wording ofconnector.zod.ts. The rest of thatreplacementparagraph (theConnectorSchema.syncConfigrouting) is untouched, because that rewrite belongs to #20281. The releasedCHANGELOG.mdsentence (line 11485) is left as history.Card 20467:
driver-sql-unresolvable-where-column-refused(protocol 18), commitf45b0968The entry named only a
wherecolumn onfind()/findOne()/count(), and onlyINVALID_FILTER/ 400. Measured on base810d42b6, which contains3e8b492d(git merge-base --is-ancestor 3e8b492d HEADexit 0):TursoDriver.remoteReadFault(packages/drivers/driver-turso/src/turso-driver.ts) classifies every remote read exit,aggregateincluded, with the local face's ownSqlDriver.aggregateBackendFault(packages/drivers/driver-sql/src/sql-driver.ts:10768).packages/drivers/driver-turso/src/turso-local-remote-missing-table-column-parity.test.tspins both faces to one answer. An aggregate over an absent table getsDATABASE_ERROR/ 500. An aggregate grouped by, or summing, a declared field whose column is absent getsINVALID_FIELD/ 400. An aggregate whosewherenames that field getsINVALID_FILTER/ 400. Before, the remote face answered[]to all three.What changed in the entry:
surfacenames theaggregate()door of the remote face and its three codes.replacement(the remedy) adds grouping and aggregating, and "so the object's table exists".acceptanceCriteriaadds reports and dashboards that group by, or aggregate over, a missing column. It quotes the driver'sINVALID_FIELDmessage fragment "has no column for, so the aggregate never ran" (unresolvableAggregateColumnRefusal) and namesDATABASE_ERROR/ 500 for an absent table.reasonis not touched.Generated copies for this card:
registry.tsonly.spec-changes.jsonanddocs/protocol-upgrade-guide.mdproject the chain up to the current protocol (spec-changes.jsonhas"from": 16, "to": 17). Neither carries any step-18 entry:grep -c driver-sql-unresolvable-where-column-refusedanswers 0 in each. The generators ran and left both files unchanged. The claim's file surface named them, so this is stated here as measured, not skipped.Proof that the generated diff is only the entries' text
git diff --stat 810d42b6 f45b0968:27070673. A token diff (git diff --word-diff=porcelain --word-diff-regex='[^[:space:]]+') shows exactly two swaps,retiredtodeletedand18to@objectstack/spec 17. They appear in 5 places: the entry,registry.ts,spec-changes.jsontwice, and the upgrade guide. No other token changed.f45b0968. The 16 changed lines ofregistry.tsare byte-identical, with indentation normalised, to the 16 changed lines of the entry file (cmpexit 0). They sit in 4 hunks at lines 9156 to 9244, inside the entry block that starts atregistry.ts:9151.810d42b6, and each gate went red:check:migration-registryexit 1,check:spec-changesexit 1,check:upgrade-guideexit 1.git checkout HEAD --then restored each copy. Its blob hash equals HEAD's, andgit diff HEADis empty. So every copy is generator-owned and gated.Verification at the final head
f45b0968pnpm --filter @objectstack/spec build: exit 0. Spec has no workspace dependencies, so its dependency closure is empty.pnpm --filter @objectstack/spec exec vitest run --project local: 578 files, 17065 passed, 1 todo. The existing pins over both entries (migrations.test.ts,cron-typed-positions-retirement.test.ts) pass 172 of 172.pnpm --filter @objectstack/spec run typecheck(tsc, scripts typecheck, test typecheck): exit 0.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 83 commands. All 83 were run, each with its exit code captured to a file before any pipe. The--ranreconciliation reads: 83 derived, 81 run, 2 NOT-MEASURED, 0 UNRUN. The named families all exit 0:check-adr-0087-registration --base origin/main("this PR adds no declared-breaking changeset"),check:migration-registry,check:spec-changes,check:upgrade-guide,check:generated("All 15 generated artifacts are up to date"),check:doc-authoring,check:changeset-no-major,check-empty-changesetandcheck:nul-bytes.node scripts/check-plugin-teardown-shape.mjs --self-testexits 3. Its positive control is pinned to commit621a4876, which this shallow clone cannot reach. It only checks the checker's own health.pnpm check:dual-build-cjs-loadsexits 3. It needs every package'sdist/, which a text-only spec diff does not build.@objectstack/formula,@objectstack/lint,@objectstack/objectql). Both then passed:check:doc-formula-expressions0 andcheck:lean-entry-closure0.eslint --no-inline-config --format jsonover the 6 changed paths atf45b0968returns 6 results. eslint's own config lints the 3.tsfiles, with 0 errors and 0 warnings. It reports the.mdand.jsonfiles as "File ignored because no matching configuration was supplied".eslint.config.mjsnever enables type-aware linting (noparserOptions.project, as it states at lines 326 to 328), so this diff cannot change a verdict on a file it did not touch. The fullpnpm lintrun belongs to CI.Acceptance notes
origin/mainmoved 4 commits past the base, to4b45afae. None of those commits touches these six paths (git diff --name-only 810d42b6 origin/mainhas no overlap). The branch was not merged, because the queue rebuilds it on current main.reasonofdriver-sql-unresolvable-where-column-refusedkeeps its 2026-08-16 addendum sentence "The entry id, surface and prescription are unchanged — this is a text amendment". That sentence describes that addendum, while this PR does changesurface. It is left as history, because the claim's file surface excludesreason. Nobody else is set to edit that text.turso-driver.ts(the "remote []" column inremoteReadFault's doc) is labelled "Measured at base6e3e5462c": it is the before-state of the refusal change, not a stale claim. This is not a finding.os migrate metaandregistry.tsonly.Generated by Claude Code