Skip to content

fix(spec): the etl-pipeline and unresolvable-where-column migration entries state what the tree does - #20838

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20773-etl-entry-retired-in-17
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20773-etl-entry-retired-in-17

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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 patch changeset 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), commit 27070673

The replacement text said syncConfig.schedule was retired "in 18 under ADR-0049". Measured on base 810d42b6:

  • packages/spec/src/integration/connector.zod.ts:296 reads: "syncConfig.schedule was DELETED here in @objectstack/spec 17 (ADR-0049 enforce-or-remove ...".
  • The deleting commit is 929d9e3f20 ("delete the seven cron-typed positions outright"). packages/spec/CHANGELOG.md lists it as 929d9e3 at line 8699, under ## 17.5.0 (line 3; the next heading is ## 17.4.0 at line 14893). The REST compare agrees: the @objectstack/spec@17.5.0 tag is 1549 commits ahead of it and 0 behind, and @objectstack/spec@17.4.0 is 172 commits behind it, so 17.4.0 does not contain it.
  • No protocol-18 entry or conversion names syncConfig: git grep syncConfig over src/migrations/entries and src/conversions has 0 hits outside this entry.

The sentence now reads "the same measurement that deleted syncConfig.schedule in @objectstack/spec 17 under ADR-0049", which matches the wording of connector.zod.ts. The rest of that replacement paragraph (the ConnectorSchema.syncConfig routing) is untouched, because that rewrite belongs to #20281. The released CHANGELOG.md sentence (line 11485) is left as history.

Card 20467: driver-sql-unresolvable-where-column-refused (protocol 18), commit f45b0968

The entry named only a where column on find() / findOne() / count(), and only INVALID_FILTER / 400. Measured on base 810d42b6, which contains 3e8b492d (git merge-base --is-ancestor 3e8b492d HEAD exit 0):

  • TursoDriver.remoteReadFault (packages/drivers/driver-turso/src/turso-driver.ts) classifies every remote read exit, aggregate included, with the local face's own SqlDriver.aggregateBackendFault (packages/drivers/driver-sql/src/sql-driver.ts:10768).
  • packages/drivers/driver-turso/src/turso-local-remote-missing-table-column-parity.test.ts pins both faces to one answer. An aggregate over an absent table gets DATABASE_ERROR / 500. An aggregate grouped by, or summing, a declared field whose column is absent gets INVALID_FIELD / 400. An aggregate whose where names that field gets INVALID_FILTER / 400. Before, the remote face answered [] to all three.

What changed in the entry:

  • surface names the aggregate() door of the remote face and its three codes.
  • replacement (the remedy) adds grouping and aggregating, and "so the object's table exists".
  • acceptanceCriteria adds reports and dashboards that group by, or aggregate over, a missing column. It quotes the driver's INVALID_FIELD message fragment "has no column for, so the aggregate never ran" (unresolvableAggregateColumnRefusal) and names DATABASE_ERROR / 500 for an absent table.
  • reason is not touched.

Generated copies for this card: registry.ts only. spec-changes.json and docs/protocol-upgrade-guide.md project the chain up to the current protocol (spec-changes.json has "from": 16, "to": 17). Neither carries any step-18 entry: grep -c driver-sql-unresolvable-where-column-refused answers 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:

 .changeset/20773-etl-entry-syncconfig-schedule-deleted-in-17.md            | 28 ++++++++++++++++++++++++++++
 docs/protocol-upgrade-guide.md                                             |  2 +-
 packages/spec/spec-changes.json                                            |  4 ++--
 .../spec/src/migrations/entries/semantic/17.etl-pipeline-layer-retired.ts  |  4 ++--
 .../entries/semantic/18.driver-sql-unresolvable-where-column-refused.ts    | 16 ++++++++++++----
 packages/spec/src/migrations/registry.ts                                   | 20 ++++++++++++++------
 6 files changed, 59 insertions(+), 15 deletions(-)
  • Commit 27070673. A token diff (git diff --word-diff=porcelain --word-diff-regex='[^[:space:]]+') shows exactly two swaps, retired to deleted and 18 to @objectstack/spec 17. They appear in 5 places: the entry, registry.ts, spec-changes.json twice, and the upgrade guide. No other token changed.
  • Commit f45b0968. The 16 changed lines of registry.ts are byte-identical, with indentation normalised, to the 16 changed lines of the entry file (cmp exit 0). They sit in 4 hunks at lines 9156 to 9244, inside the entry block that starts at registry.ts:9151.
  • Control, one-shot. With the entry carrying the new sentence, each generated copy was restored to 810d42b6, and each gate went red: check:migration-registry exit 1, check:spec-changes exit 1, check:upgrade-guide exit 1. git checkout HEAD -- then restored each copy. Its blob hash equals HEAD's, and git diff HEAD is empty. So every copy is generator-owned and gated.

Verification at the final head f45b0968

  • pnpm --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/objectstack derives 83 commands. All 83 were run, each with its exit code captured to a file before any pipe. The --ran reconciliation 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-changeset and check:nul-bytes.
  • NOT MEASURED, 2 gates. CI runs both.
    • node scripts/check-plugin-teardown-shape.mjs --self-test exits 3. Its positive control is pinned to commit 621a4876, which this shallow clone cannot reach. It only checks the checker's own health.
    • pnpm check:dual-build-cjs-loads exits 3. It needs every package's dist/, which a text-only spec diff does not build.
    • Two other gates first exited 3 and were cleared by building their prerequisites (@objectstack/formula, @objectstack/lint, @objectstack/objectql). Both then passed: check:doc-formula-expressions 0 and check:lean-entry-closure 0.
  • eslint, narrowed to the changed files. eslint --no-inline-config --format json over the 6 changed paths at f45b0968 returns 6 results. eslint's own config lints the 3 .ts files, with 0 errors and 0 warnings. It reports the .md and .json files as "File ignored because no matching configuration was supplied". eslint.config.mjs never enables type-aware linting (no parserOptions.project, as it states at lines 326 to 328), so this diff cannot change a verdict on a file it did not touch. The full pnpm lint run belongs to CI.
  • No new test. The changed text is prose, and no consumer parses the version phrase or the code list out of it.

Acceptance notes

  • origin/main moved 4 commits past the base, to 4b45afae. None of those commits touches these six paths (git diff --name-only 810d42b6 origin/main has no overlap). The branch was not merged, because the queue rebuilds it on current main.
  • The reason of driver-sql-unresolvable-where-column-refused keeps 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 change surface. It is left as history, because the claim's file surface excludes reason. Nobody else is set to edit that text.
  • The docblock table in turso-driver.ts (the "remote []" column in remoteReadFault's doc) is labelled "Measured at base 6e3e5462c": it is the before-state of the refusal change, not a stale claim. This is not a finding.
  • Card 20467's body names the upgrade guide as a reader of this entry. At protocol 17 the guide renders no step-18 entry, so today the entry reaches authors through os migrate meta and registry.ts only.

Generated by Claude Code

…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>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling labels Sep 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 1 changed file(s) yielded no anchor (packages/spec/spec-changes.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

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

Which tree this was computed on

This run read content/docs from 3a03685c69af49d4f4f5b3770c88445d01e003c3 — the merge of head f45b0968868f28087f4a1ffd4e509f610514688e into base 15b586dcffa88471afeaba4dbf560223692ad944, 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 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

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: f45b0968868f28087f4a1ffd4e509f610514688e
Local-runs: none

Inputs read: card #20773 (body + 3 comments), card #20467 (body + 3 comments), PR #20838 (body, 6-file list, net diff 810d42b6...f45b0968 via refs/review/pr20838), the check-runs on the head, and origin/main (22e584c9) by git show / git grep for every factual claim below. Nothing built, run or re-run.

① Derived judgments

Surface of the diff. Six files: one new changeset; two hand-edited D3 semantic entries (17.etl-pipeline-layer-retired.ts, 18.driver-sql-unresolvable-where-column-refused.ts); three generated copies (registry.ts, spec-changes.json, docs/protocol-upgrade-guide.md). No governed surface on the file list (Governed Surface Queue Guard: success). Head repo is the base repo, not a fork.

Accept set: nothing moves — judged RIGHT. Every changed line is inside a string literal of an entry's surface, replacement or acceptanceCriteria; both ids are unchanged; no conversion, schema, export, type or gate changes. The only consumers of those strings are os migrate meta (prints surface, replacement, reason, acceptanceCriteria — packages/cli/src/commands/migrate/meta.ts:377-380) and packages/spec/scripts/build-upgrade-guide.ts (renders them); nothing parses a version phrase or a code list out of them, and no test pins the amended sentences (git grep over packages/spec and scripts at origin/main: only two unrelated test comments carry "retired syncConfig.schedule", without a version phrase).

Card 20773 — the corrected sentence is TRUE against the tree — RIGHT.

  • packages/spec/src/integration/connector.zod.ts:296 at origin/main: "syncConfig.schedule was DELETED here in @objectstack/spec 17 (ADR-0049 enforce-or-remove ...". The new wording ("deleted ... in @objectstack/spec 17 under ADR-0049") follows it; "retired" to "deleted" matches the note's own verb.
  • packages/spec/CHANGELOG.md: the deleting commit 929d9e3 sits at line 8699, under ## 17.5.0 (heading at line 3; the next heading, ## 17.4.0, is at 14893). REST compare of 929d9e3f20fe33dc... against the tags: inside @objectstack/spec@17.5.0 (ahead 1549, behind 0) and outside @objectstack/spec@17.4.0 (that tag is 172 behind it). So "first released in 17.5.0" in the changeset is exact. Current spec version at origin/main: 17.5.0.
  • No protocol-18 entry or conversion names syncConfig (git grep syncConfig origin/main -- packages/spec/src/migrations/entries packages/spec/src/conversions: 0 hits outside this entry), so "in 18" had no reading as a step either.
  • Reach: git grep "in 18 under ADR-0049" refs/review/pr20838 excluding CHANGELOG.md: 0 hits — all five shipped copies now agree. The released CHANGELOG.md:11485 keeps the old sentence as history, as triage 5905074024 directed.
  • The rest of the replacement paragraph (the ConnectorSchema.syncConfig routing) is untouched, which is right: that rewrite is spec(integration): build the connector sync executor that syncConfig and fieldMappings declare (14 keys), once and on the mainstream shape #20281's (seat 5), excluded by the claim.

Card 20467 — the three codes and the message fragment are what main makes — RIGHT.

  • packages/drivers/driver-turso/src/turso-local-remote-missing-table-column-parity.test.ts at origin/main pins, local and remote alike: aggregate on an absent table DATABASE_ERROR / 500 (lines 208, 213); aggregate grouped by, or summing over, a declared field whose column is absent INVALID_FIELD / 400 (218, 223, 258); aggregate whose where names it INVALID_FILTER / 400 (263). Its header table records the remote face answering [] to all of these before the change. The entry's new surface sentence states exactly that set, scoped to TursoDriver's remote face, which is the face that changed.
  • The quoted INVALID_FIELD fragment "has no column for, so the aggregate never ran" is a verbatim substring of SqlDriver.unresolvableAggregateColumnRefusal (packages/drivers/driver-sql/src/sql-driver.ts:10852), which sets code = INVALID_FIELD, status = 400. The two pre-existing INVALID_FILTER fragments still match sql-driver.ts:888-889.
  • The remedy extension ("before filtering, grouping or aggregating on it, and so the object's table exists") matches the driver's own prescription text ("run schema sync so the column exists before aggregating on it").
  • No other entry registers the aggregate INVALID_FIELD refusal (git grep "aggregate never ran" origin/main -- packages/spec/src/migrations/entries: 0), so this entry is the right carrier, as PR fix(driver-turso): the remote face refuses a missing table or column with the local face's code instead of answering [] #20461's not-required (already-registered ...) marker assumed.

Generated copies — RIGHT. The registry.ts hunks are the two entry diffs and nothing else (step17 block at 2852, step18 block at 9153-9244). spec-changes.json and the upgrade guide carry only the card-20773 phrase; both project the chain from 16 to 17 and contain 0 occurrences of driver-sql-unresolvable-where-column-refused on the branch, so leaving them unchanged for card 20467 is the generators' correct output, not a skipped step. check:generated / check:migration-registry / check:spec-changes / check:upgrade-guide run under Lint & Repo Gates, which is success on this head.

Check-runs on f45b0968, as read at 2026-09-30T10:31Z: 33 runs — 25 success, 3 skipped (Console Pin Gate, Build Docs, Packed-tarball smoke opt-in), 5 in_progress (Test Core 1/6, 4/6, 5/6, 6/6; Type Check · workspace), 0 failure. Green among the completed: Lint & Repo Gates, Build Core, Check Changeset, Governed Surface Queue Guard, Spec property liveness, Type Check · source gates / debt ledger / consumer gates, Test Core 2/6 and 3/6, Dogfood Regression Gate 1-3, Dogfood Verify CLI, Temporal Conformance, the card/branch/single-writer claim checks, Check PR Size, Check Documentation Links. The in-progress conclusions are the gate's to give; nothing in this diff reaches a test input outside packages/spec.

② Semver level

  • @objectstack/spec: patch — RIGHT. The corrected strings publish: registry.ts compiles into dist, and spec-changes.json is in the package's files. A text fix in a released package is a patch; skip-changeset would have been wrong. Not breaking, so no ADR-0087 disposition marker is owed; Check Changeset is success on the head.
  • Clause-②: no — RIGHT. The line is carried identically on the PR body, in the changeset body and on both claims (5908312972, 5908548356), with no arm. No input changes side between accepted and refused, and no public export or type is added, removed or re-shaped (check:api-surface, check:authorable-surface in the dev's gate list; Lint & Repo Gates success).
  • The changeset body states the FROM to TO of the phrase and names both entries; it carries no tracker number (check:doc-authoring). One shared changeset for the two folded cards matches the fold claim ("The family shares one changeset").

③ Boundary flags

  • open_questions: [] on both os-dev reports. Nothing to answer there.
  • Out-of-scope finding (both reports, PR Acceptance notes): the 18 entry's reason keeps the 2026-08-16 addendum sentence "The entry id, surface and prescription are unchanged — this is a text amendment" while this PR changes surface. Answered: the sentence sits inside the paragraph headed "Addendum 2026-08-16" and states that addendum's own scope; it is dated history, in the same way the released CHANGELOG sentence is kept for card 20773. os migrate meta does print reason, so a reader meets it, but next to a surface that now names the aggregate door, and the changeset / CHANGELOG record this amendment's scope. The claim's file surface excluded reason, so the dev was right not to touch it. No card owed; a dated addendum line in reason is at the owning seat's discretion as a later text amendment, not a condition of this landing.
  • Deviation "spec-changes.json / upgrade guide unchanged for card 20467": measured by me as well (0 step-18 hits, chain 16 to 17) — right.
  • Deviation "no new test pin": right — the change is prose no consumer parses; the existing pins (migrations.test.ts, cron-typed-positions-retirement.test.ts) are in the Test Core shards.
  • Deviation "main advanced": origin/main is now 22e584c9; none of the six paths is touched between the base and there. The queue rebuilds on current main.
  • Deviation "commit trailers": both commits carry Claude-Session: plus Co-authored-by: Claude and the PR body the session-URL footer — the AGENTS.md form (line 453). Right; AGENTS.md outranks the harness reminder.
  • NOT MEASURED locally (check-plugin-teardown-shape --self-test, check:dual-build-cjs-loads): CI-owned; no failure on the head at read time.
  • Landing: no governed surface, so no tier applies; this record is the review face's own requirement (packages/spec/src/** non-test). The PR is draft with auto-merge unarmed; the owning seat readies and queues it once the five in-progress runs conclude green.

Implemented-by: claude/issue-20773-etl-entry-retired-in-17
Reviewed-by: session_018fxqvRJW12TaHC7DUQ89Y6

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 10:43
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 93e9e42 Sep 30, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20773-etl-entry-retired-in-17 branch September 30, 2026 11:09
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment