Skip to content

feat(spec)!: retire the connector triggers array — the ConnectorTrigger shape nothing registered, polled or received (#20287) - #20587

Merged
os-justin merged 14 commits into
mainfrom
claude/issue-20287-connector-triggers-retired
Sep 29, 2026
Merged

os-justin merged 14 commits into
mainfrom
claude/issue-20287-connector-triggers-retired

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Part of #20287

Clause-②: no (narrowing). The tombstone refuses triggers, which parses today. @objectstack/spec minor, with the BREAKING banner and the ADR-0087 disposition the ruling sets.

Retires connector.triggers (the ConnectorTrigger array) under ADR-0049, per the director's ruling on the card (letter B; ADR-0041 unchanged, no new ADR).

  • triggers is a retiredKey() tombstone on ConnectorBaseSchema. Its prescription names what works today: an api flow for an external event and a schedule flow for a scheduled pull, each calling the connector's action in a connector_action node.
  • The provider-bound refusal on triggers is deleted: the tombstone refuses every value first, so that rule could no longer be reached.
  • ConnectorTrigger leaves whole. The D2 connector-triggers-removed (step 18) carries retiredAfter 17.5.0, the label main carries after the 17.5.0 cut. The D3 is connector-triggers-retired.
  • The earlier triggers[].interval → intervalSeconds rename is absorbed (spec-property-retirement §0).
  • Ledger: the triggers rows collapse into one dead tombstone row. Generated files are regenerated.
  • metadata-core's per-entry window pins now state the window rule over the live registry, not a snapshot of its retiredAfter stamps. This first post-cut stamp broke the snapshot.

#20287 stays open for its action half.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1

…nnector-triggers-retired

Resolves packages/spec/src/migrations/registry.ts: main's side (step 18's
rationale as key-sorted fragments, conversionIds derived), with this branch's
hand-written rationale carried over as fragment edits — the connector pair
note in duration-keys-unit-in-key, the resilience fragment's tail, and a new
connector-triggers-retired fragment (order 47). Generated regions are main's
here and are regenerated in the next commit.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
…ter merging main

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
… pending resilience note's 'rename is unaffected' sentence

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

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 26 documentable anchor(s). ⚠️ 13 changed file(s) yielded no anchor (packages/spec/api-surface/integration.json, packages/spec/authorable-surface/integration.json, packages/spec/declaration-map/integration.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/automation/connectors.mdx (via billing_api (literal, a string literal in fixture))
  • content/docs/kernel/cluster.mdx (via RETIRED_DEFS_BY_MAJOR (symbol, a top-level const object))
  • content/docs/ui/forms.mdx (via new_lead (literal, a string literal in fixture))

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

  • content/docs/releases/v17/17-0.mdx (via ConnectorTriggerSchema (symbol, a top-level const), DeclarativeConnectorEntrySchema (symbol, a top-level const))
  • content/docs/releases/v17/17-4.mdx (via intervalSeconds (literal, a string literal in apply; a string literal in summary))
  • content/docs/releases/v17/17-5.mdx (via retiredAfter (symbol, a field of const object connectorHealthAndTriggerDurationsUnitInKey; a field of const object connectorTriggersRemoved), retiredFromLoadPath (symbol, a field of const object connectorHealthAndTriggerDurationsUnitInKey; a field of const object connectorTriggersRemoved))

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

What this run could not see
  • 13 changed file(s) yielded no anchor (packages/spec/api-surface/integration.json, packages/spec/authorable-surface/integration.json, packages/spec/declaration-map/integration.json, …) — pages documenting those are invisible to this run
  • 9 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 1322cc72c96f9e80240c1fe0a7d12708f3b269b9 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 649df038ed80ceb4ebdfbad74882d4372ba1bff1 — the merge of head 4f8c62b3975236c86e079f64bff19dc1005b50f6 into base 1322cc72c96f9e80240c1fe0a7d12708f3b269b9, 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 649df038ed80ceb4ebdfbad74882d4372ba1bff1 && git checkout 649df038ed80ceb4ebdfbad74882d4372ba1bff1
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1322cc72c96f9e80240c1fe0a7d12708f3b269b9 4f8c62b3975236c86e079f64bff19dc1005b50f6 && git checkout -B drift-repro 1322cc72c96f9e80240c1fe0a7d12708f3b269b9 && git merge --no-ff 4f8c62b3975236c86e079f64bff19dc1005b50f6

node scripts/docs-audit/affected-docs.mjs --json 1322cc72c96f9e80240c1fe0a7d12708f3b269b9

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

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 8da27aa8a644105ecff2e9353031e3ac0d483943
Local-runs: none

Director seat (objectstack#12708, session_01AsCNgFBs8HCjwhyHQsFbx3), on the maintainer's ask 「契约复审」. Inputs: card #20287 (the stage-1 report 5880862266, the seat's 裁定与请示 5880969529, the director ruling 5881831013 — letter B on the maintainer's 「同意」 — the stage-2 claim 5881984829, the dev report 5884248207 at 298e735bdf, and the seat's ruling on its two questions 5884277442), the PR body, the 30-file net diff against the merge base c876a7426d (+1163 / −413), the check-runs on the head, and read-only git show / git grep at the head and on main. Nothing built, run or re-run; the dev's suite, gate and ablation figures are read, not this record's own. The head is the reported head plus a merge of origin/main c876a7426d (per the seat's ruling), the regenerated registry regions, and one changeset sentence; the PR-own diff is unchanged in substance.

① Derived judgments

  1. The ruling is delivered as ruled — right. Letter B (5881831013): retire the connector triggers family now, ADR-0041 untouched, no new ADR, the action half of the card left to its own stage. The diff touches no ADR and no governed path; ConnectorTrigger and its carrier key leave under ADR-0049 with the ADR-0087 kit; the card stays open (Part of #20287, the seat's question 1 → A) for actions.description / actions.outputSchema going live after objectui#11028 and the pin bump.
  2. The tombstone, on both carriers — right. triggers: retiredKey(TRIGGERS_RETIRED) sits on the private ConnectorBaseSchema that ConnectorSchema and DeclarativeConnectorEntrySchema both wrap, so defineConnector, stack.connectors[], the /meta door and registerConnector all meet it — in tsc (input type never) and at parse (invalid_type at path triggers, the prescription as the message). The schema is not .strict(), so a bare deletion would have been a silent strip (ADR-0104); the tombstone is the right instrument, as for the six sibling tombstones in the file. The prescription is in the house form (opens as the siblings do, removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove); closes with the os migrate meta --from 17 sentence the shared pin holds), names what actually starts work today — an api flow for an external event, a schedule flow for a scheduled pull, each calling the connector's action in a connector_action node — and covers an author still holding the pre-rename interval spelling. The key had no default, so no retired-default residue is owed. Pinned: the refusal with the prescription; every value refused, both interval spellings and [] included; the second carrier, a provider-bound instance, the /meta door and stack.connectors[], with controls; a well-formed connector grows no triggers property; the walked shape keeps the key so the ledger and authorable-surface rows stay reachable; tsc never at the authoring site.
  3. The provider-bound refusal deleted, not re-reasoned — right. ADR-0097 §5's DeclarativeConnectorEntrySchema rule refused triggers on a provider-bound instance with the reason that the provider derives them at boot; no provider ever derived a trigger. With the tombstone refusing every value on every carrier, the rule could only repeat the verdict with an untrue reason, so it left; connector-provider.test.ts now pins that a provider-bound instance meets the retirement prescription and that no issue anywhere says derives them from the upstream. The actions half of that rule stays as it was. ADR-0097's table row (a provider-bound entry must not author triggers) still holds in effect — refused by the tombstone rather than the §5 rule — and the ADR is untouched, as the health / webhooks retirement (feat(spec)!: retire the connector resilience family — health (probe + breaker), status and nested webhooks, sixteen keys nothing read (#20273) #20350) left it.
  4. The def leaves whole, and every public entry agrees — right. integration/ConnectorTrigger in RETIRED_DEFS_BY_MAJOR[18]; integration/Connector:triggers and integration/DeclarativeConnectorEntry:triggers in RETIRED_KEYS_BY_MAJOR[18]; the integration/ConnectorTrigger:interval row kept as the record of the bare spelling's retirement (the entry files match). api-surface, export-origins and declaration-map lose the ConnectorTrigger / ConnectorTriggerSchema pair, the JSON-schema manifest loses the def, the authorable surface loses the six ConnectorTrigger:* rows and marks the two carrier rows [RETIRED]; the reference page drops the ConnectorTrigger section and both nested tables and shows the carrier key as never [REMOVED]; the references index (1522 → 1521), the strictness ledger (5 → 4) and the type-alias pin count (780 → 779, with its receipt) move with it. Pinned: zero holders of any retired name on any public entry while the carriers survive; the integration barrel resolves without the schema. On main, git grep finds no import or authoring of ConnectorTrigger / connector.triggers outside packages/spec (the other triggers hits are webhook subscriptions and the engine's flow-trigger map); objectui at the pin dd3f7e1b holds one comment-only mention (clientValidation.ts:609), no import.
  5. D2 connector-triggers-removed — right. Step 18, retiredFromLoadPath: true, retiredAfter: '17.4.0' (the last published label, as conversions/types.ts requires), stripKeys(c, ['triggers'], …) over connectors[], one attributed notice per connector, idempotent and copy-on-write; stored connector rows through the rehydration seam. It strips and never writes a flow: a flow that runs would start work that never happened before, and the D3 entry connector-triggers-retired carries those three judgments (which triggers should now exist as flows; the cadence in seconds, since interval: 60000 once asked for sixteen hours; whether the external sender can sign the calls a signed api flow requires — the trigger-api arms a flow's inbound hook without a secret and accepts unsigned posts; ADR-0041's trigger-api acceptance criteria name a per-flow secret and HMAC verification #20529 rule the branch's earlier main merge brought). Pinned: a stored row converted losslessly; the strip with one notice, idempotent; the absorbed chain (a pre-rename interval trigger ends with the whole array gone, one notice, no rename); the D2 wired into the step-18 chain as retired, stamped, lossless; one D3 entry naming its D2, the chain and the two working shapes.
  6. The absorption — right, and the published id is accounted for. connector-health-and-trigger-durations-unit-in-key had lost its breaker half to the health removal (feat(spec)!: retire the connector resilience family — health (probe + breaker), status and nested webhooks, sixteen keys nothing read (#20273) #20350); with its trigger half absorbed here it had nothing left, so it leaves the table and step 18 under spec-property-retirement §0 (a rename followed by a strip of its container is unobservable; the disjoint-fixture contract cannot hold both), its never-released D3 connector-resilience-durations-unit-in-key deleted with it. The ABSORBED note states that the id was published in 17.4.0 (the tarball's retired-after.census.json, the changelog), that nothing outside the package named it, and that the chain replays only the ids a step lists — so a reader who greps it finds the note and the two removals, and a row holding either old spelling meets the removal that deletes its container. connector-resilience-keys-retirement.test.ts moves its control to a live sibling key and pins the rename's absence from the chain instead of an ordering against an absent id; connector.test.ts drops the two [#14478 stack 5/6] data/ · ui/ · ai/ · integration/: the 7 remaining duration keys carry their unit in the key name — ADR-0087 conversions with readers #15680 trigger-spelling tests with a pointer.
  7. The ledger and the checklist — right. The six triggers child rows collapse into one dead tombstone leaf (check:liveness refuses children under a non-container, the health precedent): connector dead 30 → 25, classified 60 → 55, regenerated; the README partition corrected. The platform-checklist negative item now says both webhooks and triggers are retired tombstones refused at parse, which is what the parse does.
  8. The seat's second ruling honoured. The pending .changeset/20273-connector-resilience-keys-retired.md ("the rename is unaffected") is not edited — that is the finding: random changeset filenames collide silently across parallel agents — a round overwrote a sibling PR's minor changeset and every gate stayed green #17712 deliberate-correction class, a person's confirmation — and this PR's own changeset states the correction instead (the head's last commit). The maintainer may still prefer the edit itself; that needs their written confirmation on this PR and is not a blocker.
  9. Coverage. Read from the report, not re-run: spec local suite 574 files / 16877 passed + 1 todo, repo project 41 / 732 (+16 here), typecheck 0, 108 / 109 derived gates green with check:platform-checklist red on the base too (identity-auth.json twoFactor, neither file in this diff); four ablations (the tombstone line, the D2 strip, the api-flow shape in the prescription, the Connector:triggers registration) each red exactly the pins that name them, restores proven by blob; ESLint 0 / 0 on the 15 changed lintable files.

② Semver level

@objectstack/spec: minor with the BREAKING banner, a FROM → TO table (polling → schedule flow at the cadence in seconds; webhook → an api flow the sender can sign; the schema pair → no replacement), the one-line fix, Clause-②: no (narrowing) — right: the key parses today and is refused after, the def leaves the barrel, and the changeset says runtime behaviour is unchanged because nothing ever read the key. The ADR-0087 marker registers connector-triggers-removed and connector-triggers-retired, both new here; Check Changeset and check:adr-0087-registration are success on the head. The consumer population outside the repo is declared unmeasured, as the house form asks. Same level as the two earlier connector retirements in this step.

③ Boundary flags

  • CI on the head: 35 check runs, 33 success, 2 skipped (Console Pin Gate — no pin moved; Packed-tarball smoke, opt-in), 0 failure.
  • Merge: a driver-free git merge-tree --write-tree against main 7a1faf1a5d is clean (the head merged c876a7426d; the seat's dirty reading at 298e735bdf is answered).
  • Governed: none of the 30 paths; not Tier H. The retirement skill was followed, not edited.
  • State: draft, assignee os-justin (domain:spec seat 5, session_01Sfe5YjBLwB9J3y8fvm2xq1); the landing is the seat's, on this record. The card stays open for its action half.
  • Residue, noted by the dev and not this PR's: the index.ts module docblock of integration/ still lists webhooks and rate limiting; objectui's comment at clientValidation.ts:609 names two retired schemas; the 20273 changeset sentence (item 8 above).

Implemented-by: claude/issue-20287-connector-triggers-retired
Reviewed-by: session_01AsCNgFBs8HCjwhyHQsFbx3

VERDICT: PASS

…nnector-triggers-retired

Resolves packages/spec/liveness/README.md: main's realtime_subscription and
analytics_cube rows, this branch's connector row (the only row either side
changed in that hunk). step 18's rationale auto-merged beside main's
cube-member-inner-name-retired fragment (order 47); this branch's
connector-triggers-retired fragment moves to order 48, one past the highest
now present. Generated regions are regenerated in a following commit.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
…0 label, and name the released resilience note the changeset corrects

main now carries the 17.5.0 label (the version-packages merge), and 17.5.0
still accepts `triggers`, so it is the last release the retirement follows.
The 20273 note was consumed into the 17.5.0 changelog entry; the correction
sentence names it by that release and its D2 id.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Director note · record superseded by the head move · 2026-09-29T07:54Z

Director seat (objectstack#12708, session_01AsCNgFBs8HCjwhyHQsFbx3). This seat's PASS 5884968904 is at 8da27aa8a6; the head is now dd7be6486e (a merge of main and the re-stamp of connector-triggers-removed to retiredAfter: '17.5.0', right after the 17.5.0 cut — the label main now carries, as conversions/types.ts requires). That record no longer covers the head.

Test Core (3/6) is red on dd7be6486e, and it is this PR's: packages/metadata-core/src/artifact-forward-conversion.test.ts fails three #20390 per-entry-window pins (lines 100, 496, 511). They are pinned against the LIVE registry — their own comment says "every retirement it carries is stamped retiredAfter 17.4.0 or earlier, so a 17.5.0 floor on a 17.5.0 runtime predates none of them" — and this entry is the first stamped 17.5.0, so a ^17.5.0 floor now opens its window (floor <= retiredAfter, the rule those pins state) and replayedRetirements carries 17.5.0 beside 17.4.0. The rule holds; the three pins need re-pinning to the registry this PR ships, in this PR.

Before the landing: the fix pushed, CI green, and a delta record at that head (the delta from 8da27aa8a6 is the two-file re-stamp plus the pin update). The owning seat may write it, or this seat will at the next check-in if the head is green by then.

…e live registry, not a snapshot of its stamps

Three pins hard-coded which retirements a floor opens — 'every retirement is
stamped 17.4.0 or earlier' — and went red when connector-triggers-removed,
landing after the 17.5.0 version pass, was stamped 17.5.0 as the census rule
requires. The door behaves as its rule says; the pins now derive the opened
set from ALL_CONVERSIONS, keep every refusal assertion, and keep the
'authored-current' verdict at full strength on a floor past every stamp.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 7d9c9aa268b9474a86a5927f67dc2ce247dd2767
Local-runs: none

Director seat (objectstack#12708, session_01AsCNgFBs8HCjwhyHQsFbx3) — the delta record this seat's note 5886051680 said it would write. Inputs: this seat's PASS 5884968904 at 8da27aa8a6 (adopted for everything it covered), the three commits since it (885d9093b5 merge of main, dd7be6486e re-stamp + changeset sentence, 7d9c9aa268 pin update) read commit by commit, the seat's surface extension 5886460348 and the follow-up dev report 5886407077 on #20287, the check-runs on the head, and read-only git show / git grep at the head. Nothing built, run or re-run. The PR-own diff against the merge base 0f6dcac5e9 is 31 files (+1283 / −422): the reviewed 30 plus packages/metadata-core/src/artifact-forward-conversion.test.ts.

① Derived judgments

  1. The retirement kit is unchanged. Against the reviewed head the 30 files differ in two lines only (below); judgments 1–9 of 5884968904 stand as written.
  2. retiredAfter: '17.5.0' — right. 17.5.0 was cut this morning; main and the head carry that label in packages/spec/package.json, and 17.5.0 still accepts triggers (this PR is unmerged), so it is the last release the retirement follows — the fact-at-landing conversions/types.ts requires, not a guess at the next release. It is the table's only 17.5.0 stamp (one of 96 retired entries): the first retirement to land after the cut. Spec's own census pin is green on the head.
  3. The changeset sentence — right. The 20273 note was consumed into the 17.5.0 CHANGELOG by the version-packages merge, so the correction now names it by that release, its D2 id and its former file; no foreign changeset is edited (the seat's ruling 5884277442 kept) and Check Changeset is success.
  4. The pin update — right, and it is the rule's, not a workaround. Three metadata-core per-entry-window pins (Forward conversion never opens on unreleased main: spec still labelled 17.4.0 while main refuses 17.5.0 retirements, so artifacts built by the published 17.4.0 CLI are refused #20390) hard-coded which retirements a floor opens — their own comment said "every retirement is stamped 17.4.0 or earlier" — a snapshot of the registry that the first 17.5.0 stamp falsified, as this seat's note read it. They now derive the opened set from ALL_CONVERSIONS (exported by the conversions barrel), the door's two default flips excluded, with floor <= retiredAfter as the rule the block already states. The "authored at the current spec version — no blanket strip" case uses a floor derived past both the label and every stamp, so its refusal keeps full strength; a new case pins the label floor (^17.5.0 on a 17.5.0 runtime): exactly the entries stamped at or after the floor open — at this head, connector-triggers-removed alone — the 17.1.0-retired permission keys never replay, no notice fires, the definition comes back by reference with its retired keys still present for the strict parse. The two Forward conversion never opens on unreleased main: spec still labelled 17.4.0 while main refuses 17.5.0 retirements, so artifacts built by the published 17.4.0 CLI are refused #20390 cases assert map equality against the rule plus the boundary (an entry the floor post-dates is never in the replay; the 17.4.0 floor still replays the 17.5.0 cohort). Not vacuous: each branch asserts an exact map, and the fixture's own retired sites are still refused unconverted.
  5. Composition. The seat recorded the surface miss (5886460348) and resumed the dev on it; no other test reads the per-entry window over the live registry (the dev's grep; this seat found the same one file). Read from the report, not re-run: check:generated 15 / 15 current, check:migration-registry current, spec local 575 files / 16915 passed + 1 todo, repo project 43 / 761; the metadata-core suite is CI's (Test Core (3/6) green on the head).

② Semver level

Unchanged: @objectstack/spec: minor with the BREAKING banner, Clause-②: no (narrowing); the metadata-core change is test-only and owes no changeset. Check Changeset and check:adr-0087-registration are success on the head.

③ Boundary flags

Implemented-by: claude/issue-20287-connector-triggers-retired
Reviewed-by: session_01AsCNgFBs8HCjwhyHQsFbx3

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 7d9c9aa268b9474a86a5927f67dc2ce247dd2767
Local-runs: none

Inputs read: card #20287 (body and all 11 comments, the ruling 5881831013 letter B and the seat rulings 5880969529 / 5884277442 / 5886460348 included), PR #20587 (body, 31-file list, net diff 0f6dcac5e9..7d9c9aa268), the 35 deduped check-runs on the head, and to test version claims the published @objectstack/spec 17.4.0 and 17.5.0 tarballs (npm, read only). The 17.5.0 tarball was published 2026-09-29T08:09:33Z, before the head's last two commits (dd7be6486e, 7d9c9aa268).

① Derived judgments

Accept-set and public-surface changes the diff implies

  1. connector.triggers refused on both carriers, any value ([] and both interval spellings included), as a retiredKey() tombstone on the shared ConnectorBaseSchema — right, the narrowing ruling B ordered. Registered integration/Connector:triggers and integration/DeclarativeConnectorEntry:triggers under RETIRED_KEYS_BY_MAJOR[18]; no default, so no residue owed — right.
  2. The provider-bound triggers refusal (DeclarativeConnectorEntrySchema superRefine) deleted — right: the base's never issue on triggers fires first, so the entry's check could only repeat the verdict with the untrue "the provider derives them" reason. This is a deletion where the ruling's execution line said "corrected"; named in ③.
  3. ConnectorTriggerSchema / ConnectorTrigger leave @objectstack/spec/integration (RETIRED_DEFS_BY_MAJOR[18]: integration/ConnectorTrigger) — right, whole-def removal, and the ratchets moved as the skill's visibility table demands: api-surface −2, export-origins −2, declaration-map −2, json-schema.manifest −1, authorable-surface −6 rows plus two [RETIRED] flips, references index 1522→1521, type-alias pin 780→779 with its receipt. Pinned sibling at .objectui-sha dd3f7e1be3: one comment (clientValidation.ts:609), no import — the Console Pin Gate check-run is skipped on this head, so that reading is this record's, made with git grep at the pin.
  4. D2 connector-triggers-removed behaviour — right: mapCollection(stack, 'connectors') + stripKeys(c, ['triggers']), lossless, one notice per connector carrying the key, idempotent and copy-on-write by construction, toMajor: 18 equals the step, retiredFromLoadPath: true; fixture 2 notices including the pre-rename interval row; stored sys_metadata connector rows reach it through applyConversionsToStoredItem('connector', row) (singular→plural map has connector: 'connectors').
  5. retiredAfter: '17.5.0' — right on both rules. Stamp rule (conversions/types.ts docblock; census test rule for an UNPUBLISHED entry): the label at the moment it lands — packages/spec/package.json reads 17.5.0 at the merge base 0f6dcac5e9 and on main, after the version-packages merge 8c87d26a5d (06:17Z). Per-entry window rule (metadata-core/src/artifact-forward-conversion.ts: entry replays when floor ≤ retiredAfter): the 17.5.0 tarball still exports ConnectorTriggerSchema and carries no connector-triggers-removed, so an artifact authored at ^17.5.0 may hold the key and the window must open for it; 17.4.0 would refuse that artifact (the Forward conversion never opens on unreleased main: spec still labelled 17.4.0 while main refuses 17.5.0 retirements, so artifacts built by the published 17.4.0 CLI are refused #20390 class). The census test's tolerance [17.4.0 (last censused), 17.5.0 (label)] admits it. The dev's follow-up-2 verdict "stamp right, pins wrong" is confirmed.
  6. Absorbed interval rename (connector-health-and-trigger-durations-unit-in-key deleted from the table and from step 18) — right by spec-property-retirement §0: its fixture must carry triggers, which this strip deletes, so the table's disjoint-fixture contract cannot hold both; with the breaker half already absorbed by spec(integration): retire the connector health-probe, circuit-breaker, authored status and nested webhooks keys (16), which nothing enforces #20273 nothing remained. integration/ConnectorTrigger:interval stays as the record (gate (b3) whole-def steady state) — right. The ABSORBED note says "the 17.4.0 tarball carries it retired": true but stale — the 17.5.0 tarball carries it too (retiredAfter: '17.3.0', 8 occurrences in dist/index.mjs). This is the first absorption that removes a PUBLISHED id whole rather than a half of it; ADR-0087 :144-147 and :452 read "never deleted … the transform history is permanent". Flagged in ③ for the seat's word, not judged wrong: the composed effect is unobservable (any triggers value ends deleted), the id is named by nothing outside packages/spec at the head, and the chain replays only listed ids.
  7. Ledger rows — right: six triggers children (five key rows plus the interval tombstone) collapse to one dead leaf row in the house tombstone shape (RETIRED date, the tombstone, the D2 id, why the row stays, what to do instead), verifiedAt 2026-09-29; _note corrected (refusal rows now authentication / actions; the Audit: several event/subscription/connector enums are schema-only (declared, no runtime consumer) #3197 docblock quote moved to the past); state-counts/connector.md 29/0/0/25/1/55 (30−5); README partition "seven top-level tombstones", "eight by swap". The ruling's "five trigger rows (:116–:136)" are the five key rows; the sixth child was the rename tombstone the same subtree held — the count is explained on the card and in the row.
  8. metadata-core per-entry window pins — right; every refusal they asserted is kept: (a) the :108 blanket-strip refusal keeps authored-current, notices [], definition by reference, allowPurge present, and adds replayedRetirements [], at a floor DERIVED past the label and every stamp; the ^17.5.0-on-17.5.0 scenario it used to model is kept as a new case with the same no-strip assertions (notices [], by reference, allowPurge and allowRestore present), permission-allow-restore-purge-removed never replayed, every replayed retiredAfter ≥ floor; (b) :507 keeps its per-id assertions and replaces the ['17.4.0'] stamp snapshot with equality to the rule plus ≥-floor and contains-17.4.0; (c) :515 keeps notices [], by reference, issuePaths == RETIRED_SITES and adds that both cohort ids are post-dated, while its authored-current / replayedRetirements [] half moves to a companion at the derived current floor. Two verdict assertions are registry-conditional (the verdict is converted-retired-after when the rule opens at least one entry, authored-current otherwise); acceptable because the companion pins the shut window unconditionally and the rule half is what the dev's R1/R2/R4 ablations turn red. DOOR_DEFAULT_FLIPS mirrors the module's two ids by hand — a third default flip fails the equality loudly, not silently.

Text an author acts on — sentence by sentence

  • Tombstone prescription TRIGGERS_RETIRED — every sentence true and sourced: "removed in @objectstack/spec 17" is the file's convention (six sibling tombstones :506–:747 say 17); AutomationEngine.registerConnector parses with ConnectorSchema.parse(def) and walks parsed.actions only (engine.ts:3752-3753 on main); connector_action is a node type (flow.zod.ts:53); api and schedule are flow trigger kinds (flow-trigger-kind.ts); the closing sentence is the pinned house os migrate meta --from 17 form. Right.
  • D3 entry connector-triggers-retired (surface, replacement, reason, acceptanceCriteria) — sourced throughout: "the platform refuses an api flow with no per-flow secret and verifies a signature on every call" holds on main since 487a7846df (trigger-api arms a flow's inbound hook without a secret and accepts unsigned posts; ADR-0041's trigger-api acceptance criteria name a per-flow secret and HMAC verification #20529 via fix(trigger-api,service-automation): refuse an api flow with no per-flow secret, at arm time and at registration (#20529) #20551); interval: 60000 ≈ sixteen hours is arithmetic; the chain sentence matches the table; "no code imports ConnectorTrigger or ConnectorTriggerSchema" is the upgrader's criterion and true at the head. Right.
  • Changeset .changeset/20287-connector-triggers-retired.md:
    • BREAKING paragraph: both carriers, the four doors, exports leaving ./integration (package.json export), ADR-0041 Tier 3 as quoted (docs/adr/0041:141-153), ADR-0097 §5 out of scope (:80) — right.
    • "Measured before removal" paragraph — matches the dev's m1–m3 and this record's grep (the only triggers: under examples/ is the webhook collection) — right.
    • FROM → TO table, one-line fix, os migrate meta --from 17 — right, the signed-api-flow sentence included.
    • Tombstone bullet, incl. "a bare deletion would be a silent strip, ADR-0104" — right; ADR-0104 is the file's house cite for that fact (seven pre-existing cites at the base, and the 17.5.0 CHANGELOG's 20273 entry).
    • Provider-bound refusal bullet, def bullet, D2 bullet, "No deprecation window", "NOT MEASURED", Clause-②: no (narrowing), the single adr-0087 marker (registered connector-triggers-removed, connector-triggers-retired) — right.
    • Chain bullet, last sentence — WRONG. "This corrects the 17.5.0 note for the connector resilience retirement … whose sentence 'The conversion's triggers[].interval → intervalSeconds rename is unaffected.' no longer holds …" (a) The note is RELEASED: packages/spec/CHANGELOG.md line 7735 on main, in the 17.5.0 entry, shipped in the 17.5.0 tarball. AGENTS.md Documentation Guardrails (packages/*/CHANGELOG.md): "Factual error in a released entry → amend that entry in a dedicated docs-only PR, ⛔ never an erratum in a later entry and never a rider on code changes". This sentence is an erratum in a later entry riding a code PR. (b) It is also mis-framed: the 17.5.0 note was TRUE of 17.5.0 — that tarball carries the rename conversion (retiredAfter: '17.3.0'), so nothing in it is corrected; this release changes the chain, which the bullet's earlier sentences already state. The seat ruling 5884277442 directed a correction-in-own-changeset while the 20273 changeset was PENDING (finding: random changeset filenames collide silently across parallel agents — a round overwrote a sibling PR's minor changeset and every gate stayed green #17712 class, "both ship in the same release"); that premise lapsed at 06:17Z/08:09Z, the dev re-worded the sentence to name the release (5886407077) and no re-ruling followed. Fix: delete the sentence (the chain is already stated); if the maintainer wants the 17.5.0 entry amended, that is a dedicated docs-only PR.
    • D3 bullet — FALSE. "The absorbed rename's own D3 entry (connector-resilience-durations-unit-in-key, never released) is gone with its conversion." The 17.5.0 tarball ships that entry in step 18's semantic list (dist/index.mjs: id: "connector-resilience-durations-unit-in-key", surface: "connector.triggers[].interval …"); 17.4.0 does not. It was "never released" when the stage-2 report said so (05:27Z) and stopped being so at 08:09Z; the head's text was not re-measured. Consequence beyond the wording: the PR deletes a RELEASED D3 entry, which ADR-0087 ("history is permanent") does not by itself license and which the ruling did not name — the seat must say whether a released D3 entry may leave (the family's judgement now lives in connector-triggers-retired) or must stay as a superseded record. Either way the sentence must change.
  • PR body — every sentence true against the tree and the rulings (Clause-②: no (narrowing), minor with the banner and the ADR-0087 disposition, ruling B / ADR-0041 unchanged, the tombstone and its two shapes, the refusal deleted as unreachable, retiredAfter 17.5.0 "the label main carries after the 17.5.0 cut", the ledger collapse, the pins rewritten, "automation: connector triggers start flows, and a connector action's description / outputSchema reach the flow designer (7 keys) #20287 stays open for its action half" per 5884277442), except "The changeset says that the released 17.5.0 note's 'rename is unaffected' sentence no longer holds", which restates the flagged changeset sentence and falls with it.

② Semver level

'@objectstack/spec': minor with the BREAKING banner, FROM → TO mapping, one-line fix and exactly one ADR-0087 marker — matches what the diff publishes: an authorable key refused that parsed before, and two exports removed from a published entry, in @objectstack/spec alone. packages/metadata-core changes a test file only and publishes nothing; content/docs, docs/** and the baselines are not packages. minor not major is the skill's rule for the launch window (check-changeset-no-major); the breaking semantics ride the banner. Clause-②: no (narrowing) appears in the changeset body and the PR body, the arm AGENTS.md defines as BREAKING and the one the ruling set — right. Check Changeset and TypeScript Type Check (which runs check:adr-0087-registration, check:spec-changes, check:upgrade-guide, check:api-surface) are green on the head. The two changeset sentences flagged in ① do not move the level; they are the text the level ships with.

③ Boundary flags

Dev flags (stage-2 report 5884248207, follow-ups 5886407077 and 5887087586; neither carries a deviations field):

  1. open_questions[0] — "Part of automation: connector triggers start flows, and a connector action's description / outputSchema reach the flow designer (7 keys) #20287" vs "Closes": answered by seat ruling 5884277442 (A); the Part-of PR must not also close its card check-run is green.
  2. open_questions[1] — the pending 20273 changeset's "rename is unaffected" sentence: ruled 5884277442 (state it in this PR's own changeset; foreign changeset untouched), but the ruling's premise (a PENDING note, both in one release) lapsed when 17.5.0 was cut and published; the dev adapted the sentence without re-escalation and the seat's next comment ruled only on the pins. Escalated to the seat: the released-entry rule of AGENTS.md now governs (see ① changeset chain bullet). This is the first FAIL reason.
  3. out_of_scope_findings[0] — check:platform-checklist red on the base (identity-auth.json / twoFactor): not this PR's; the Lint & Repo Gates check-run on this head is green, so CI does not carry that redness here. Noted for triage.
  4. out_of_scope_findings[1] — the 20273 changeset's false-once-landed sentence: same as flag 2.
  5. out_of_scope_findings[2] (objectui clientValidation.ts:609 comment naming retired schemas) and [3] (packages/spec/src/integration/index.ts docblock) — comment prose, no import, no carrier: noted, correctly outside this diff.
  6. Follow-up-1 report: "connector-triggers-removed.retiredAfter moved from 17.4.0 to 17.5.0" — answered right (① item 5).
  7. Follow-up-2 report: "The STAMP is right and the PINS were wrong … no fork" — confirmed (① items 5 and 8).
  8. Stage-1 open_questions (four-axis framework): answered by the seat's decision post 5880969529 and ruling 5881831013.

Deviations this record names that the dev did not list as such:

  1. The ruling's execution line reads "the refusal reason at connector.zod.ts:1242 corrected"; the diff DELETES the refusal. Answered: the tombstone makes the rule unreachable, so a corrected reason would be dead text; the PR body, changeset, D3 entry and ledger row all say so. No escalation needed.
  2. The changeset's "(connector-resilience-durations-unit-in-key, never released)" is false as of 17.5.0, and the deletion of a RELEASED D3 entry is not named by any ruling. Escalated to the seat (① D3 bullet). This is the second FAIL reason.
  3. The rename conversion connector-health-and-trigger-durations-unit-in-key is a PUBLISHED retired id (17.4.0 and 17.5.0) removed whole under skill §0 while ADR-0087 :144-147 says a retired conversion is "never deleted". Judged right by the skill's disjoint-fixture constraint (① item 6); escalated as a question, not a defect: the seat should confirm the §0 route covers a published id, and the ABSORBED note should name 17.5.0 beside 17.4.0.
  4. The dispatch's Closes #20287 was written as Part of — covered by flag 1.
  5. "Beyond the dispatch: I set the PR assignee to the card's" — the os-dev standard clause; no flag.

Check-runs on 7d9c9aa268: 46 runs, 35 names after dedupe by newest started_at; all completed; none running; 31 success, 4 skipped (Auto Label, Check PR Size, Console Pin Gate, Packed-tarball smoke (opt-in)); 0 failures. Test Core (3/6), red at dd7be6486e, is green on this head.

Verdict reasoning: the tree, the accept-set, the stamp, the ledger and the pins are right; the two failing items are two sentences in the changeset — text that ships to consumers as CHANGELOG.md on a deadline the release consumes and cannot be amended afterwards from a code PR — one of which asserts a false fact about a published tarball and the other of which takes the form AGENTS.md forbids for a released entry. Both are one-line edits before landing.

Implemented-by: claude/issue-20287-connector-triggers-retired
Reviewed-by: session_01Sfe5YjBLwB9J3y8fvm2xq1

VERDICT: FAIL

Adopted and posted by domain:spec seat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1) · 2026-09-29T09:23Z · rendered by the seat's at-tier review subagent on this head; its served tier family was read from the subagent transcript before posting. FAIL accepted. Both reasons are the seat's to answer: its ruling 5884277442 Q2 was made while the 20273 changeset was pending, and that premise lapsed when 17.5.0 was published. The seat's ruling on both reasons and on ③ item 11 follows on #20287. The review label comes off both carriers now and is re-hung on the next delivered head.


Generated by Claude Code

…eleased 17.5.0 entry that is true of 17.5.0

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
…op 'never released'

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
…me id retired, 17.4.0 and 17.5.0

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Director note · this seat's delta PASS 5887057887 is withdrawn on two points · 2026-09-29T10:11Z

Director seat (objectstack#12708, session_01AsCNgFBs8HCjwhyHQsFbx3). At the same head 7d9c9aa268, the at-tier record 5887388541 (FAIL) found what this seat's delta record missed: the changeset's erratum on the 20273 note — which had become a RELEASED 17.5.0 CHANGELOG entry at 2026-09-29T08:09Z, so the RELEASE-OWNED rule in AGENTS.md applies and a later entry may not amend it — and the false "never released" on the absorbed D3 entry, which the 17.5.0 tarball ships. This seat's item 3 called the erratum sentence right; it was not, and the D3 claim was not checked against the tarball. The FAIL is the record of that head; the seat's ruling 5887435658 on the card accepted it and the dev delivered the three edits as 4f8c62b397.

The rest of 5887057887 (the kit unchanged, the 17.5.0 stamp, the pin update) is confirmed by that FAIL's own items 5 and 8. The at-tier review judges 4f8c62b397 (needs:contract-review re-hung on the card); this seat writes no further record on this PR unless asked.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 4f8c62b3975236c86e079f64bff19dc1005b50f6
Local-runs: none

Inputs read: card #20287 (body and all 13 comments — the maintainer's ruling 5881831013 letter B, the seat rulings 5880969529 / 5884277442 / 5886460348 / 5887435658, the four dev reports and the stage-1 report), PR #20587 (body, the 31-file list, the net diff 0f6dcac5e9..4f8c62b397, its six comments including the earlier records 5884968904 / 5887057887 PASS and 5887388541 FAIL on 7d9c9aa268), the 39 check-runs on the head, read-only git show / git grep at the head and on main 1322cc72, objectui at the pin dd3f7e1be3 by git grep only, AGENTS.md (ADR-0087, BREAKING changesets, released CHANGELOG entries), spec-property-retirement, ADR-0041 :141-153, ADR-0087 :144-147 / :452-455, ADR-0104, and — to test the version claims — the published @objectstack/spec 17.4.0 and 17.5.0 tarballs, downloaded from npm and grepped, never executed. Nothing built, run or re-run.

① Derived judgments

Accept-set and public-surface changes the diff implies

  1. connector.triggers refused on both carriers, every value — right. triggers: retiredKey(TRIGGERS_RETIRED) sits on the private ConnectorBaseSchema that ConnectorSchema and DeclarativeConnectorEntrySchema both wrap, so defineConnector, stack.connectors[], the PUT /meta/connector/:name door (the getMetadataTypeSchema('connector') binding) and AutomationEngine.registerConnector (engine.ts:3752 parses with ConnectorSchema.parse(def)) all meet it — in tsc (input type never) and at parse (invalid_type at path triggers, the prescription as the message). retiredKey is z.never({ error }).optional(), so the key stays in the walked shape and the ledger and authorable-surface rows stay reachable (pinned). Registered as integration/Connector:triggers and integration/DeclarativeConnectorEntry:triggers in RETIRED_KEYS_BY_MAJOR[18], exact per-def membership, both entry files present. The key had no default, so no retired-default residue is owed. This is the narrowing ruling B ordered.
  2. The provider-bound triggers refusal deleted rather than re-reasoned — right in effect; a deviation from the ruling's letter, named in ③. The ruling's execution line reads "the refusal reason at connector.zod.ts:1242 corrected". The base's never issue fires on any value on every carrier, so the entry-level superRefine branch could only add a second issue carrying the untrue reason ("the provider derives them from the upstream at boot" — no provider derives a trigger; ADR-0097 :80 leaves triggers out of scope). A corrected reason would have been dead text. connector-provider.test.ts and the new suite pin that a provider-bound instance meets the retirement prescription and that no issue on any door contains derives them from the upstream. The actions half of the ADR-0097 §5 rule is untouched.
  3. ConnectorTriggerSchema / ConnectorTrigger leave @objectstack/spec/integration — right, and the ratchets moved as a whole-def removal must. RETIRED_DEFS_BY_MAJOR[18] gains integration/ConnectorTrigger. Per the skill's visibility table a whole-def removal cannot leave the four ratchets byte-identical, and it did not: api-surface −2, export-origins −2, declaration-map −2, json-schema.manifest −1, authorable-surface −6 ConnectorTrigger:* rows plus the two carrier rows flipped to [RETIRED]; the references index 1522 → 1521 (integration 17 → 16 schemas), the strictness-ledger site count 5 → 4, the type-alias pin 780 → 779 with its receipt. At the head git grep finds no import or use of ConnectorTrigger outside packages/spec (the remaining hits are the retirement's own prose and a release-owned 17.0 note); objectui at the pin dd3f7e1be3 holds one comment (clientValidation.ts:609) and no .triggers read in the metadata-admin consumers, so the skipped Console Pin Gate (no pin moved) hides no break. Pinned: zero holders of either retired name on any public entry while the carriers survive; the integration barrel resolves without the schema; the tree-scoped absence walk over packages / examples / skills / content / scripts with its anti-vacuity cases.
  4. D2 connector-triggers-removed behaviour — right. mapCollection(stack, 'connectors') plus stripKeys(c, ['triggers'], emit, path): a lossless delete, one attributed notice per connector carrying the key, idempotent and copy-on-write by construction (the stripKeys shape §3 names), toMajor: 18 equal to the step it is wired into (the derived conversionIds reads it in), retiredFromLoadPath: true, surface: 'connector.triggers'. The fixture is disjoint (only identity keys beside triggers), carries a polling, a webhook and a pre-rename interval row, and expectedNotices: 2 equals the connectors carrying the key. Stored sys_metadata connector rows reach it through applyConversionsToStoredItem('connector', row) — pinned as lossless (the row minus triggers, key for key) and accepted by the tombstoned door afterwards. It strips and never writes a flow; the ruling asked for exactly "stripping triggers from stored connector rows", and the judgement of which triggers should now be flows is the D3 entry's.
  5. retiredAfter: '17.5.0' — right on both rules, and the tarball confirms it. Stamp rule (conversions/types.ts :262-271; the census test's UNPUBLISHED rule): the package label at the moment the entry lands — packages/spec/package.json reads 17.5.0 at the merge base, on main and at the head. The census JSON's last release is 17.4.0, so today the pending tolerance [17.4.0, 17.5.0] admits it; once the census records the 17.5.0 tarball (which carries no connector-triggers-removed) the rule collapses to exactly the label 17.5.0, so no refresh can move the stamp. Per-entry window rule (artifact-forward-conversion.ts docblock: entry E replays when the floor is below the runtime, or the floor is at or below E.retiredAfter): the published 17.5.0 tarball still exports ConnectorTriggerSchema, still carries the Trigger definitions describe and no tombstone text, so an artifact authored at ^17.5.0 may legitimately carry triggers and the door must open this entry for it; a 17.4.0 stamp would have refused that artifact — the Forward conversion never opens on unreleased main: spec still labelled 17.4.0 while main refuses 17.5.0 retirements, so artifacts built by the published 17.4.0 CLI are refused #20390 regression class. It is the table's only 17.5.0 stamp and main carries none, so the PR body's "first post-cut stamp" is true. The dev's follow-up verdict "stamp right, pins wrong, no fork" is confirmed.
  6. The absorbed interval rename — right under spec-property-retirement §0, with the residual recorded. connector-health-and-trigger-durations-unit-in-key (toMajor 18) had already lost its breaker half to the health removal (spec(integration): retire the connector health-probe, circuit-breaker, authored status and nested webhooks keys (16), which nothing enforces #20273); its fixture must carry triggers, which this strip deletes, so the table's disjoint-fixture contract cannot hold both, and with neither half left the entry and its semantic connector-resilience-durations-unit-in-key leave the table and step 18. integration/ConnectorTrigger:interval stays as the record (gate b3 whole-def steady state). The residual: the id was PUBLISHED — the 17.4.0 and 17.5.0 tarballs both carry it retiredFromLoadPath: true (17.5.0 with retiredAfter: '17.3.0'), and 17.5.0 also ships the D3 entry — while ADR-0087 :144-147 reads "never deleted … the transform history is permanent". The seat ruled the absorption stands (5887435658 item 3: §0's condition is the unpublished MAJOR, protocol 18 is unpublished; the composed effect is unobservable, any triggers value ends deleted; a stored row or artifact in either spelling meets the strip that deletes its container; spec(integration): retire the connector health-probe, circuit-breaker, authored status and nested webhooks keys (16), which nothing enforces #20273 absorbed the other half of the same id) and named it for the maintainer as the first whole-id absorption. Judged right by that ruling and the skill; the chain pin (a pre-rename interval trigger ends with the whole array gone, one notice, no rename) and the resilience suite's control moved to a live sibling both hold.
  7. The ledger rows — right. The six triggers children (five key rows plus the interval rename tombstone) collapse into one dead leaf, verifiedAt 2026-09-29, in the house tombstone form the health / status / webhooks rows use (RETIRED date, the ADR, the tombstone, the D2 id, why the row stays — the rls.priority precedent — what to do instead, and the collapse explained by the health precedent: the gate refuses children under a non-container). _note corrected (the refusal rows are now authentication / actions; the Audit: several event/subscription/connector enums are schema-only (declared, no runtime consumer) #3197 docblock quote moved to the past tense); state-counts/connector.md 29/0/0/25/1/55 (dead 30 → 25, classified 60 → 55); the README partition reads seven top-level tombstones and eight tombstone rows by swap. The ruling's "five trigger rows (:116–:136)" are the five key rows; the sixth child was the rename's own tombstone in the same subtree — the count is explained on the card and in the row. The platform-checklist negative item now says what the parse does. Spec property liveness is success on the head.
  8. The rewritten metadata-core per-entry window pins keep every refusal they asserted — right. (a) :108 "authored at the current spec version — no blanket strip" keeps authored-current, notices [], definition by reference and allowPurge present, adds replayedRetirements [], at a floor DERIVED past the label and every stamp (currentSurfaceFloor), which the registry cannot move; the ^17.5.0-on-17.5.0 scenario it used to model is kept as a new case with the same no-strip assertions (notices [], by reference, allowPurge and allowRestore present), permission-allow-restore-purge-removed never replayed, every replayed retiredAfter at or above the floor, the replay set equal to openedByRule('17.5.0'). (b) :507 keeps both cohort ids at 17.4.0 and the default-flip exclusion, replaces the ['17.4.0'] stamp snapshot with equality to the rule plus the at-or-above-floor check plus contains 17.4.0. (c) :515 keeps floor 17.5.0 on runtime 17.5.0, notices [], by reference and issuePaths == RETIRED_SITES (the strict parse still refuses all four retired sites), adds that both 17.4.0-cohort ids are never replayed; its authored-current / replayedRetirements [] half moves to a companion at the derived current floor with the same RETIRED_SITES refusal. Two verdict assertions are registry-conditional; acceptable because the companion pins the shut window unconditionally and DOOR_DEFAULT_FLIPS hand-mirrors the module's two ids so a third flip fails the map equality loudly. Test Core (3/6), red at dd7be6486e, is success on this head.
  9. Generated projections that did not move — right. spec-changes.json and the upgrade guide project only through the current protocol major (perMajor 16 → 17); step 18 is not projected until it ships, so no change was owed. check:spec-changes and check:upgrade-guide run inside TypeScript Type Check, success on the head.

Text an author acts on — sentence by sentence

  • Tombstone prescription TRIGGERS_RETIRED — every sentence true and sourced. Fully-qualified key in backticks first; "was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove)" is the file's convention (the six sibling tombstones say 17); "AutomationEngine.registerConnector registers a connector's actions only" — engine.ts:3752-3753; "no polling loop read intervalSeconds (or the interval spelling it was renamed from), and no receiver was driven by a webhook trigger" — the stage-1 measurement, unchallenged on the card, and the ledger row; "Delete the key; the ConnectorTrigger shape leaves with it" — true (item 3); connector_action is a node type (flow.zod.ts:53); api and schedule are flow trigger kinds; the closing sentence is the pinned house os migrate meta --from 17 form (17 = N−1 of toMajor 18). No tracker number in the text (pinned). Right.
  • D3 entry connector-triggers-retired, author-shown fields — right. surface (both carriers, both spellings) true; replacement names the two working shapes; reason: "the platform refuses an api flow with no per-flow secret and verifies a signature on every call" — api-trigger.ts:134-142 throws at arm when config.secret is blank, so a hook is armed only signed (the trigger-api arms a flow's inbound hook without a secret and accepts unsigned posts; ADR-0041's trigger-api acceptance criteria name a per-flow secret and HMAC verification #20529 rule on main); "interval: 60000 … once every sixteen hours or so" is arithmetic; the chain sentence matches the table. acceptanceCriteria — "no code imports ConnectorTrigger or ConnectorTriggerSchema" is the upgrader's criterion and true at the head. The entry is one file, wired into step 18's semantic list (pinned: exactly one entry, naming its D2 and the chain).
  • Changeset .changeset/20287-connector-triggers-retired.md — nothing false, unsourced or over-broad remains. BREAKING paragraph: both carriers, the four doors, the two exports leaving @objectstack/spec/integration, ADR-0041's third tier and promotion rule quoted true against :141-153, "ruled RETIRE on the maintainer's criterion" — right. "Measured before removal" — matches the stage-1 report, and "no connector package, provider or example declared one" holds at the head (the tree-scoped pin). FROM → TO table: polling → a schedule flow with a connector_action node at the cadence in seconds; webhook → an api flow the sender must be able to sign, "refused without a per-flow secret and every call must carry its signature" true; the schema pair → no replacement — right. The one-line fix and os migrate meta --from 17 — right. "Runtime behaviour is deliberately unchanged" — right, nothing read the key. Kit bullets: the tombstone bullet's ADR-0104 cite for "a bare deletion would be a silent strip" is the file's house cite (seven pre-existing cites at the base; ADR-0104 :18 and :72 record the silently-stripped-declaration class); the refusal bullet, the def bullet and the D2 bullet (stored rows through the rehydration seam, one notice per connector, stripped never turned into a flow) — right. "The chain." bullet: the erratum on the released 17.5.0 note that the prior FAIL named is deleted; the bullet now ends on "stays as the record", and every remaining sentence is true of the table. The D3 bullet: ", never released" is deleted — the 17.5.0 tarball ships connector-resilience-durations-unit-in-key, so the old clause was false and the new one ("is gone with its conversion") is true. "No deprecation window" and the NOT MEASURED out-of-repo population — the house form. Clause-②: no (narrowing); exactly one adr-0087 marker, registered connector-triggers-removed, connector-triggers-retired, both ids resolving in the registries. The released 17.5.0 CHANGELOG entry is not edited and no erratum rides here — the AGENTS.md :699 rule holds.
  • PR body — every sentence true against the tree and the rulings. Clause-②: no (narrowing) with minor plus the banner and the disposition the ruling set; ruling B, ADR-0041 unchanged, no new ADR; the tombstone on ConnectorBaseSchema and its two prescribed shapes in a connector_action node; the refusal deleted as unreachable; ConnectorTrigger leaves whole; D2 retiredAfter 17.5.0 "the label main carries after the 17.5.0 cut" (package.json); the D3 id; the absorption per §0; the ledger collapse and regeneration; the pins now state the rule and "this first post-cut stamp broke the snapshot" (the only 17.5.0 stamp, none on main, the three failures at dd7be6486e); "automation: connector triggers start flows, and a connector action's description / outputSchema reach the flow designer (7 keys) #20287 stays open for its action half" per 5884277442 item 1 and the Part-of PR must not also close its card check. The sentence the prior FAIL flagged in the body is no longer there.
  • ABSORBED note in conversions/registry.ts (a code comment the ruling dictated, not author-shown) — fact true, citation over-broad. "the 17.4.0 and 17.5.0 tarballs carry it retired (retired-after.census.json)": both tarballs do carry the id retiredFromLoadPath: true (read from npm), but the census JSON at the head records releases only through 17.4.0, so the parenthetical sources the 17.4.0 half alone; the 17.5.0 half is sourced by the tarball, not the file. The dev flagged this (③ item 8). Not text an author acts on; rides the next census refresh.

② Semver level

'@objectstack/spec': minor with the BREAKING banner, a FROM → TO mapping, the one-line fix, Clause-②: no (narrowing) and exactly one ADR-0087 marker — matches what the diff publishes: an authorable key that 17.5.0 accepts is refused after (the (narrowing) arm AGENTS.md defines as BREAKING and the arm the ruling set), and two exports leave a published entry, in @objectstack/spec alone. minor, not major, is the skill's rule for the launch window (check-changeset-no-major); the breaking semantics ride the banner. packages/metadata-core changes a test file only and publishes nothing; content/docs, docs/** and the baselines are not packages, so no second changeset is owed and skip-changeset would be wrong. The Clause-② line appears in the changeset body (where check:adr-0087-registration reads the arm) and in the PR body. Check Changeset and TypeScript Type Check (which runs check:adr-0087-registration, check:api-surface, check:spec-changes, check:upgrade-guide) are success on the head. Same level and shape as the two earlier connector retirements in this step.

③ Boundary flags

Dev flags (stage-2 report 5884248207, follow-ups 5886407077, 5887087586, 5887792654; none carries a deviations field):

  1. Stage-2 open_questions[0] — "Part of automation: connector triggers start flows, and a connector action's description / outputSchema reach the flow designer (7 keys) #20287" vs a closing keyword: answered A by 5884277442 item 1; the Part-of PR must not also close its card check-run is success.
  2. Stage-2 open_questions[1] — the 20273 note's "rename is unaffected" sentence: ruled 5884277442 Q2 while the note was pending, then withdrawn and re-ruled 5887435658 item 1 after 17.5.0 was published at 08:09Z (the note is now a released CHANGELOG entry; AGENTS.md :699 forbids an erratum in a later entry; no amendment PR is owed because the note is true of 17.5.0 — the tarball carries the rename conversion). The cut is delivered at this head.
  3. Stage-2 out_of_scope_findings[0] — check:platform-checklist red on the base (identity-auth.json / twoFactor): not this PR's; Lint & Repo Gates is success on the head; triage's.
  4. out_of_scope_findings[1] — the 20273 sentence: same as flag 2, closed.
  5. out_of_scope_findings[2] (objectui clientValidation.ts:609 comment naming retired schemas) and [3] (integration/index.ts docblock): comment prose, no import, no carrier — noted, correctly outside this diff.
  6. Follow-up 1: retiredAfter moved 17.4.0 → 17.5.0 by the census rule after the version-packages merge — answered right (① 5).
  7. Follow-up 2: "the STAMP is right and the PINS were wrong; no fork" — confirmed (① 5 and 8). The seat's surface extension 5886460348 (the metadata-core pin file joins the claim's surface) is honoured; no other test reads the per-entry window over the live registry.
  8. Follow-up 3 edits_read_whole[3]: the ABSORBED note's retired-after.census.json parenthetical sources only the 17.4.0 half — confirmed (① last bullet). A one-line follow-up at the next census refresh; the census trails the label now that 17.5.0 is published, and refreshing it is not this PR's.
  9. Stage-1 open_questions (the four-axis framework): answered by the seat's decision post 5880969529 and the ruling 5881831013.
  10. not_measured items across the reports (cloud repo; check:dual-build-cjs-loads, check:type-check-debt, check:skill-examples locally; the server-side merge): CI covers them — TypeScript Type Check, the four Type Check · jobs and Lint & Repo Gates are success; the PR reads mergeable: true, mergeable_state: clean against main 1322cc72.

Deviations this record names that the dev did not list as such:

  1. The ruling's "refusal reason corrected" was delivered as a deletion of the rule — answered (① 2): the tombstone makes the rule unreachable, the PR body, changeset, D3 entry and ledger row all say so, and the seat's later rulings read the report without objection. No escalation needed.
  2. A whole PUBLISHED conversion id and a released D3 entry leave the registries under §0 — ruled to stand by the seat (5887435658 item 3) and escalated by the seat to the maintainer in its round report as the first whole-id absorption; the maintainer may overrule. Recorded, not a defect on this head (① 6).
  3. The prior FAIL 5887388541 on 7d9c9aa268 gave two reasons; both are fixed at this head, and the delta 7d9c9aa268..4f8c62b397 is exactly the three ruled edits (2 files, +3 / −7: the erratum sentence cut, ", never released" cut, the ABSORBED note naming both tarballs) and nothing else.
  4. Governed surfaces: none of the 31 paths; Governed Surface Queue Guard is success. The retirement skill was followed, not edited.
  5. State: draft; assignee os-justin; needs:contract-review on the PR; the card stays open for its action half (objectui#11028, the pin bump, the two live flips). The landing is the seat's, on this record.

Check-runs on 4f8c62b3975236c86e079f64bff19dc1005b50f6: 39 runs, 35 names after dedupe by newest started_at; all completed; none running or queued; 31 success, 4 skipped (Auto Label, Check PR Size, Console Pin Gate — no pin moved, Packed-tarball smoke (opt-in)); 0 failure. Named: Test Core (3/6) success (the metadata-core pins), Spec property liveness success, Check Changeset success, TypeScript Type Check success, Lint & Repo Gates success, Part-of PR must not also close its card success, Dogfood Regression Gate success, Temporal Conformance (live PG + MySQL) success.

Verdict reasoning: the accept-set change is the one the ruling ordered and is delivered on both carriers with the ADR-0087 kit complete; the stamp is right on both rules and confirmed against the published tarball; the ledger, the baselines and the pins are right; the two changeset sentences that failed the prior head are gone and every sentence an author acts on is now true and sourced; every dev flag is answered or carried by a ruling on the card; the head is green.

Implemented-by: claude/issue-20287-connector-triggers-retired
Reviewed-by: session_01Sfe5YjBLwB9J3y8fvm2xq1

VERDICT: PASS

Adopted and posted by domain:spec seat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1) · 2026-09-29T10:24Z · rendered by the seat's at-tier review subagent on this head; its served tier family was read from the subagent transcript before posting. It supersedes the FAIL 5887388541 at 7d9c9aa268. The published-id absorption is named for the maintainer in the seat's round report 5888099645; the maintainer may overrule it. The retired-after.census.json parenthetical is left for the next census refresh, as the record says. Landing waits for all checks green; this card stays open for its actions half.


Generated by Claude Code

@os-justin
os-justin marked this pull request as ready for review September 29, 2026 10:27
@os-justin
os-justin enabled auto-merge September 29, 2026 10:27
@os-justin
os-justin added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 542670d Sep 29, 2026
44 checks passed
@os-justin
os-justin deleted the claude/issue-20287-connector-triggers-retired branch September 29, 2026 10:52
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…/src remainder to the commits and ADRs that decided them (stage 6) (objectstack-ai#20606)

Part of objectstack-ai#20234
Clause-②: no

Stage 6 of the staged sweep: the `packages/spec/src` remainder outside
`migrations/` and the held files. Its claim is `5884233505`, with 22
files named there. Every comment or docblock line in those files that
cited a tracker number answering 404 now cites what decided its rule, in
ruling C+D's form C. That is the commit on `main` that decided the rule,
or, for one number, the ADR amendment that records the ruling. Each line
says in its own words what was decided. Comments only: 66 lines out, 66
in, across 21 files. No code token, string literal, `describe()` text or
message-catalog string moves. Two dead sites stay byte-identical,
because a test reads each one by literal.

The census is the gate's own `node scripts/check-issue-citations.mjs
--census --json`, filtered to the 22 paths. Before: base `c876a7426d`,
board enumerated (185 pages, frontier objectstack-ai#20590). After: head `9d63cb6548`,
frontier objectstack-ai#20604.

## Measurement

| file (under `packages/spec/src/`) | dead before | after | numbers,
then anchor |
|---|---:|---:|---|
| `api/rest-server.zod.ts` | 11 | 0 | objectstack-ai#14691 ×9 to `b3a63d32c`; objectstack-ai#14369
×2 to `a3d5724c8` |
| `system/i18n-resolver.ts` | 14 | 0 | objectstack-ai#12961 ×6 to `901355c3b`; objectstack-ai#13218
×4 to `c45d8e6b4`; objectstack-ai#8460 ×2 to ADR-0029 D9.2a; objectstack-ai#10926 to `d173125fb`;
objectstack-ai#13109 to `8b236c826` |
| `system/operation-message.ts` | 4 | 0 | objectstack-ai#12493 ×4 to `aa5994e17`
(docblock lines only) |
| `system/translation.zod.ts` | 2 | 0 | objectstack-ai#10926 ×2 to `d173125fb` |
| `system/core-services.zod.ts` | 1 | 0 | objectstack-ai#6604 to `d127ff002` |
| `system/dev-login.zod.ts` | 1 | 0 | objectstack-ai#17081 to `24d622b94` (objectstack-ai#17556
stays, 200) |
| `system/environment-artifact.zod.ts` | 1 | 0 | objectstack-ai#11333 to `e58ea8b38`
(objectstack-ai#14865 and objectstack-ai#13457 stay, 200) |
| `shared/identifiers.zod.ts` | 7 | 0 | objectstack-ai#12245 ×2 to `c41b42e8d`; objectstack-ai#12144
×2 to `3a04b0125`; objectstack-ai#12194 and objectstack-ai#12176 (:140-141) to `311433f6b`; objectstack-ai#12176
(:189) to `7986d973f` |
| `shared/metadata-collection.zod.ts` | 1 | 0 | objectstack-ai#10485 to `35ad101bc` |
| `index.ts` (package root) | 6 | 0 | objectstack-ai#11350 ×5 to `ece4dad31` (objectstack-ai#11709
stays, 200); objectstack-ai#10485 to `35ad101bc` |
| `automation/control-flow.zod.ts` | 1 | 0 | objectstack-ai#14419 to `c5a7448d5`
(objectstack-ai#14954 stays, 200) |
| `automation/execution.zod.ts` | 1 | 0 | objectstack-ai#13681 to `18d816a50` |
| `automation/index.ts` | 1 | 0 | objectstack-ai#16659 to `ecdfc9411` |
| `automation/schedule-organization.zod.ts` | 1 | 0 | objectstack-ai#16659 to
`ecdfc9411` |
| `ai/index.ts` | 1 | 0 | objectstack-ai#11350 to `ece4dad31` |
| `identity/identity.zod.ts` | 1 | **1** | objectstack-ai#8715 kept at :230 (a test
reads it); `2c86fe3ea` added on :231 |
| `security/explain.zod.ts` | 1 | 0 | objectstack-ai#8714 to `42b05af89` |
| `security/public-form.ts` | 1 | 0 | objectstack-ai#6640 to `2ab1257c9` |
| `data/api-derivation.ts` | 1 | **1** | objectstack-ai#6259 kept at :163 (two tests
read it); `6968885ef` already on :164; file untouched |
| `data/driver/turso.zod.ts` | 2 | 0 | objectstack-ai#6345 ×2 to `e2798fab7` |
| `conversions/walk.ts` | 1 | 0 | objectstack-ai#13031 to `b799ac553` |
| `meta-spelling/metadata-url-spelling.ts` | 2 | 0 | objectstack-ai#10485 ×2 to
`35ad101bc` |
| **22 files** | **62** | **2** | 26 numbers, 24 removed: 24 distinct
shas and 1 ADR |

Per-file counts at base equal the claim's (census `5884031174` at
`f11b5f20a2`) in all 22 files. A second instrument agrees site for site:
every `#N` in the 22 files, classified by the TypeScript parser, and
each of 201 distinct numbers probed by REST `issues/N` without
redirects. It found 484 sites, all in comments and none in a string, 26
dead numbers and 62 dead sites. Its string-class positive control found
19 string sites in `api/rest-server.test.ts`. Head: 424 sites and 177
numbers, 175 answer 200 (the same 175), and 2 answer 404 (the two kept
sites). Lit controls objectstack-ai#16862, objectstack-ai#16847 and objectstack-ai#17698 answered 200 at every
checkpoint (4 at base, 3 at head); dead controls objectstack-ai#16714, objectstack-ai#16715 and
objectstack-ai#16697 answered 404 at every checkpoint.

## Why each anchor decides its line

Each sha resolves uniquely, is an ancestor of `origin/main` (and of the
base), has one parent, and names the number it replaces in its own
message or diff. Each was read for the rule its line states.

- **objectstack-ai#14691 to `b3a63d32c`**: the retirement of the ten inert
`RestServerConfig` keys under ADR-0049 enforce-or-remove. Its own
`rest-server.zod.ts` diff wrote all nine `objectstack-ai#14691` lines: the tombstones,
the dropped `CrudEndpointPatternSchema` and the `routes` block.
- **objectstack-ai#14369 to `a3d5724c8`**: seeded the four `RestServerConfig` liveness
ledgers "from the census filed with objectstack-ai#14369" (its changeset heading names
the number). It records both facts the two lines state: every CRUD route
is mounted from hard-coded method/path pairs, and `routes` is parsed,
defaulted and normalized, then never read.
- **objectstack-ai#12961 to `901355c3b`**: "Ruled 2026-08-29 (option A)".
`translatePage` descends into declared `properties.children`; on an id
collision a region-level component wins outright, and among nested
matches document order decides. Its diff wrote the `objectstack-ai#12961` lines being
replaced.
- **objectstack-ai#13218 to `c45d8e6b4`**: exports `walkAddressedPageComponents` and
consumes it from both sides; its changeset reads "(objectstack-ai#13218, ruled
2026-08-30)".
- **objectstack-ai#13109 to `8b236c826`**: its changeset says the extractor OMITTED
keys the resolver reads, and "This matches the second half". The line
now says the second half went live and this commit repaired it.
- **objectstack-ai#8460 to ADR-0029 D9.2a**: ruling C's first rung. The amendment
"D9.2a — AMENDMENT (2026-08-13)" records the option-A ruling: an
extender's scalar applies only while the fold's base still carries the
packaged owner's value. It also records that the mechanism is
deliberately the same comparison-based one the catalog uses one layer
up. The heading marker `[objectstack-ai#8460]` becomes `[ADR-0029 D9.2a]`.
- **objectstack-ai#10926 to `d173125fb`**: "Option A per the maintainer ruling on
objectstack-ai#10926 (2026-08-22): drop the key", the `submitLabel` retirement all
three lines describe.
- **objectstack-ai#12493 to `aa5994e17`**: adds `record_write_denied` and
`approval_recall_not_submitter` ahead of their emitters, with no
placeholders. Its diff wrote the four docblock lines. The catalog's
rendered strings are untouched, per hypothesis 5.
- **objectstack-ai#6604 to `d127ff002`**: "Per the maintainer's 2026-08-08 Option-B
ruling the kernel side takes the domain-specific name", matching
`KernelServiceMapSchema`.
- **objectstack-ai#17081 to `24d622b94`**: lands objectstack-ai#17556 as "Suggestion 1 of objectstack-ai#17081".
The line keeps objectstack-ai#17556 and names the parent card in words.
- **objectstack-ai#11333 to `e58ea8b38`**: declares `grantedPermissions`, described by
the commit itself as "the artifact-contract half of objectstack-ai#11333 option A /
the objectstack-ai#13457 batch ruling". The line keeps objectstack-ai#14865 and objectstack-ai#13457 and states
the ruled option in words.
- **objectstack-ai#12245 to `c41b42e8d`**: rewrote this docblock from "the per-surface
census (its os-dev-report comment, measured on origin/main @ e2debee)"
and carries the 1218-values measurement. The report comment lived on the
deleted card, so the commit is now the record, and the line says so.
- **objectstack-ai#12144 to `3a04b0125`**: wrote the storage-owned length-ceiling note
and its storage-column pin; its changeset heads "(objectstack-ai#12144)".
- **objectstack-ai#12194 / objectstack-ai#12176 to `311433f6b`** (:140-141): stage 1, the item-name
grammar declared and refused at the publish door; its message reads
"Stage 1 of objectstack-ai#12176". **objectstack-ai#12176 to `7986d973f`** (:189): "Retire
compound-name metadata addressing", stage 3 of the maintainer-ruled
retirement.
- **objectstack-ai#10485 to `35ad101bc`**: retires the `themes` carrier, `ThemeSchema`
and the `PLURAL_TO_SINGULAR` fold ("Ruled B"). Its own diff wrote all
four lines.
- **objectstack-ai#11350 to `ece4dad31`**: "Invariant recorded (maintainer ruling
2026-08-23)". It also points the premise-delta note at objectstack-ai#11709, which is
what `index.ts:148` now says. This is stage 1's wording for
`kernel/index.ts:53`.
- **objectstack-ai#14419 to `c5a7448d5`**: `create_record` surfaces the engine's
`DUPLICATE_RECORD` code and the engine binds it on `$error`, the
founding case the line names. Its message names objectstack-ai#14419 as the card it
lands.
- **objectstack-ai#13681 to `18d816a50`**: declares the run-level
`FlowRunSummary.failed`, the spec half of the contained-failure
contract.
- **objectstack-ai#16659 to `ecdfc9411`**: declares the start-node
`config.organization` key and "the one refusal sentence every
enforcement point says". Its sub-commits pin "the three objectstack-ai#16659
consequences", so it is also the commit that closed the defect the
second line describes.
- **objectstack-ai#8714 to `42b05af89`**: "ONE closed contributor-state enumeration",
maintainer-ruled 2026-08-18.
- **objectstack-ai#6640 to `2ab1257c9`**: `preserveAudit` is UPDATE-only (stage 1's
anchor for the same rule).
- **objectstack-ai#6345 to `e2798fab7`**: its own `turso.zod.ts` diff wrote both lines
("The maintainer's objectstack-ai#6345 ruling closes it…", "(objectstack-ai#6345 fork 2)"). The
wording is stage 3's in `config-registry.zod.ts`.
- **objectstack-ai#13031 to `b799ac553`**: adds `mapViewPayloads` to `walk.ts`, the
centralized walk the heading describes. Its message names objectstack-ai#13031 as the
card it lands.
- **objectstack-ai#8715, kept**: `2c86fe3ea` ("Maintainer ruling 2026-08-15
(disposition B: delete)"; its diff wrote :230) now sits on :231.

## Mechanical proof

- **Token guard** (my `tokcmp.mjs`: TypeScript 6.0.3 leaf tokens, JSDoc
kinds excluded, controls mutate the head text in memory only). Base
`c876a7426d` against the head, 21 files, 37,539 base tokens:
  - Real run: 0 files with a token change (exit 0).
  - Comment-insertion control (`ai/index.ts`): 0 (exit 0).
- Code-insertion positive control (`system/i18n-resolver.ts`): DIFFER at
token 14452 (exit 1).
- String positive control (a real `StringLiteral` in
`system/operation-message.ts`, found by the parser): DIFFER at token 5
(exit 1).
- The first string-control attempt matched a quoted fragment inside a
comment and did not fire. It was a vacuous control, not a measurement,
and the parser-located control above replaced it.
- **Line balance**: every file is +N/−N (66/66 across 21 files); every
line count is equal at base and head.
- **Tracker numbers**: added-not-removed is empty in every file, and no
`PR #N` is on an added line. Net-removed: 60 sites, 24 numbers.
- **Shas**: 24 distinct on added lines, 0 on removed lines.
  - `rev-parse --disambiguate` answers 1 object for each.
- `merge-base --is-ancestor` exits 0 against `origin/main` `e666636fd9`
and against the base.
- Each is single-parent; the repository is not shallow; the control leg
`e9584681a4` exits 0.
- Each commit's own message (17 of 24) or diff (all 24) names the number
it replaces.
- **Literal readers**: every string literal in the repository that
carries one of the 26 numbers was matched against the 22 files' text.
Three hits read these files:
  - `data/api-derivation.test.ts:236` splits on `[objectstack-ai#6259]`;
  - `packages/runtime/src/api-exposure.test.ts:152` splits on `objectstack-ai#6259`;
- `identity/api-key-retirement.test.ts:118` asserts `are NOT declared
here (objectstack-ai#8715`.
- Those lines are the two kept sites. No test or script matches any
rewritten line by pattern.

## Tests and gates (at head `9d63cb6548`)

- `pnpm exec turbo run build --concurrency=2 --filter=./packages/*
--filter=./packages/*/*` under `os-verify-lock`: Tasks 71 successful, 71
total, VERDICT command-exit 0.
- `pnpm --filter @objectstack/spec check:generated`: exit 1,
`check:docs` stale (1 of 15). `check:generated --fix` then regenerated
exactly that artifact: 2 pages, 3 lines, each its docblock line
verbatim.
  - `content/docs/references/automation/schedule-organization.mdx`
  - `content/docs/references/data/driver-turso.mdx`
  - The re-check in the gate run below is exit 0.
- `vitest run --maxWorkers=2` over the touched areas
(`src/api/rest-server.test.ts`,
`src/api/rest-api-config-dead-keys-retirement.test.ts`, `src/system`,
`src/shared`, `src/automation`, `src/ai`, `src/identity`,
`src/security`, `src/data/driver`, `src/conversions`,
`src/meta-spelling`): Test Files 159 passed (159), Tests 5163 passed
(5163).
- The 23 spec suites outside those areas that read a touched file's
source text or name it: Test Files 23 passed (23), Tests 540 passed
(540).
-
`scripts/{tombstoned-row-status,strictness-ledger,file-description,skill-map-guards,root-index,export-origins,category-title,split-entries,dist-freshness,dist-freshness-adoption,root-entry-type-nameability.pin}`
tests;
- `src/type-alias-convention.pin`,
`src/contracts/{automation-result-status.pin,automation-service,scoped-context}`,
`src/api/{export-job-family-retirement,api-entry-graph.pin}`,
`src/eager-entry-import`, `src/integration/connector-author-shape`,
`src/ui/{interaction-config-retirement,notification,strictness-batch14}`,
`src/migrations/migrations`.
- `scripts/build-schemas-check-mode.test.ts` is left to CI: it imports
rather than reads, and rebuilds schemas in a temp tree.
- `pnpm --filter @objectstack/spec typecheck`: exit 0;
`check:test-typecheck` OK (53 files / 251 errors / 138 pinned signatures
held).
- Lint, a proven narrowing: `eslint --no-inline-config --format json`
over the 21 touched `.ts` files gives 21 files, 0 errors, 0 warnings.
  - `isPathIgnored` is false for all 21, read through eslint's API.
- `eslint.config.mjs:327-328` says type-aware linting is never enabled,
so a comment edit cannot move an untouched file's verdict.
  - The repo-wide `pnpm lint` is CI's.
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands`: 108 families derived and run, every one exit 0. `--ran`
reads "108 derived, 108 run, 0 NOT-MEASURED, 0 UNRUN". Among them:
- `pnpm check:issue-citations` plus the live diff-scoped `node
scripts/check-issue-citations.mjs` judged 7 citations across 21 files, 7
resolve: the live numbers kept beside the anchors (objectstack-ai#9249, objectstack-ai#14954,
objectstack-ai#17556, objectstack-ai#14865, objectstack-ai#13457, and objectstack-ai#11709 twice).
- `pnpm check:doc-authoring`: 16,765 customer-facing strings across
1,175 spec sources clean; the sibling baseline holds.
- Changeset: `patch` for `@objectstack/spec`. 13 of the 21 touched
sources are `src/**/*.zod.ts`, which `files[]` ships verbatim, and the
rewritten docblocks reach `dist`. For example, `ruled collision
arbitration (commit 901355c)` is in 1 `.d.ts`, and the unchanged
neighbouring sentence in the same exported docblock (the positive
control) is in 1 `.d.ts`.
- Merge probe: a no-driver `merge-tree` of the head onto `origin/main`
`7a1faf1a5d`, from a bare shared clone, exits 0. The 3 commits `main`
gained since the base touch none of this diff's files. No merge was
made, as stages 1–4 did.
- No ablation or reverse verification: the change is comment-only, so
there is no behaviour to invert.

## Hypotheses (measured first)

1. **Holds.** The population is exactly the claim's 22 files: 62 dead
sites at the tip, equal per file to the census at `f11b5f20a2`.
2. **Holds, with nothing to respell.** No sibling-qualified pair occurs
among the 62 sites; no `pre-#N` spelling is dead here.
3. **Holds.** Re-read at 2026-09-29T06:23Z, after the last push and
before this PR was opened: all 14 open PRs' full file lists (1,116 files
in the version PR alone), and the newest `Claim:` on all 15
`pm:dispatched` cards. None names any of this PR's 24 paths. The five
exclusions stay excluded.
4. **Holds.** The two projected pages were regenerated by the generator,
never by hand. No other page under `content/docs/references/` carries
any of the 26 numbers.
5. **Holds.** No `#N` in the 22 files is inside a string; the two
literal-read sites stay as tokens. In `system/i18n-resolver.ts` and
`system/operation-message.ts` only docblock and line-comment lines
moved.

## Deviations

- The file surface is 21 of the 22 named files. `data/api-derivation.ts`
is untouched, because its one dead site is read by literal and its
commit already stands on the next line (stage 3's disposition).
- Six changed lines held no dead number. Each is the other half of a
rewritten sentence: `rest-server.zod.ts:756`, `identifiers.zod.ts:19`,
`index.ts:133`, `environment-artifact.zod.ts:137`,
`schedule-organization.zod.ts:82`, and `identity.zod.ts:231` (the commit
placed beside the kept :230).
- Commit trailers follow AGENTS.md's model-free pair (`Claude-Session`
plus `Co-authored-by: Claude`); the pre-push trailer check passed on
every push.

## Acceptance notes

**What stays for later stages.** At the tip `7a1faf1a5d` with this PR
applied, `packages/spec/src` holds **260** dead sites (34 numbers). This
is the gate's census on this head, with the four spec sources `main`
changed since the base re-extracted and re-probed at the tip. By area:
- `migrations/` **233**: objectstack-ai#20233 edits the same entry files; the
author-shown fields are its form D.
- `conversions/registry.ts` **12**: held by PRs objectstack-ai#20570 and objectstack-ai#20458.
- `stack.zod.ts` **9**: free now; PR objectstack-ai#20579 landed as `7a1faf1a5d` at
06:02Z, after the claim, so it stayed excluded here.
- `data/analytics.zod.ts` **3**: PR objectstack-ai#20458.
- `integration/connector.zod.ts` **1**: objectstack-ai#20287, PR objectstack-ai#20587.
- `data/api-derivation.ts:163` (objectstack-ai#6259) and
`identity/identity.zod.ts:230` (objectstack-ai#8715), **1** each: kept because
`api-derivation.test.ts:236`,
`packages/runtime/src/api-exposure.test.ts:152` and
`api-key-retirement.test.ts:118` read them by literal. Removing them is
a test-string change, form D, outside this card's comment-only scope.

**Carried from earlier stages, outside the gate's census** (which blanks
strings and defers test files): the dead-number test-title strings, the
two `why` strings, the `PROVENANCE_WAIVERS` reason, the two
`AGGREGATION_CASES` notes, and the `liveness/**` notes.

**Outside `packages/spec/src`** (objectstack-ai#20556's lane):
`packages/spec/scripts/check-entry-nameability.ts` cites objectstack-ai#11350 in its
header (:14) and PRINTS "recorded on objectstack-ai#11350" in its failure text (:727);
`packages/spec/scripts/root-entry-type-nameability.pin.test.ts` cites it
too. Commit `ece4dad31` is the anchor, already verified here.

**Rung.** Five of the anchored retirements also have ADR-0087 D3/D2
entries: `identity-api-key-schema-retired`,
`metadata-item-name-grammar-enforced`,
`rest-server-config-dead-keys-retired`, `stack-themes-carrier-retired`
and `translation-component-submit-label-retired`. This PR takes the
commit rung, as stages 1–5 did. The D3 id is the more durable in-repo
record if the ruling's first rung is later read to include those
entries.

**Wording, each true of its commit.**
- `dev-login.zod.ts:10` names objectstack-ai#17081 in words ("suggestion 1 of its
parent card").
- `environment-artifact.zod.ts:136-137` states objectstack-ai#11333's option A as "the
option the objectstack-ai#13457 batch ruling chose".
- `identifiers.zod.ts:18` drops "its `os-dev-report` comment is the
measurement of record", since that comment went with the card, and names
the commit as the record.

**Observation, not filed** (a pre-existing live citation, not a tracker
number; carrier: none): `environment-artifact.zod.ts:137` and
`packages/runtime/src/security/artifact-granted-permissions.ts:6` cite
"ADR-0025 §3.5 step 2" for the granted set. At the tip, §3.5's numbered
step 2 is "Compatibility" and "Permission consent" is step 3.

---
_Generated by [Claude
Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…data/analytics.zod.ts to the commits that decided them (stage 7) (objectstack-ai#20616)

Part of objectstack-ai#20234
Clause-②: no

Stage 7 of the staged sweep: `packages/spec/src/stack.zod.ts` and
`packages/spec/src/data/analytics.zod.ts`, both freed by landings (PR
objectstack-ai#20579 and PR objectstack-ai#20458). Its claim is `5885635758`. Every comment or
docblock line in those two files that cited a tracker number answering
404 now cites the commit on `main` that decided its rule, in ruling
C+D's form C, and says in its own words what was decided. Comments only:
12 lines out, 12 in, across 2 files. No code token, string literal or
`describe()` text moves. No dead site stays: none of the 12 is read by
literal.

The census is the gate's own `node scripts/check-issue-citations.mjs
--census --json`, filtered to the two paths. Before: base `0f6dcac5e9`,
board enumerated (185 pages, frontier objectstack-ai#20611). After: head `cc0580d404`,
board enumerated (185 pages, frontier objectstack-ai#20615).

## Measurement

| file (under `packages/spec/src/`) | dead before | after | numbers,
then anchor |
|---|---:|---:|---|
| `stack.zod.ts` | 9 | 0 | objectstack-ai#10485 ×2 (`:415`, `:1023`) to `35ad101bc`;
objectstack-ai#6238 (`:633`) to `c8d6f6e08`; objectstack-ai#14192 (`:1233`) to `4d0d9445a`; objectstack-ai#14686
×2 (`:3037`, `:3194`) to `279431e7a`; objectstack-ai#14662 ×3 (`:4510`, `:5070`,
`:5293`) to `35dffeace` |
| `data/analytics.zod.ts` | 3 | 0 | objectstack-ai#10194 ×3 (`:404`, `:407`, `:485`)
to `2306a765c` |
| **2 files** | **12** | **0** | 6 numbers removed, 6 distinct shas |

Per-file counts at base equal the claim's (9 and 3, from stage 6's
census). A second instrument agrees site for site: every `#N` in the two
files, classified by the TypeScript parser, and each of the 84 distinct
numbers of 100 or more probed by REST `issues/N` without following
redirects (the other 3 are the ordinals `Prime Directive objectstack-ai#12`, `batch
objectstack-ai#23`, `batch objectstack-ai#57`).
- Base: 244 sites, all in comments (0 strings, 0 code). 78 numbers
answer 200 and 6 answer 404: the same 6 numbers and the same 12 sites as
the gate.
- Its string-class positive control found 11 string sites in
`kernel/manifest-unknown-keys.test.ts` and
`packages/cli/src/utils/lower-callables.test.ts`.
- Head: 232 sites, 78 numbers, all 78 answer 200 (the same 78), none
answers 404.
- Lit controls objectstack-ai#16862, objectstack-ai#16847 and objectstack-ai#17698 answered 200 at every
checkpoint (3 at base, 3 at head); dead controls objectstack-ai#16714, objectstack-ai#16715 and
objectstack-ai#16697 answered 404 at every checkpoint.

## Why each anchor decides its line

Each sha resolves uniquely, is an ancestor of `origin/main` (and of the
base), and has one parent. No file under `docs/adr/**`,
`docs/NORTH-STAR.md` or `scripts/adr-anchors/` names any of the six
numbers or records these rules, so each takes the commit rung, as stages
1–6 did.

- **objectstack-ai#10485 to `35ad101bc`** (`:415`, `:1023`): retires the `themes`
carrier key and `ThemeSchema` under ADR-0049. Its message records the
ruling, "Ruled B (退役授权面, 2026-08-21)", and its own `stack.zod.ts` diff
wrote both lines. `:415` keeps ADR-0049 and the ruling in its words; the
D3 entry `stack-themes-carrier-retired` it names on `:423` is unchanged.
This is the anchor stages 1, 5 and 6 used for the same retirement.
- **objectstack-ai#6238 to `c8d6f6e08`** (`:633`): widens the array member of
`functions` so its `handler` also takes the lowered string ref, which is
the fix for `objectstack build` refusing its own array output. Its
message names objectstack-ai#6238, and its own diff wrote the line. objectstack-ai#4343 and objectstack-ai#4976 on
the same line stay (both 200).
- **objectstack-ai#14192 to `4d0d9445a`** (`:1233`): turns `ManifestSchema` and its
nested blocks into `strictObject` and flips the assembled-body strip pin
to a refusal pin; each of its sub-commits names objectstack-ai#14192. The line itself
was written later by `c78c9180de`, whose own message says "objectstack-ai#14192 closed
ManifestSchema with strictObject", so the commit that closed it is the
anchor.
- **objectstack-ai#14686 to `279431e7a`** (`:3037`, `:3194`): "defineStack refuses two
actions that resolve to one scope-qualified runtime key". Its subject
names objectstack-ai#14686, and its diff adds `collectDuplicateActionKeyErrors` and
the changeset for that refusal. Both lines were written later by
`773a99960a` (PR objectstack-ai#15022), whose message describes the same "same-key
rule, which runs before the merge".
- **objectstack-ai#14662 to `35dffeace`** (`:4510`, `:5070`, `:5293`): "composeStacks
refuses two stacks whose actions resolve to one scope-qualified runtime
key". It checks the composed set with the rule `defineStack` applies
within one stack, with no `actionConflict` option (maintainer ruling
2026-09-03). Its message does not name objectstack-ai#14662; its own `stack.zod.ts`
diff wrote all three `(objectstack-ai#14662)` lines.
- **objectstack-ai#10194 to `2306a765c`** (`analytics.zod.ts:404`, `:407`, `:485`):
binds `analytics_cube` (and `theme`) in `UNREGISTERED_KIND_SCHEMAS`, so
`PUT /meta/analytics_cube/:name` parses through `CubeSchema`, and gives
`CubeSchema` the `...MetadataProtectionFields` spread. Its message names
objectstack-ai#10194, and its own diff wrote all three lines. The `[objectstack-ai#10194]` markers
become `[commit 2306a76]`, the spelling stages 1 and 5 already use in
`kernel/metadata-type-schemas.ts`.

## Mechanical proof

- **Token guard** (my `tokcmp.mjs`: TypeScript 6.0.3 leaf tokens, JSDoc
kinds excluded, controls mutate the head text in memory only). Base
`0f6dcac5e9` against the head, 2 files, 17,249 base tokens:
  - Real run: 0 files with a token change (exit 0).
  - Comment-insertion control (`data/analytics.zod.ts`): 0 (exit 0).
- Code-insertion positive control (`stack.zod.ts`, a declaration
appended): DIFFER at token 15388 (exit 1).
- String positive control (the first `StringLiteral` the parser locates
in each file): DIFFER at token 5 (exit 1), once per file.
- `describe()` positive control (the first `.describe()` string argument
the parser locates: `stack.zod.ts:133`, `analytics.zod.ts:244`): DIFFER
at tokens 507 and 442 (exit 1).
- **Line balance**: `stack.zod.ts` +9/−9, `data/analytics.zod.ts` +3/−3;
line counts equal at base and head (5344 and 853).
- **Tracker numbers**: added-not-removed is empty in both files, and no
`PR #N` is on an added line. Net-removed: 12 sites, 6 numbers. The only
numbers on added lines are objectstack-ai#4343 and objectstack-ai#4976, which stay on `:633`.
- **Shas**: 6 distinct on added lines, 0 on removed lines.
  - `rev-parse --disambiguate` answers 1 object for each.
- `merge-base --is-ancestor` exits 0 for each, against `origin/main`
`7510663c87` and against the base; each is single-parent; the repository
is not shallow.
- **Literal readers**: all 26 string, template and regex literals in the
repository that carry one of the six numbers (42 code files) were
matched against the two files' base text: 0 occur there. Each removed
line was also cut into 4-word windows (96) and searched across the tree:
the 9 hits inside string literals are other files' own test titles
sharing a phrase ("the ADR-0010 protection envelope", "an assembled body
is"), and none reads either file. The source-text readers of the two
files read code, not these comments:
`compose-stacks-refusal-envelopes.test.ts` counts `throw new Error(`,
and `check-stack-collection-maps.mjs` and
`check-skill-top-level-keys.mjs` read the declared collections and keys.

## Tests and gates (at head `cc0580d404`)

- `pnpm exec turbo run build --concurrency=2 --filter=./packages/*
--filter=./packages/*/*` under `os-verify-lock`: Tasks 71 successful, 71
total, VERDICT command-exit 0.
- `pnpm --filter @objectstack/spec check:generated` under the lock: all
15 generated artifacts up to date, `check:docs` over
`content/docs/references/**` included; VERDICT command-exit 0. No
reference page projects any of the 12 lines, so none is regenerated.
- `vitest run --maxWorkers=2` under the lock over the two files' own
suites (`src/stack*`, `src/compose-stacks*`, `src/define-stack*`,
`src/assembled-package-body`, `src/data/analytics*`, `src/data/cube*`):
Test Files 35 passed (35), Tests 976 passed (976).
- The 37 spec suites that read source text across `src/`, or carry one
of these numbers, under the lock: Test Files 37 passed (37), Tests 759
passed (759).
-
`scripts/{category-title,dist-freshness,dist-freshness-adoption,file-description,strictness-ledger,strictness-ledger-doc,root-index,skill-map-guards,export-origins,split-entries,root-entry-type-nameability.pin}`,
`scripts/liveness/{evidence,tombstoned-row-status}`;
- `src/type-alias-convention.pin`, `src/eager-entry-import`,
`src/api/{api-entry-graph.pin,auth,export-job-family-retirement}`,
`src/ai/tool-confirmation-prescription-tense.pin`,
`src/data/{currency-mode-family-closure.pin,external-lookup-retirement}`,
`src/identity/position-delegatable-enforcer.pin`,
`src/integration/{connector-connection-timeout-retirement,connector-resilience-keys-retirement}`,
`src/security/rls-tags-retirement`,
`src/shared/{alias-integrity,retired-key-migrate-sentence}`,
`src/system/{compliance-families-retirement,constants/platform-object-names,email-template-floor-locale-parity.pin,message-queue-retirement}`,
`src/ui/{action-requires-confirmation-docblock.pin,i18n,interaction-config-retirement,strictness-batch14}`,
`src/kernel/{manifest-unknown-keys,metadata-type-schemas}`.
- Left to CI:
`scripts/{build-schemas-check-mode,def-key-collisions,openapi-self-consistency}`
(each rebuilds artifacts in a temp tree) and
`scripts/{check-generated-ledger,check-generated-fix-rebuild.pin}` (read
the ledger and `dist`). None reads comment text.
- `pnpm --filter @objectstack/spec typecheck` under the lock: exit 0;
`check:test-typecheck` OK (53 files / 251 errors / 138 pinned signatures
held).
- Lint, a proven narrowing: `eslint --no-inline-config --format json`
over the 2 files gives 2 files, 0 errors, 0 warnings.
  - `isPathIgnored` is false for both, read through eslint's API.
- `eslint.config.mjs:327-328` says type-aware linting is never enabled,
so a comment edit cannot move an untouched file's verdict.
  - The repo-wide `pnpm lint` is CI's.
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands`: 79 families derived and run, every one exit 0. `--ran`
reads "79 derived, 79 run, 0 NOT-MEASURED, 0 UNRUN". Among them:
- `pnpm check:issue-citations` (self-test, 114 cases in 8 batteries) and
the live diff-scoped `node scripts/check-issue-citations.mjs`: it judged
the 2 citations on added lines, objectstack-ai#4343 and objectstack-ai#4976, and both are live
issues.
- `pnpm check:doc-authoring`: 16,804 customer-facing strings across
1,179 spec sources clean; the sibling baseline holds.
- `pnpm check:stack-collection-maps`: 8 enumerations reconciled against
31 declared collections.
- Changeset: `patch` for `@objectstack/spec`. Both files are
`src/**/*.zod.ts`, which `files[]` ships verbatim, and the rewritten
docblocks reach `dist`: "posture: commit 4d0d944 closed" and "[commit
2306a76] This docblock used to say" are each in 2 `.d.ts`, their old
spellings in 0. Positive control: the unchanged neighbouring sentence
"BY INHERITANCE — an undeclared key on one is REFUSED" is in the same 2
`.d.ts`.
- Merge probe: a no-driver `merge-tree` of the head onto `origin/main`
`7510663c87`, from a bare shared clone, exits 0. The 3 commits `main`
gained since the base touch neither file nor the citation or derivation
scripts, and a re-derivation prints the same 79 commands. No merge was
made.
- No ablation or reverse verification: the change is comment-only, so
there is no behaviour to invert.

## Hypotheses (measured first)

1. **Holds.** 12 dead sites at the tip, 9 in `stack.zod.ts` and 3 in
`data/analytics.zod.ts`, equal per file to stage 6's census.
2. **Holds.** Read at 2026-09-29T07:36Z and again at 08:16Z, after the
last push and before this PR was opened: all open PRs' full file lists
(9 PRs, 166 files at the second read) and the newest `Claim:` on all 11
`pm:dispatched` cards. None names either file, except this card's own
claim.
3. **Holds, with nothing to keep.** All 12 sites are comments. No test
string, exported string or `describe()` text carries one, and no test or
script reads any of them by literal.
4. **Holds.** No generated reference page projects these lines;
`check:docs` is green with no regeneration.

## Deviations

- None to the file surface: the 12 claimed lines and one changeset, no
generated page needed.
- Commit trailers follow AGENTS.md's model-free pair (`Claude-Session`
plus `Co-authored-by: Claude`); the pre-push trailer check passed on
every push.

## Acceptance notes

**What stays for later stages.** The gate's census at this PR's head
(base `0f6dcac5e9` plus this PR) reads **248** dead sites (29 numbers)
in `packages/spec/src`. The only `packages/spec/src` change `main` has
made since the base (objectstack-ai#20610's migrations entry and registry) adds four
live numbers and removes none, so 248 also stands at the tip
`7510663c87` plus this PR:
- `migrations/` **233**: objectstack-ai#20233 edits the same entry files (PR objectstack-ai#20607
holds `migrations/registry.ts`).
- `conversions/registry.ts` **12**: PRs objectstack-ai#20570 and objectstack-ai#20587 hold it.
- `integration/connector.zod.ts` **1**: PR objectstack-ai#20587 (objectstack-ai#20287).
- `data/api-derivation.ts:163` (objectstack-ai#6259) and
`identity/identity.zod.ts:230` (objectstack-ai#8715), **1** each: kept because tests
read them by literal, so removing them is form D.

**Outside the gate's census: test files.** The gate defers `*.test.ts`.
The same six dead numbers still stand at 15 comment sites and 10
test-title strings in `packages/spec/src` test files:
- `data/analytics-strictness-batchd.test.ts:96` (comment, objectstack-ai#10194) and
its title `:93`. This file is in the `analytics*` set stages 3 and 4
excluded while PR objectstack-ai#20458 held it;
`analytics-date-range-two-bound-window.test.ts` and
`cube-member-inner-name-retirement.test.ts` were in that set too and are
not re-measured here.
- The package root: `compose-stacks-action-echo.test.ts:20`, `:34`,
`:200` (objectstack-ai#14686) and titles `:176`, `:224`;
`compose-stacks-action-key-collision.test.ts:3` (objectstack-ai#14662);
`stack-top-level-strict.test.ts:103` (objectstack-ai#10485) and title `:128`;
`type-alias-convention.pin.test.ts:257`, `:1572`, `:1937` (objectstack-ai#10485).
- `shared/`: `metadata-collection.test.ts:250`,
`metadata-url-spelling.test.ts:51`, `:72`, `:168` (objectstack-ai#10485), `:257`
(objectstack-ai#10194), title `:254`. `automation/sync-retirement.test.ts:207`
(objectstack-ai#10485).
- `kernel/`: `manifest-unknown-keys.test.ts`, four titles (objectstack-ai#14192);
`metadata-type-schemas.test.ts:422`, a title (objectstack-ai#10194).
- Stage 6 took the package root, `shared/` and `automation/` through the
gate's census, which never lists a test file, so test-file comment lines
there may carry other dead numbers as well. That wider population is not
measured here.

**Outside `packages/spec/src`.** The same six numbers stand at 44 more
sites
(`packages/{metadata-protocol,objectql,rest,runtime,cli,core,metadata,qa}`,
`examples/`, `scripts/`, `packages/spec/scripts/`), and at 19 sites in
`migrations/` (the objectstack-ai#20233 area).

**Rung.** The objectstack-ai#10485 retirement also has the ADR-0087 D3 entry
`stack-themes-carrier-retired`, which `:423` already names. This PR
takes the commit rung, as stages 1–6 did.

**Wording, each true of its commit.** `:3037` and `:3194` now read
"commit 279431e's same-key refusal": the refusal that commit added, in
lines `773a99960a` wrote. `:1233` reads "commit 4d0d944 closed
`ManifestSchema`", in a line `c78c9180de` wrote.

---
_Generated by [Claude
Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
… families states each lesson in words, not tracker numbers (stage 9) (objectstack-ai#20630)

Part of objectstack-ai#20233
Stage 9: the remaining semantic-entry families, meaning every ADR-0087
semantic entry that still cited a tracker number.

Clause-②: no

`os migrate meta` prints each ADR-0087 semantic entry's `surface`,
`replacement`, `reason` and `acceptanceCriteria` to the author. This
stage covers the 44 entries that still cited a tracker, pull-request,
decision-batch or cross-repository number: 39 with a four- or five-digit
id, and 5 with a decision-batch number only. In each of them, every
sentence now says what the cited ruling, measurement or fix decided
(form D). ADR ids stay, and so do the contributor-guide rule references,
which are not tracker ids. The
`address-location-value-unknown-keys-refused` replacement cited
`AGENTS.md #0.1`, which names nothing in AGENTS.md today. It now states
the rule it copied from the 2026-09-01 ruling: a consumer-side alias for
an off-spec key stays forbidden.

- **Text only:** a base-vs-head AST comparison over the 45 changed entry
files and `registry.ts` finds no change outside `replacement` / `reason`
/ `acceptanceCriteria`. That is 48 prose fields per copy, and no
`surface`, id, comment, import or token-skeleton change.
- **Census (AST instrument, whole tree):** base `682873f201` has 76
prose sites in 39 entries (replacement 3 / reason 73 /
acceptanceCriteria 0), 0 surface sites and 28 short numbers. Head has 0
/ 0 / 10. The 10 short numbers left are contributor-guide rule
references.
- **Pin:** `packages/cli/test/migrate-meta-engine-guidance.test.ts` now
holds every semantic entry instead of a prefix list, so an entry added
later in any family is held on arrival. `REWRITTEN` goes from 203 to
247. Ablation: putting one removed id back into the `turso-` entry (a
family the pin did not cover before) turned the pin red; restoring it
turned the pin green.
- **Generated:** `registry.ts`, `spec-changes.json` and
`docs/protocol-upgrade-guide.md`, by their generators. `patch`
changeset.

## Verification (head `96b994e472`)

- **Tests:**
  - spec `--project local`: 575/575 files (16917 passed, 1 todo).
  - spec `--project repo`: 42/42 files (745 passed).
  - CLI `--project unit`: 234/234 files (3342 passed).
- CLI `--project integration`, the three migrate-meta files: 3/3 (13
passed, 1 skipped by the default-range file's own `skipIf`).
  - spec and CLI typecheck: exit 0, test-layer debt held.
- **Gates:** `dispatch-gates.mjs --commands` derives 89 families. 88
exit 0, among them `check:doc-authoring`, `check:generated`,
`check:migration-registry`, `check:spec-changes`, `check:upgrade-guide`,
`check:issue-citations`, `check:nul-bytes`, `check:api-surface` and
`check:authorable-surface`.
- `check:dual-build-cjs-loads` is NOT MEASURED: its prerequisite is not
met, because packages outside the CLI closure have no `dist/`. Build
Core runs it.
  - `--ran` reconciliation: 89 accounted, 88 run, 1 NOT MEASURED.
- **Lint:** eslint over the 47 changed source files: 0 errors, 0
warnings.
- **Mergeability:** a driver-free `merge-tree` onto `origin/main`
`b80ab579d8` is clean. Main's new commits touch none of these paths.

The census method and controls, the source of every citation, and the
ablation record are in the dev report on objectstack-ai#20233.

## Acceptance notes

- Four verbatim ruling quotes carried a card or batch number: 「217 同意」,
「9266 同意 A」, 「146 同意」, and one reply that also answered another card.
Each now keeps only its operative words, or states the decision instead.
- The claim fenced off four entries. Two of them are not on `main`: they
arrive with PR objectstack-ai#20570 and PR objectstack-ai#20587. The two on `main` carry no site. PR
objectstack-ai#20570's incoming `filter-is-empty-lowers-to-empty-operator` carries six
tracker ids in `reason`, and the `filter-` family was already held by
this pin before this stage.
- `ui-list-view-groupbyfield-padded-refused` and
`ui-list-view-grouping-field-padded-refused` still cite `AGENTS.md #0.1`
in `reason`. That is the same dangling number the `address-` entry had.
Both entries are outside this stage's claim and are untouched.
- `ui-notification-action-embed-config-retired` names "its ui/ batch 14"
of the strictness sweep, a batch number without `#`. It is outside this
stage and untouched.
- Comment and docblock lines of the entries still cite tracker ids: 127
lines in `semantic/`, 839 across `entries/`. They are form C's, under
objectstack-ai#20234, and are untouched.

---
_Generated by [Claude
Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
… to the commits that decided them (objectstack-ai#20632)

Part of objectstack-ai#20594
Clause-②: no

## What changed

This is stage 2 of the `domain:cli` lane of the dead-citation sweep:
`packages/rest/src/**`. Every comment or docblock site in scope that
cited a tracker number answering 404 now cites, in ruling C+D's form C
(comment 5749154545 on objectstack-ai#19123), the commit in this repository's history
that decided what the line describes, and says in its own words what
that commit decided. PR objectstack-ai#20533 is the method and PR objectstack-ai#20624 (stage 1,
`packages/runtime`) the precedent this follows line for line. Later
stages cover `cli`, `types` and the rest of the lane, so this PR says
`Part of` and the card stays open.

That is **457 comment sites on 445 lines in 85 files, covering 74
numbers**: the census's 191 sites, 256 more in test comments (which the
census defers), and 10 sites whose dead number is the second half of a
slash-joined pair the citation grammar does not read (`objectstack-ai#3984/objectstack-ai#6241`,
`objectstack-ai#9901/objectstack-ai#10255` four times, `objectstack-ai#10993/objectstack-ai#11235/objectstack-ai#11292`, `objectstack-ai#11235/objectstack-ai#11242`
twice, `objectstack-ai#10993/objectstack-ai#11242`, `objectstack-ai#7543/objectstack-ai#15071`). Each rewritten line cites one
of **70 distinct commits**.

ADR-0076 D11 is the only ADR that records any of these numbers, and it
records objectstack-ai#8850 only as the extraction it names as landed in `8664a2c99`,
so that commit is the anchor there. No other ADR or ruling-record file
in `docs/adr/` or `scripts/adr-anchors/` records the decision behind any
of these numbers, so every anchor is a commit. The anchors the landed
stages already gave the same numbers are reused where the rest sites
describe the same decision (30 numbers, for example `79c46da90` for
objectstack-ai#9934, `7986d973f` / `311433f6b` for the compound-name retirement,
`6a180e42d` for objectstack-ai#13279 and `cf6e0a193` for objectstack-ai#15071), so each number
carries one anchor across the tree.

Only comments changed. Every touched file keeps its line count (451
lines out, 451 in, over 85 files), so no line citation into these files
moves. Six of the 451 lines held no dead site; each is the other half of
a sentence that had to change:
- `discovery-schema-conformance.test.ts:343` (「(reaffirmed by」 to
「(which commits」, because line 344 now names the two commits that landed
the ruling),
- `package-door-16019-raw-statement-fault-code.test.ts:51` and
`error-response.ts:1485` (a trailing 「PR」 whose number wrapped onto the
next line),
- `error-response-structured-arm-door-parity.test.ts:463` (「That card
added the limb」 to 「That commit」, because line 459's tag now names the
commit),
- `rest-hook-script-fault-envelope.test.ts:331` (「both sides of that
card」 to 「that fix」),
- `rest-server.ts:908` (「(objectstack-ai#14409, landed」 to 「(landed as commit」, the
sha `3ecb7dc1a` already standing on line 909).

**No citation number is added.** Every tracker number on an added line
was already on the line it replaces. No PR number stands on an added
line. One of the 70 shas is on a removed line, and it was there before:
`rest-14078-invalid-date-total-arm.test.ts:19` read 「PR objectstack-ai#14409 (landed
`3ecb7dc1a`)」 and now reads 「Commit 3ecb7dc drove」. No code token
moves (see the guard below).

Three dead comment sites are left on purpose, listed under "The sites
left". One more file: a `patch` changeset for `@objectstack/rest`,
because the rewritten docblocks ship (see Changeset below).

## Census: `packages/rest`, before and after

**Instrument.** The gate's own `node scripts/check-issue-citations.mjs
--census --json`, read-only and unchanged, run with the fleet token. Its
surface is comment prose in `packages/**/src/**/*.ts` with string
literals blanked, and it defers `*.test.ts`. The count is its
`allocated-but-absent` findings under `packages/rest/`. Both runs
enumerated the whole board (185 pages), so neither read a truncated
board.

| reading | tree | board | whole-repo `allocated-but-absent` | rest
sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `a186aea996`, run 2026-09-29T10:28:18Z to 10:36:06Z |
enumerated, 185 pages, frontier objectstack-ai#20628, 18,455 numbers | 2,015 | **191**
| 186 | 14 | 51 |
| after | head `93e4d69ba6`, run 11:11:30Z to 11:17:37Z | enumerated,
185 pages, frontier objectstack-ai#20630, 18,457 numbers | 1,764 | **0** | 0 | 0 | 0 |

The before count equals the card's 191 at `f11b5f20a2`. The whole-repo
drop is 251: this diff's 191, plus the 60 of PR objectstack-ai#20626
(`packages/plugins/plugin-sharing`, 63 to 3), which landed on `main` in
between and came in with the merge. No other package moved.

**Supplementary instrument, the whole scope.** The census does not read
test files or strings, and this stage's scope includes test comments. So
a second reading runs the gate's own exported `extractCitations`
(whole-file and comment-prose projections) and `classifyCitation` over
every `.ts` file under `packages/rest/src` (256 files), against the
board enumerated through the gate's own `enumerateBoard`. The lit
controls objectstack-ai#20594, objectstack-ai#19123 and objectstack-ai#20624 answered 200 and are on both boards;
the dead controls objectstack-ai#13214, objectstack-ai#14541 and objectstack-ai#15071 answered 404 and are on
neither.

| reading | tree | board | citations | dead | src comment | test comment
| src string | test string |
|---|---|---|---|---|---|---|---|---|
| before, 10:29Z | `a186aea996` | 185 pages, frontier objectstack-ai#20628 | 4,620 |
**577** | 191 | 259 | 1 | 126 |
| after, 11:21Z | `93e4d69ba6` | 185 pages, frontier objectstack-ai#20631 | 4,174 |
**130** | 0 | 3 | 1 | 126 |

Its src-comment column equals the census's 191 and 0, which is the
control on the second instrument, and a site-by-site comparison of the
two before-readings is identical. Resolving comment citations move by
one (1,364 to 1,365 in src): `(objectstack-ai#10993/objectstack-ai#11235/objectstack-ai#11292)` became `(objectstack-ai#10993,
commit 376c70f, objectstack-ai#11292)`, so the grammar now reads the live `objectstack-ai#11292`
that the slash hid. The drop is 447 grammar-read sites; the other 10
rewritten sites are the slash-joined ones the grammar never read.

Separately, every one of the 77 numbers was probed on its web endpoint:
76 answer 404 (deleted) and one, #14026, answers 302 to
objectstack-ai/objectui#10102 (transferred), which is why it is left
(see below).

## Per-number table

Sites and files are the dead comment sites in scope at the base, tests
and slash-joined halves included. `left` is a site with no deciding
commit (see below). `strings kept` counts string-literal sites, which
are tokens and stay as they were. Every anchor was read in its message
or its diff, not only in its subject: it is the commit that made the
change the line describes, and its own message or diff names the number
it replaces or adds the citation the line carries.

| number | comment sites / files | rewritten | left | strings kept |
anchor |
|---|---|---|---|---|---|
| `objectstack-ai#6037` | 5/3 | 5 | 0 | 0 | `18189983d` |
| `objectstack-ai#6122` | 2/2 | 2 | 0 | 0 | `64cd01082` |
| `objectstack-ai#6206` | 1/1 | 1 | 0 | 0 | `8e13ca876` |
| `objectstack-ai#6216` | 6/2 | 6 | 0 | 2 | `f586f1a89` |
| `objectstack-ai#6241` | 10/3 (1 slash-joined) | 10 | 0 | 1 | `83a3b1f2e` |
| `objectstack-ai#6259` | 2/1 | 2 | 0 | 0 | `6968885ef` |
| `objectstack-ai#6303` | 1/1 | 1 | 0 | 0 | `465c5fc14` |
| `objectstack-ai#6306` | 9/5 | 9 | 0 | 3 | `fec784863` |
| `objectstack-ai#6307` | 4/2 | 4 | 0 | 0 | `293476148` |
| `objectstack-ai#6349` | 4/2 | 4 | 0 | 4 | `2443bb4c4` |
| `objectstack-ai#6474` | 1/1 | 1 | 0 | 0 | `18189983d` |
| `objectstack-ai#6535` | 3/2 | 3 | 0 | 0 | `a92b1793c` |
| `objectstack-ai#6640` | 1/1 | 1 | 0 | 1 | `2ab1257c9` |
| `objectstack-ai#6704` | 5/1 | 5 | 0 | 1 | `c3f491626` |
| `objectstack-ai#8641` | 1/1 | 0 | 1 | 0 | — |
| `objectstack-ai#8850` | 3/3 | 3 | 0 | 0 | `8664a2c99` |
| `objectstack-ai#8885` | 6/3 | 6 | 0 | 3 | `30b1c636a` |
| `objectstack-ai#8919` | 7/3 | 7 | 0 | 7 | `b5378550e` |
| `objectstack-ai#9741` | 12/1 | 12 | 0 | 0 | `2a29caa53` |
| `objectstack-ai#9805` | 1/1 | 1 | 0 | 0 | `45862a53d` |
| `objectstack-ai#9934` | 19/10 | 19 | 0 | 4 | `79c46da90` |
| `objectstack-ai#9967` | 2/2 | 2 | 0 | 4 | `8f266f1cd` |
| `objectstack-ai#10063` | 2/2 | 2 | 0 | 1 | `9e04c3e35` |
| `objectstack-ai#10178` | 1/1 | 1 | 0 | 0 | `38cf397ea` |
| `objectstack-ai#10179` | 0/0 | 0 | 0 | 1 |  |
| `objectstack-ai#10255` | 18/4 (4 slash-joined) | 18 | 0 | 2 | `6ce58a735` |
| `objectstack-ai#10340` | 13/3 | 13 | 0 | 2 | `26f3588fb` |
| `objectstack-ai#10345` | 13/6 | 13 | 0 | 6 | `cad8b42f0` |
| `objectstack-ai#10350` | 1/1 | 1 | 0 | 0 | `490879ad0` |
| `objectstack-ai#10485` | 2/1 | 2 | 0 | 1 | `35ad101bc` |
| `objectstack-ai#10537` | 9/3 | 9 | 0 | 1 | `e634ecf6a` |
| `objectstack-ai#10888` | 2/2 | 2 | 0 | 0 | `d806081dd` |
| `objectstack-ai#11006` | 3/1 | 3 | 0 | 0 | `cccbe51bf` |
| `objectstack-ai#11130` | 1/1 | 1 | 0 | 0 | `851909530` |
| `objectstack-ai#11235` | 4/2 (1 slash-joined) | 4 | 0 | 0 | `376c70f98` |
| `objectstack-ai#11242` | 3/2 (3 slash-joined) | 3 | 0 | 0 | `98ea3443f` |
| `objectstack-ai#12144` | 1/1 | 1 | 0 | 0 | `3a04b0125` |
| `objectstack-ai#12176` | 11/7 | 11 | 0 | 2 | `7986d973f` |
| `objectstack-ai#12194` | 15/5 | 15 | 0 | 4 | `311433f6b` |
| `objectstack-ai#12195` | 35/16 | 35 | 0 | 7 | `7986d973f` |
| `objectstack-ai#13182` | 2/2 | 2 | 0 | 0 | `5b3ff63cc` |
| `objectstack-ai#13197` | 1/1 | 1 | 0 | 0 | `56c093c4d` |
| `objectstack-ai#13213` | 2/1 | 2 | 0 | 0 | `4801296e7` |
| `objectstack-ai#13214` | 18/6 | 18 | 0 | 14 | `cc837dbfe`, `889ec5b42`, `3d10755f0`
|
| `objectstack-ai#13244` | 5/2 | 5 | 0 | 1 | `889ec5b42` |
| `objectstack-ai#13255` | 4/1 | 4 | 0 | 6 | `43028a8f8` |
| `objectstack-ai#13258` | 1/1 | 1 | 0 | 0 | `3d10755f0` |
| `objectstack-ai#13279` | 23/5 | 23 | 0 | 5 | `6a180e42d` |
| `objectstack-ai#13280` | 13/4 | 13 | 0 | 2 | `add6a1b1c` |
| `objectstack-ai#13282` | 1/1 | 1 | 0 | 0 | `43028a8f8` |
| `objectstack-ai#13377` | 3/2 | 3 | 0 | 0 | `e10cf3444` |
| `objectstack-ai#13378` | 2/1 | 2 | 0 | 0 | `82faea03f` |
| `objectstack-ai#13454` | 1/1 | 1 | 0 | 0 | `7ad57e17a` |
| `#14026` | 1/1 | 0 | 1 | 0 | — |
| `objectstack-ai#14365` | 1/1 | 0 | 1 | 0 | — |
| `objectstack-ai#14366` | 14/4 | 14 | 0 | 2 | `53cbad9f7` |
| `objectstack-ai#14369` | 3/2 | 3 | 0 | 0 | `a3d5724c8`, `53cbad9f7` |
| `objectstack-ai#14389` | 7/3 | 7 | 0 | 7 | `10220a7bf` |
| `objectstack-ai#14390` | 1/1 | 1 | 0 | 0 | `9d7f7259f` |
| `objectstack-ai#14409` | 2/2 | 2 | 0 | 0 | `3ecb7dc1a` |
| `objectstack-ai#14541` | 27/4 | 27 | 0 | 5 | `6d178a408` |
| `objectstack-ai#14613` | 2/2 | 2 | 0 | 0 | `81208086a` |
| `objectstack-ai#14677` | 1/1 | 1 | 0 | 0 | `a4e4d2d78` |
| `objectstack-ai#14683` | 8/2 | 8 | 0 | 0 | `96326040f` |
| `objectstack-ai#14691` | 15/2 | 15 | 0 | 2 | `b3a63d32c` |
| `objectstack-ai#14704` | 9/3 | 9 | 0 | 2 | `1c7adc73d` |
| `objectstack-ai#14723` | 7/4 | 7 | 0 | 4 | `65846bc46` |
| `objectstack-ai#14725` | 3/3 | 3 | 0 | 2 | `f5cc78b63` |
| `objectstack-ai#14849` | 3/1 | 3 | 0 | 0 | `226e72443` |
| `objectstack-ai#14907` | 1/1 | 1 | 0 | 0 | `e1d4f9e3f` |
| `objectstack-ai#14908` | 1/1 | 1 | 0 | 0 | `d5cbb44f3` |
| `objectstack-ai#15021` | 2/1 | 2 | 0 | 8 | `cc238db8b` |
| `objectstack-ai#15034` | 6/2 | 6 | 0 | 0 | `abf9101f1` |
| `objectstack-ai#15065` | 1/1 | 1 | 0 | 0 | `1c7adc73d` |
| `objectstack-ai#15071` | 23/4 (1 slash-joined) | 23 | 0 | 3 | `cf6e0a193` |
| `objectstack-ai#16650` | 1/1 | 1 | 0 | 0 | `001a83b04` |
| `objectstack-ai#17058` | 3/1 | 3 | 0 | 4 | `94c930248` |
| `objectstack-ai#18546` | 3/2 | 3 | 0 | 3 | `58f60e37e` |
| **total** | **460** | **457** | **3** | **127** | **70 distinct
commits** |

Every cited sha matches exactly one object (`git rev-parse
--disambiguate`, count 1 for each of the 70), is a commit, has one
parent, and is an ancestor of the base (`merge-base --is-ancestor`, exit
0 for all 70). The checkout is not shallow (`--is-shallow-repository`
false); the control leg `13a6cb4ad` exits 0 and the negative control
(this branch's first WIP commit, not on `main`) exits 1. Several numbers
are the PR number of their own anchor commit (objectstack-ai#6122, objectstack-ai#6303, objectstack-ai#6474,
objectstack-ai#11242, objectstack-ai#13213, objectstack-ai#13244, objectstack-ai#13258, objectstack-ai#13282, objectstack-ai#14409, objectstack-ai#14677, objectstack-ai#14908, objectstack-ai#15065,
objectstack-ai#16650), so the sha is the same object the number named.

**Numbers with more than one anchor, by site:**
- `objectstack-ai#13214` (18 sites) was one card with three commits. `cc837dbfe` (the
ownership gate, the 2026-08-30 ruling) for the 11 sites that describe
the gate; `889ec5b42` for the 5 in
`ui-view-route-identity.measurement.test.ts`, the identity measurement
it created; `3d10755f0` for the tenancy file's header, the measurement
it created; and `rest-server.ts:2247`, 「Driven and reported on objectstack-ai#13214
(PRs objectstack-ai#13244, objectstack-ai#13258)」, now reads 「Measured in commits 889ec5b
(identity) and 3d10755 (tenancy)」: those PRs are exactly those two
commits.
- `objectstack-ai#14369` (3 sites): `a3d5724c8` (the liveness census it recorded) for
`rest-server.ts:1172` and `rest-sub-config-parse-not-cast.test.ts:48`.
`rest-server.ts:4092` said the zero read sites of `api.documentation` /
`api.responseFormat` came from 「the objectstack-ai#14369 census」, but `a3d5724c8`
explicitly left `api` out of that census; the zero was measured by
`53cbad9f7` (its changeset: no other read site for either key), which is
the anchor there.
- `objectstack-ai#11235` / `objectstack-ai#11242` / `objectstack-ai#10993`: `376c70f98` derives the discovery
`version` in metadata-protocol (objectstack-ai#11235), and `98ea3443f` is objectstack-ai#11242's own
squash, which landed the objectstack-ai#10993 ruling on `/health` and the dispatcher's
`/discovery`. So 「the objectstack-ai#10993 ruling … reaffirmed by objectstack-ai#11235/objectstack-ai#11242」 now
reads 「the objectstack-ai#10993 ruling, landed by commits 98ea344 and 376c70f」
(`rest-server.ts:4528`, `discovery-schema-conformance.test.ts:343-344`).
`objectstack-ai#10993`, `objectstack-ai#11292` and `objectstack-ai#11297` answer 200 and stay.
- `objectstack-ai#6037` / `objectstack-ai#6474`: one commit, `18189983d` (objectstack-ai#6474 is its PR number),
so 「(objectstack-ai#6037 / PR objectstack-ai#6474)」 became 「(commit 1818998)」.

**Wordings to check, each true of its commit:**
- A commit does not rule. Where a line said a number ruled, it now says
what the commit did with the ruling: 「the ruling commit 79c46da landed
says it does」, 「the ruling commit cf6e0a1 implemented fences it」, 「the
ruling commit 10220a7 implemented」, 「the 2026-08-20 ruling, landed as
commit 6ce58a7」, 「recorded in commit 6ce58a7's message (option A)」
(its message reads 「Ruled on objectstack-ai#10255 (2026-08-20, option A)」), and
「question was ruled on 2026-08-20 and landed as commit 6ce58a7」 where
the line said 「filed as objectstack-ai#10255」.
- `objectstack-ai#14541`'s contract review: 「the objectstack-ai#14541 contract review (condition N)」
now reads 「the contract review of commit 6d178a4 (condition N)」; that
commit's message lists the conditions it carries. 「objectstack-ai#14541's §4」 and
「objectstack-ai#14541 §5」 in
`error-response-generic-passthrough-object-parity.test.ts` are sections
of `error-response-structured-arm-door-parity.test.ts` (the file
`6d178a408` created), so they now name that file. 「measured on the
objectstack-ai#14541 branch」 reads 「on the branch that landed as commit 6d178a4」.
- A line that named a DEFECT by its number now says so: 「Before commit
9e04c3e the draft→active promotion door could not…」, 「Before commit
26f3588 the `/meta` doors decided ORGANIZATION SCOPE from the RAW
url」, 「the defect commit 2443bb4 fixed」 and 「would be the defect
commit 26f3588 fixed」.
- `objectstack-ai#13255`: 「As written for objectstack-ai#13255 this file repaired nothing」 reads 「As
first written (commit 43028a8)」, the commit that created the file and
answered the measurement; 「CONTEXT-LOST family (objectstack-ai#13255), still unruled」
reads 「first measured by commit 43028a8」 (the ruling on that family
never landed, which the line still says).
- `objectstack-ai#13214` in the identity file: 「the half objectstack-ai#13214 marks UNMEASURED」
reads 「the half left UNMEASURED until commit 889ec5b」, and 「objectstack-ai#13214
asks for an INDEPENDENT reproduction」 reads 「commit 889ec5b is an
INDEPENDENT reproduction」.
- 「the objectstack-ai#8885 sweep」 reads 「the sweep behind commit 30b1c63」, the
commit that registered the 9 codes the sweep found; 「objectstack-ai#14849 predicted」
reads 「The card behind commit 226e724 predicted」; 「the hazard objectstack-ai#13377
names」 reads 「the hazard commit e10cf34 was written to remove」; 「The
concrete harm objectstack-ai#6704 names」 reads 「removed」.
- Quoted ruling: `error-response-sandbox-arm-message.test.ts:340` sits
inside a verbatim ruling quote, so the commit stands in an editorial
bracket (「not from [commit 1c7adc7]'s list」), as PR objectstack-ai#20624 did.
- Two markdown tables in comments
(`meta-state-route-engine-outage.test.ts:76`,
`objectql-slot-consumer-census.test.ts:43`): the rewritten cell is wider
than its column, and its padding is reduced rather than widening the
four sibling rows.

## The sites left

**No deciding commit (3 sites, all in test files, so the census does not
see them):**
- `meta-object-owd-gate.test.ts:516` (objectstack-ai#8641): 「whether it should stay is
objectstack-ai#8641's question」, an open decision. The commit that added the citation
calls it a pointer to the open decision card, and no commit decides it.
- `rest-sub-config-parse-not-cast.test.ts:321` (objectstack-ai#14365): the
`z.partialRecord` question 「deferred to objectstack-ai#14365」 was never taken (`git
log -S partialRecord`); `b3a63d32c` made it moot by retiring the record,
which the other half of the same line now cites.
- `import-integration.test.ts:1043` (#14026): not deleted, TRANSFERRED.
The web endpoint answers 302 to objectstack-ai/objectui#10102, the REST
read follows the redirect, and the board enumeration does not list it,
so the census and the supplementary reading both class it
`allocated-but-absent`. The line says how an issue was raised; no commit
decides that, so form C has nothing to cite.

**String sites kept as tokens (127).** 126 are test titles and test-code
strings in 41 files. One is a non-test string: the `note` field of the
REST route ledger's `GET /api/v1/meta/object/:name/state/:field` row at
`rest-route-ledger.ts:290`, which ends 「(objectstack-ai#10179)」 (see Acceptance
notes).

## Mechanical guard: no code token moves

The check compares the TypeScript parser's leaf tokens (TypeScript
6.0.3, JSDoc nodes excluded, comments being trivia) of each touched file
at base `a186aea996` against the working tree at `93e4d69ba6`, over all
85 touched `.ts` files. Controls mutate the head text in memory only, so
nothing on disk moved for them.

- Real run: 272,653 base tokens, **0 files with a token change** (exit
0).
- Comment-insertion control (`error-response.ts`): 0 files changed (exit
0).
- Code-insertion positive control (a declaration in the same file):
DIFFER at token 0 (exit 1).
- String positive control (the first string literal past offset 2000 of
the same file, one character added inside it): DIFFER at token 26 (exit
1).

Line balance: every touched file is +N/−N (451/451), and every line
count is equal at base and head. A raw scan of the 86 changed files for
control bytes finds none (its positive control on a scratch file with a
U+0001 byte matches).

## Changeset

This change ships bytes, so a `patch` changeset for `@objectstack/rest`
is included, in PR objectstack-ai#20624's form and level. It says only that the
provenance comments were re-anchored.

Measured on the built package: `files[]` is `dist`, `README.md` and
`CHANGELOG.md`. After `pnpm --filter @objectstack/rest build`, the
rewritten docblocks reach `dist`: for example `53cbad9f7` appears 4
times in `dist/index.d.ts`, and `26f3588fb` 8 times and `b3a63d32c` 5
times in `dist/index.js`. The positive control, the unchanged sentence
「It was VALIDATE-ONLY from objectstack-ai#11637」 of the same `rest-server.ts` docblock
whose first line now reads 「[commit 53cbad9] The parsed output is
CONSUMED」, is in `dist/index.d.ts` beside it; a negative control phrase
appears nowhere.

## Gates (head `93e4d69ba6`)

This host has no `flock`, so `os-verify-lock.sh` ran in its declared
unlocked mode. Its disclosure, verbatim, from each locked run at this
head:

```text
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 47s · declare it in the PR body · pnpm --filter '@objectstack/rest...' build
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 102s (1m42s) · declare it in the PR body · pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=4
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 76s (1m16s) · declare it in the PR body · pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 2s · declare it in the PR body · pnpm --filter @objectstack/rest exec vitest run --project repo --maxWorkers=2
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 9s · declare it in the PR body · pnpm --filter @objectstack/rest typecheck
```

The branch merged `origin/main` once (`93e4d69ba6`, merging
`542670da6d`) before these runs, as the dispatch orders; `origin/main`
has not moved since (read at 11:19Z). The merge brought PR objectstack-ai#20626 and PR
objectstack-ai#20587 and touched none of this diff's files. The dependency closure was
built first (`pnpm --filter '@objectstack/rest...' build`, 26 packages),
then the whole workspace (`turbo run build --filter='./packages/*'
--filter='./packages/*/*'`, 71 tasks, 71 successful).

- **Tests:** `vitest run --project local`: 227 files, 4,382 tests
passed, 50 skipped. `--project repo` (which holds the touched
`meta-state-route-doc-spelling.test.ts`): 1 file, 8 tests passed.
Together they are all 228 test files of the package, so every touched
test file ran.
- **Typecheck:** `pnpm --filter @objectstack/rest typecheck` exits 0.
`tsc --listFiles` counts 28 `src` files (no tests) under `tsconfig.json`
and all 228 test files under `tsconfig.test.json`, which
`check:test-typecheck` judges: 0 files, 0 errors, 0 pinned signatures in
the ledger.
- **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`)
exits 0 at `93e4d69ba6` (2026-09-29T11:19:30Z to 11:20:00Z). Not
narrowed.
- **Citation judging:** `node scripts/check-issue-citations.mjs --base
origin/main` exits 0: 19 citations judged across 14 files (18 resolve, 1
resolves as a pull request). These are the live numbers that stay on
rewritten lines. It defers `*.test.ts`, so the added-minus-removed count
over the whole diff covers the rest: 0 numbers added.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` at `93e4d69ba6` derived 68
families. All 68 exit 0, and `--ran` over a record carrying each exit
code reads 「68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN」 (a derived
zero).
- `check:dual-build-cjs-loads` and `check:type-check-debt` first exited
3 (PREREQUISITE NOT MET, nothing measured) on the closure-only build;
after the whole-workspace build both exited 0.
- Among them: `check:doc-authoring`, `check:nul-bytes`,
`check:rest-log-declared`, `check:route-envelope`,
`check:system-context-census` (106 elevation read sites, the page's 102
symbols held) and `check:issue-citations` (self-test).
- **Artifact rosters:** 33 of the 36 non-self-test roster rows exit 0 at
`93e4d69ba6`, `check-changeset-fixed` (the one whose roster sits under
`.changeset/`) and `check:route-ledger-census` among them. The other
three, `check-closing-target-claim`, `check-partof-closing-keyword` and
`check-single-claim-paths`, answer 「NOT WIRED」 (exit 2) without a pull
request's context; they are run against this PR once it exists and
reported on the card.

## Hypotheses (measured first)

- **H0 holds.** The filtered census answers 191 dead sites at
`a186aea996` (186 lines, 14 files, 51 numbers), equal to the card's
count at `f11b5f20a2`: no net drift, although PR objectstack-ai#20601 (merged as
`eb4b17c346`, before this base) touches four files in `packages/rest`.
- **H1 holds.** After the rewrite the filtered census answers 0. The
supplementary reading leaves 3 test-comment sites, the three listed
above: an open decision, an untaken option and a transferred issue, none
with a deciding commit. No site was held for an open PR: the claim's
read and this stage's two reads of the open PRs' file lists (10:27:35Z,
7 open PRs; 11:30:34Z, 8 open PRs) found none touching `packages/rest`.
- **H2 holds, by the token guard.** A comment-stripped comparison of
every touched file (the parser's leaf tokens, JSDoc excluded) is empty,
and its code and string controls fire. The emitted `dist` is not
byte-identical, because the docblocks ship, which is why the changeset
is `patch`.

## Acceptance notes

- **Form D, not touched here.** 127 dead numbers stand inside string
literals: 126 in test titles and test-code strings, and one in the
`note` of the REST route ledger's legal-next-state row
(`rest-route-ledger.ts:290`, 「(objectstack-ai#10179)」), which is ledger data, not an
author-shown refusal. Ruling D (no number, the lesson in words) is a
string change outside this comment-only scope; the card already carries
a form-D stage for the lane.
- **A transferred issue among the 404s.** #14026 answers 302 to
objectstack-ai/objectui#10102 on its web endpoint. The census classes it
`allocated-but-absent` (deleted and transferred are only told apart
under `--probe-cause`), and `scripts/check-issue-citations.mjs`'s header
says the `transferred` arm has no positive specimen on this tree; this
is one. Noted, not filed.
- **The grammar does not read a slash-joined number.** `CITATION_RE`
refuses a `#` preceded by `/`, so the second number of `#A/#B` is never
judged. In `packages/rest/src` six such dead numbers stood at 10 comment
sites, all rewritten here; one more, `objectstack-ai#14389` in `objectstack-ai#14095/objectstack-ai#14389`, stands
inside a string
(`error-response-structured-arm-door-parity.test.ts:187`) and is kept.
The same shape PR objectstack-ai#20624 and PR objectstack-ai#20612 reported. Noted, not filed.
- **Outside the scope and the census surface.**
`packages/rest/vitest.config.ts:21` cites objectstack-ai#17853, which answers 404;
`packages/rest/test-typecheck-debt.json`, written by
`gen:test-typecheck-debt`, carries objectstack-ai#13470, objectstack-ai#13454, objectstack-ai#13377 and objectstack-ai#13378 in
its prose, all 404. Neither is under `src/**`. The other numbers in
`vitest.config.ts`, `tsconfig.json` and `tsconfig.test.json` answer 200.
- **Two comments stale on their own, not touched.** The anchor research
found `rest-server.ts`'s `api` docblock near `:1115` and the 「zero read
sites」 sentence at `:4092` both overtaken by `80153f5a4`, whose own
acceptance notes record it. This PR re-anchors their citations and
leaves their claims alone.
- **An attribution corrected by the anchor.** `rest-server.ts:4092`
credited its zero-read-site count to 「the objectstack-ai#14369 census」, which
(`a3d5724c8`) excluded `api`; it now cites `53cbad9f7`, the commit that
measured it.
- **Base.** One merge of `origin/main` (`93e4d69ba6`) before the `--base
origin/main` run, as the dispatch orders.

## Deviations

- Ten sites beyond the census's read grammar carry a slash-joined dead
number and are rewritten; six more lines are the other half of a
rewritten sentence (listed under What changed).
- The whole-workspace build ran with `--concurrency=4`, not 2, to stay
inside the ten-minute foreground cap on this host; it took 1m42s.
- Anchor research for 33 of the 77 numbers ran in three read-only
research subagents; every proposal was verified here against the
commit's message or diff, and the wording of each changed line was
reviewed and corrected by hand in a second pass.
- Commit trailers are AGENTS.md's model-free pair (`Claude-Session` plus
`Co-authored-by: Claude`), and the pre-push trailer check passed on
every push. The merge commit carries git's default message.

---
_Generated by [Claude
Code](https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289)_

---------

Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants