Skip to content

docs(skills): objectstack-automation calls the api flow secret required and routes explicit-only starts to autolaunched - #20796

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-20569-automation-skill-api-secret
Sep 30, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-20569-automation-skill-api-secret

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #20569

Clause-②: no

skills/objectstack-automation/SKILL.md still described the api flow kind and its inbound-hook secret as they were before PR #20551: the Flow Types row said a type: 'api' flow is "invoked explicitly via the API / engine.execute(), or bound as an inbound webhook", and the secret row called the HMAC secret "strongly recommended — without it unsigned posts are accepted and a warning is logged". Both are false on main. Tier H (skills/**): a draft PR for the maintainer's hand — no seat readies, queues or arms it.

What changed — one file, skills/objectstack-automation/SKILL.md (+5 / −6)

Line on main 7a09eee1 Was Now
:51 Flow Types api row explicit invocation or inbound webhook, route inline "Inbound webhook — every api flow is bound to its hook endpoint and needs a start-node secret (see Inbound webhook triggers below); a flow only ever started explicitly is autolaunched"
:342 "An api flow can be bound to an inbound HTTP endpoint" "is bound" — every api flow is
:344-345 "(the start config is a free-form record, so these keys are read at runtime, not Zod-validated)" "(the start config is a free-form record with no Zod shape)" — secret is now judged before runtime, so only the still-true half stays
:350 secret row "Strongly recommended — without it unsigned posts are accepted and a warning is logged" "Required — without a non-blank one the flow is refused at registration (os validate too) and never armed at boot; the signature goes in x-objectstack-signature"
:352 Signature bullet "sender sends x-objectstack-signature: sha256=…" the value shape only (sha256= plus the hex) — the header name now lives in the secret row

Untouched on purpose: the http row (:87) and the examples-flows Slack node (PR #20778's same-day churn on this file). Kept abstract under the security disclosure rule: no request recipe was added; the bullet lost text.

Measured at the code (origin/main 7a09eee1)

Every claim in the new text was read from the runtime, none recalled:

  • The header. packages/triggers/trigger-api/src/plugin.ts:89 reads c.req.header('x-objectstack-signature'); api-trigger.ts:78-81 (verifySignature) compares it, constant-time, with sha256= plus the hex HMAC-SHA256 of the raw body under the start-node secret.
  • api is the inbound-webhook kind; there is no explicit-only api form. packages/spec/src/automation/flow-trigger-kind.ts:83: if (f.type === 'api' || triggerType === 'api') return 'api', and AutomationEngine.deriveTriggerBinding (packages/services/service-automation/src/engine.ts:3526) binds from that resolver, so every type: 'api' flow is handed to trigger-api. A type: 'autolaunched' flow with no start-node binding resolves to no kind (:84) — the explicit-only form. The enum is ['autolaunched', 'record_change', 'schedule', 'screen', 'api'] (packages/spec/src/automation/flow.zod.ts:1060).
  • Refused at registration, never armed at boot. engine.ts:4311 calls validateApiTriggerSecret (:10036-10052), which throws on an api binding whose start node has no non-blank config.secret; every registerFlow call site try/catches per flow, so at boot the flow is skipped loudly. trigger-api's own start() refuses the same binding before anything is stored or subscribed (api-trigger.ts:133-143: "not armed").
  • Every authoring door. The /automation write doors answer the throw with VALIDATION_FAILED_STATUS (packages/runtime/src/domains/automation.ts:2174-2178). os validate runs runAuthoringRules('validate', …) (packages/cli/src/commands/validate.ts:461) over AUTHORING_RULES, which carries validateFlowApiTriggerSecret (packages/lint/src/authoring-rules.ts:1170-1195: tier: 'gating', commands: ALL) and answers flow-api-trigger-secret-missing at error (validate-flow-trigger-readiness.ts:875-895). /meta runs the same table and throws its 422 INVALID_METADATA (packages/metadata-protocol/src/protocol.ts:4933-4941).
  • Exposure. packages/services/service-automation/src/flow-credential-projection.ts:129 withholds the start node's secret from every served definition; the skill already says so at :87, so the row does not repeat it.

skills/** readings (token = ceil(utf8 bytes / 4), the ratchet's own convention)

Reading Before (7a09eee1) After (32847a29) Delta
skills/objectstack-automation/SKILL.md 439 lines · 23106 bytes · 5777 tokens 438 lines · 23125 bytes · 5782 tokens −1 line · +19 bytes · +5 tokens (ceiling 5785, headroom 3)
Whole package — the 54 hand-authored files the ratchet covers 12786 lines · 143329 tokens 12785 lines · 143334 tokens −1 line · +5 tokens
Whole package — every SKILL.md 4395 lines 4394 lines −1 line

Line budget (PM-set: net +2 at most across the package): net −1. No ceiling raised and none lowered (the file grew by 5 tokens). No re-wrap: the one removed line is the :345 clause replaced by its true half. The new text is paid by three in-file deletions, each of which keeps its home in this same file — the hook route is now stated once (:343, in the section the api row points to); "sender sends x-objectstack-signature: " folded into the secret row; "so these keys are read at runtime, not Zod-validated" cut to "with no Zod shape". Nothing left the published package.

Verification

Gates derived from the change with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands at head 32847a29 (no paths: the change set read from the merge base) and reconciled with --ran: 24 derived, 24 run, 0 NOT-MEASURED, 0 UNRUN, every recorded exit code 0. The first run of pnpm --filter @objectstack/lint run check:doc-formula-expressions answered exit 3 (PREREQUISITE NOT MET: @objectstack/formula and @objectstack/lint unbuilt); it was re-run after pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint under the verify lock (VERDICT command-exit 0) and answered exit 0 — the exit-3 run is a non-measurement, not a red. Also green outside the derivation: pnpm --filter @objectstack/spec run check:skill-refs. check:skills-token-ratchet prints "skills/objectstack-automation/SKILL.md is 5782 tokens (ceiling 5785; headroom 3)". check:skill-examples is not owed: no edited block carries an os:check marker. No control bytes in the file. No package test or typecheck owed: the diff touches no package.

Acceptance notes

Census of skills/** and content/docs/** for sentences describing the api trigger or its secret (case-insensitive grep over unsigned post, strongly recommended, invoked explicitly, explicit-only, hooks/:flowName, x-objectstack-signature, inbound webhook, type: 'api', api … secret, autolaunched; content/docs/references/ and content/docs/releases/ excluded as generated / release-owned):

  • skills/** outside this file: no sentence about the api flow trigger or its secret. The type: 'api' hits in skills/objectstack-ui/rules/actions.md:22,38 and skills/objectstack-ai/SKILL.md:158 are the UI action kind, not the flow kind. skills/objectstack-automation/references/* and evals/* carry no api-flow or secret sentence.
  • content/docs/**: no sentence calls the secret optional or type: 'api' explicit-only, so nothing there is false in the card's sense. Two observations, noted and not filed: content/docs/automation/flows.mdx:92 (api — "Exposed as an API endpoint" / "HTTP request") is true but names neither the hook nor the secret; content/docs/automation/webhooks.mdx:733-736 ("Inbound webhooks … reintroduce it only alongside a real inbound runtime") is the outbound protocol's non-goals list written before trigger-api (ADR-0041 Tier 1) and reads as if no inbound runtime existed — stale, not false; carrier: none. content/docs/releases/v17/17-5.mdx already states the requirement correctly.

维护者速读(草稿)

改了什么 — skills/objectstack-automation/SKILL.md 里两处过时说法:Flow Types 表的 api 行不再说 type: 'api' 可以「只显式调用」,改为「入站 webhook,每个 api 流都绑到它的 hook 端点、都要 start 节点的 secret;只显式启动的流是 autolaunched」;secret 行由「强烈建议」改为「必填」,写明缺失时的真实后果(注册时拒绝、启动时不装载),并点名签名头 x-objectstack-signature。另外三处小改是为 token 上限付账:hook 路由只在下方章节写一次、签名要点只留值的形状、「运行时才读取、不经 Zod 校验」只保留仍成立的后半句。

为什么改 — PR #20551 之后,运行时所有写入口(/automation 写门、os validate、/meta、引擎注册、trigger-api 装载)都拒绝没有非空 config.secret 的 api 流;技能包却仍在教 AI 写一个运行时必拒收的流,并暗示 type: 'api' 有「只显式调用」的形态。每条新句子都从代码读出,正文附行号。

风险与代价(含回滚) — 纯文档改动,不发布任何包(skills/** 不在任何包的 files[] 内,打 skip-changeset)。token 上限 5785 内(现 5782,余量 3),行数净 −1,上限未动。风险仅限措辞;回滚为 git revert 单个 commit,无连带。

席位意见 — (留空)

你要做的 — 以维护者身份审阅并合并这个 draft PR(Tier H:席位不得 ready / queue / auto-merge)。若想保留「read at runtime, not Zod-validated」原句,需另删等量内容守住 token 上限,请在评审中指出。


Generated by Claude Code

…ed and routes explicit-only starts to autolaunched

The `api` Flow Types row said a `type: 'api'` flow could be invoked
explicitly OR bound as an inbound webhook; the engine binds every
`api`-kind flow to the inbound trigger, so the explicit-only form is
`autolaunched`. The `secret` row called the HMAC secret "strongly
recommended"; the runtime refuses an `api` flow with no non-blank
`config.secret` at registration (`/automation` doors, `os validate`,
`/meta`) and `trigger-api` never arms it. The row now says so and names
the header the signature goes in, read from `trigger-api`'s handler.

Paid in-file: the hook route stays stated once (the section the row
points to), the signature bullet keeps only the value shape, and the
"read at runtime, not Zod-validated" clause — now false for `secret` —
keeps only its true half.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Sep 30, 2026
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 30, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 32847a291404fbe17511948eff46011083008f10
Local-runs: none

Inputs: card #20569 (body + its three comments, the dev report 5905964684 included), PR #20796 (body, file list, the net diff against main at the head), and the check-runs on the head. Repository files read with git show / git grep at the head and at origin/main 8acdae9d. Merge-base is 7a09eee1; no main commit touched skills/objectstack-automation/SKILL.md between the two, so the net diff is exactly the one file, +5 / −6. Head repo is the base repo (not a fork); the PR is draft with auto-merge unset.

① Derived judgments

Accept-set and public-surface changes implied by the diff: none. One published skill file edited; no spec key, export, runtime behaviour, route or generated artifact moves. Every statement the changed lines make, judged at the code on main 8acdae9d:

  1. :51 "Inbound webhook — every api flow is bound to its hook endpoint" — right. packages/spec/src/automation/flow-trigger-kind.ts:83 answers api for type: 'api' (or a start-node triggerType: 'api'); AutomationEngine.deriveTriggerBinding (engine.ts:3478) takes that answer and activation hands the binding to trigger-api, whose route is HOOKS_PATH (packages/triggers/trigger-api/src/plugin.ts:25). One precision caveat, not a defect: the resolver ranks record-* / timeRelative / schedule ahead of api (:76-83), so a type: 'api' flow whose start node ALSO carries one of those binds to that trigger and is never asked for a secret — the lint header at validate-flow-trigger-readiness.ts:813-821 documents exactly this. That is a mixed declaration the section never teaches; the pre-PR sentence carried the same simplification and the record_change row already tells the author the engine reads the start node. Right as an authoring rule.
  2. :51 "needs a start-node secret" — right. engine.ts:10127-10142 throws on an api binding whose start node has no non-blank config.secret; api-trigger.ts:133-143 refuses the same binding independently.
  3. :51 "a flow only ever started explicitly is autolaunched" — right. The resolver returns no kind for an autolaunched flow with no binding (flow-trigger-kind.ts:84, header :55-57); the engine's own refusal sentence and the lint hint (validate-flow-trigger-readiness.ts:889-892) name the same form.
  4. :342 "An api flow is bound to an inbound HTTP endpoint" + the route — right (plugin.ts:25; the caveat in item 1 applies identically).
  5. :344 "the start config is a free-form record with no Zod shape" — right. flow.zod.ts:591 declares config: z.record(z.string(), z.unknown()).optional(); the FlowNodeSchema transform parses only an end node's config (:544-552); no start-node config schema exists under packages/spec/src/automation/.
  6. :349 "Required — without a non-blank one the flow is refused at registration (os validate too) and never armed at boot; the signature goes in x-objectstack-signature" — right, on every door. Registration: registerFlow calls validateApiTriggerSecret at engine.ts:4311. Boot: every registerFlow call site catches per flow and warns (service-automation/src/plugin.ts:992-997, :2044-2048, :2092-2097), so the flow is skipped, never bound; ApiTrigger.start() additionally refuses before anything is stored (api-trigger.ts:138-143, "not armed"). /automation write doors: packages/runtime/src/domains/automation.ts:2180-2186 answers the throw with VALIDATION_FAILED_STATUS (400). os validate: packages/cli/src/commands/validate.ts:461 runs runAuthoringRules('validate', …) over AUTHORING_RULES, whose row validateFlowApiTriggerSecret (packages/lint/src/authoring-rules.ts:1184-1197) is tier: 'gating', commands: ALL, surfaces: CLI_AND_RUNTIME, runtimeTypes: ['flow'], and answers flow-api-trigger-secret-missing at error (validate-flow-trigger-readiness.ts:257, :857-895). /meta: packages/metadata-protocol/src/protocol.ts:4933-4941 runs the same table and throws 422 INVALID_METADATA. Header: plugin.ts:89 reads c.req.header('x-objectstack-signature').
  7. :351 "Signature: sha256= plus the hex (GitHub/Stripe style)" — right. api-trigger.ts:80 computes 'sha256=' + hex HMAC-SHA256 of the raw body; ADR-0041 docs/adr/0041-flow-trigger-family.md:119 says "per-flow secret; HMAC signature verification (GitHub/Stripe style)".

Deleted clauses, each judged for a surviving home:

  • "Invoked explicitly via the API / engine.execute(), or" — the false half the card names (item 2); not a rule to rehome. That any flow may be started explicitly stays at :47 (autolaunched … "triggered by events, APIs, or other flows"). Not a lost rule.
  • The inline route in the api row — home at :343, in the section the row points to. Not lost.
  • "can be bound" → "is bound" — the card's direction, right per item 1.
  • "so these keys are read at runtime, not Zod-validated" → "with no Zod shape" — the "read at runtime" half is now false for secret (judged at registration and by the lint rule); the "not Zod-validated" half survives in meaning (item 5). Not a lost rule.
  • "sender sends x-objectstack-signature:" — the header name rehomed into the secret row (:349); the inbound direction is carried by the section title and the route above it. Not lost.

Contradictions: none found. In-skill: :47, :326 (triggers token turns on api start bindings), :328 (queue absent → 503) and :87 (a start node's secret withheld from served definitions; flow-credential-projection.ts:10-12) all agree with the new text. references/* and evals/* at the head carry no sentence on the api flow or its secret (the autolaunched hits are examples consistent with :51). content/docs/automation/** at main: flows.mdx:88 (autolaunched "Invoked by other flows or API"), :92 (api "Exposed as an API endpoint / HTTP request" — true, incomplete), :1847, :2277 are all consistent; webhooks.mdx:733-736 (the outbound protocol's v1 non-goals note) predates trigger-api and is stale on main before this PR, neither introduced nor worsened by it. Frontmatter version: "1.3" untouched — the precedent on this file (7a09eee1, PR #20778) bumped nothing on a body fix.

Security disclosure constraint: right. The head states the route (once), the header name, the value shape and "GitHub/Stripe style"; main before this PR stated all four plus "sender sends". Nothing new; main's own lint hint (validate-flow-trigger-readiness.ts:889-891) already discloses more than the skill does.

Token ratchet, measured at both trees: 23106 → 23125 bytes, 5777 → 5782 tokens (ceil(bytes/4)) against the ceiling 5785 at scripts/check-skills-token-ratchet.mjs:323 — headroom 3; 439 → 438 lines. Matches the dev's readings byte for byte. The three in-file deletions that paid for it are the ones judged above.

② Semver level

skip-changeset with Clause-②: no — right. skills/** is in no released package's files[] (at main, only spec/lint gate scripts read that tree); create-objectstack installs the catalog into a generated project via npx skills add, not from an npm tarball; the diff publishes nothing from any released package. The precedent on this same file, #20778 (7a09eee1), landed with the same label, no .changeset entry, merged by the maintainer. No accept-set change, so the Clause-②: no line is well-formed (no arm, none owed).

③ Boundary flags

Dev report 5905964684 — every deviation answered:

  1. gh CLI absent; card/PR read by plain REST; writes only through scripts/pm (with-fleet pr_create, label-write, post-stamped), body read back byte-identical, no PATCH — answered, nothing to escalate: reads are unrestricted and the three writes took the sanctioned path.
  2. Three neighbouring sentences (:342, :344-345, :352) edited beyond the two card rows — answered: inside the dispatch's file surface ("any other sentence … that describes the api trigger or its secret"); each judged true in ① (items 4, 5, 7) and each deletion has a home; without them the ceiling would have been breached.
  3. No request recipe added — answered in ①.

open_questions: none declared, none found.

out_of_scope_findings (both content/docs/automation/**, carrier: none) — answered and escalated to the dispatching seat, not blocking: the dispatch reserved content/docs/** for census-only reporting, so noting them in the PR's acceptance notes is the right shape under Prime Directive #10. Classification holds: webhooks.mdx:733-736 is stale, not false; flows.mdx:92 is true but names neither the hook nor the required secret — it is the docs-lane twin of this card's :51 and a candidate for one docs-lane card (the webhooks.mdx note could ride it). Left to the seat to card.

Other flags: Tier H (skills/**) — this record is the seat's review of record; landing still takes an authorized APPROVED review and the maintainer's hand, and no seat readies, queues or arms it. Non-blocking note for the maintainer: :51's "every api flow" carries the resolver-precedence caveat in ① item 1; a qualifying clause would cost tokens the ceiling (headroom 3) does not have.

Check-runs on the head, read 2026-09-30T07:11:41Z — 34 runs: 20 success, 11 skipped, 2 in_progress, 1 failure.

  • in_progress (not a pass, recorded as read): Lint & Repo Gates (required; run 36681172476) and Test Core (1/6) (a shard of the required Test Core; run 36681172464). Shards 2/6–6/6 are success.
  • failure: Check Changeset on the opened run (36681172216, 06:59:31Z) — its event payload predated the skip-changeset label; the labeled run of the same job (36681380057) is skipped. pr-automation.yml:257-263 names this stale-red-by-construction pair itself. Advisory: not one of the seven required contexts.
  • Required contexts success: TypeScript Type Check, Dogfood Regression Gate, Governed Surface Queue Guard. Build Core and Temporal Conformance (live PG + MySQL) are skipped by paths filter on a docs-only diff. Type Check · source gates, the job carrying check:skill-docs / check:skill-refs (lint.yml:5502), is success.

Implemented-by: claude/issue-20569-automation-skill-api-secret
Reviewed-by: session_01KTZmMfzVzjNvyaLyQ8mHvg

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读(终稿)— PR #20796 · automation 技能:api 流程的 secret 必填(#20569)

skills 席 1 · session_01KTZmMfzVzjNvyaLyQ8mHvg · 2026-09-30T07:16Z · 所审 head 32847a29

改了什么:只改 skills/objectstack-automation/SKILL.md 一个文件(+5 / −6,净 −1 行)。

  • Flow Types 表的 api 行:改为「入站 webhook」。每个 api 流程都绑定到自己的 hook 端点,必须有 start 节点的 secret;只靠显式调用的流程应写成 autolaunched。
  • 入站 webhook 一节的 secret 行:从「强烈建议」改为必填。没有非空 secret 的流程在注册时被拒,os validate 也会报错,启动时不会挂上;签名放在 x-objectstack-signature 头里。
  • 同节顺手改了三句相邻的话,把 token 付回来,每处删掉的内容在同一文件里都另有归属。

为什么改:PR #20551 之后,运行时在每个入口都拒绝没有 secret 的 api 流程,技能却还写着「可选」「可只显式调用」。AI 照着写,会写出一个每个入口都拒收的流程。

风险与代价(含回滚):纯文档,不动代码,不发布任何包(skip-changeset 已挂)。没有新增任何请求构造细节:路由、头名、签名格式在 main 上本来就写着。token 余量剩 3。回滚:revert 本 PR。

席位意见:ACCEPT,建议批准。

  • 契约复核 PASS(评论 5906148151,由隔离的达档子代理出具,席位核验后采纳)。复核在代码层逐个入口核实了每一句:引擎注册、启动、trigger-api、/automation 写入、os validate、/meta。
  • CI:Lint & Repo Gates 仍在跑,落地前须转绿。唯一的红是打标签之前那次 Check Changeset 的旧结果,打标签后那次已跳过。
  • content/docs 里 flows.mdx:92 对 api 的描述不完整(没提 hook 和 secret),但没有说错,本 PR 不改,留给 docs 车道下次编辑。

你要做的:在本 PR 上给 APPROVED。之后由本席位核对 CI 全绿,再翻 ready 入队。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 30, 2026 08:26
@os-zhuang
os-zhuang enabled auto-merge September 30, 2026 08:26
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit eac538c Sep 30, 2026
43 of 44 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-20569-automation-skill-api-secret branch September 30, 2026 08:52
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
… commits and ADR that decided them (objectstack-ai#20816)

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

## What changed

This is the fourteenth stage of the `domain:services` lane of the
dead-citation sweep. It covers
`packages/services/service-automation/src/**` and nothing else. By the
seat's claim (`5905919247`), it is the largest package left in the lane,
and it was free once the `engine.ts` work of the previous holder landed
as `c8111a575`. Later stages cover the other packages, so this PR says
`Part of` and the card stays open.

Every comment or docblock site in scope that cited a tracker number
answering 404 has been rewritten in ruling C+D's form C (comment
5749154545 on objectstack-ai#19123), by the method of stages 1 to 13 (the latest is PR
objectstack-ai#20789, landed as `8acdae9d8`). That is **73 sites on 73 lines in 27
files, covering 14 numbers**:

- 44 census sites (every census site this package has at the base);
- 24 sites in test comments, which the census defers; 3 of their numbers
(`objectstack-ai#11504`, `objectstack-ai#16709`, `objectstack-ai#8778`) stand only in test files here, and each
was read on its own and answers 404;
- 5 sites the census grammar cannot see: the `objectstack-ai#13398-class` spelling (a
hyphen after the digits), 2 in `engine.ts` and 3 in test files.

Each rewritten line now cites the record in this repository that decided
what the line describes, and says in its own words what was decided:
**15 distinct commit shas, plus ADR-0126 §7.2** on 2 lines, per ruling
C's order (the ADR first where it records the decision; see the
per-number table). No number was dropped.

Only comments changed. Every touched source file keeps its line count
(76 lines out, 76 in, over 27 files), so no line citation into these
files moves. 73 of the 76 changed lines carried a dead citation; the
other three are listed under Wordings. No code token moves (see the
guard below).

**No citation number is added.** The only tracker numbers on added lines
are the live `objectstack-ai#14095`, `objectstack-ai#14456`, `objectstack-ai#8287` and the cross-repo
`hotcrm#1206`, each once and each on the line it already stood on. No
number is new to the diff, no number grew, and no PR number is the
citation on an added line.

17 dead sites are left on purpose: 16 test titles and 1 runtime string
(see the list below).

One more file: a `patch` changeset for
`@objectstack/service-automation`, because the rewritten prose ships
(see Changeset below).

## Census: `service-automation`, before and after

**Instrument (A1).** The gate's own `node
scripts/check-issue-citations.mjs --census --json`, read-only and
unchanged. The count below is its `allocated-but-absent` findings under
`packages/services/service-automation/`. Each run counts as a reading
only because its board frontier equals the newest issue or pull-request
number, read by a separate request just before and just after the run.
In two of the three runs a new number was opened while the run was
enumerating; each frontier equals the newest number at the run's end,
which is the criterion (stages 7, 11 and 13 met the same shape).

| reading | tree | board | whole-repo `allocated-but-absent` |
service-automation sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `8acdae9d8`, run 2026-09-30T07:03:52Z to 07:07:25Z |
enumerated, 187 pages, frontier objectstack-ai#20798 (newest objectstack-ai#20796 before, objectstack-ai#20798
after) | 796 | **44** | 44 | 11 | 11 |
| after | `3511e88cc` (comments and changeset), run 07:32:02Z to
07:35:27Z | enumerated, 187 pages, frontier objectstack-ai#20803 (newest objectstack-ai#20803 before
and after) | 752 | **0** | 0 | 0 | 0 |
| after, final head | head `a602c4003`, run 07:58:50Z to 08:02:19Z |
enumerated, 187 pages, frontier objectstack-ai#20809 (newest objectstack-ai#20807 before, objectstack-ai#20809
after) | 752 | **0** | 0 | 0 | 0 |

The whole-repo drop is 44, exactly this diff's census sites. The
`resolves` tally is 33,096 in all three runs, and
`resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (1,003) did
not move either. No run was truncated or discarded: all three
enumerations read 187 pages at the newest frontier.

The seat's census counted 45 here at `6bff748b`. The difference is one
`objectstack-ai#10243` comment line that `36d043be1` (PR objectstack-ai#20724) removed from
`engine.ts` in the meantime; the other seven commits since then add or
remove no dead site in this package's non-test `src`.

**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 `namesThisRepository`
over every `.ts` file under `service-automation/src` (191 files). It
takes its verdicts from the before census's own board reading rather
than from a second enumeration: a number is dead when that census
reported it `allocated-but-absent`, and alive when the gate's own
census-scope extraction judged it and the census did not report it. 39
numbers are covered by neither, because they stand only in test files or
strings here; each was read on its own through the read-only tools (2
answer 404: `objectstack-ai#11504` and `objectstack-ai#16709`; 18 answer 200 as issues; 19 answer
200 as pull requests, 18 of them also the squash suffix of a commit on
`main`).

| reading | citations | dead | src comment | test comment | src string |
test string |
|---|---|---|---|---|---|---|
| before, `8acdae9d8` | 3,019 | **85** | 44 | 24 | 1 | 16 |
| after, `a602c4003` | 2,951 | **17** | 0 | 0 | 1 | 16 |

Its src-comment column equals the census's 44, which is the control on
the second instrument. The 2,874 live citations and 60 cross-repo
citations are the same in both readings, and the drop of 68 citations is
exactly the rewritten sites the gate's grammar can see. A third, raw
reading (every `#` followed by 2 to 6 digits, whatever surrounds it)
finds 3,078 occurrences before and 3,005 after: the 68, plus the 5
`objectstack-ai#13398-class` sites only this reading sees.

## Per-number table

Sites and files count every dead occurrence in scope at the base
(comments and strings, tests included, and the 5 hyphen-joined sites).
`rewritten / left` counts the sites rewritten and the sites left. Each
anchor was read in its message and diff, not only its subject.

| number | sites / files | rewritten / left | anchor: what it decided |
|---|---|---|---|
| `objectstack-ai#11060` | 17/4 | 12/5 | `815585513` (PR objectstack-ai#11347): flow value
expressions gain exactly `round` / `floor` / `ceil` / `abs` / `min` /
`max`, mirrored 1:1 from the CEL stdlib, and an unknown name in call
position becomes the loud `FlowExpressionFunctionError` instead of a
silent `null`. Its message records the maintainer ruling on `objectstack-ai#11060`
(2026-08-23, option A) and its diff names the number 18 times; `git
blame` puts 11 of the 12 lines in it, and the twelfth
(`end-node-refused-outcome.test.ts:333`) was written later. The lint
lane's anchor for the same number |
| `objectstack-ai#10243` | 14/5 | 13/1 | Three rungs, per line. **ADR-0126 §7.2** on 2
lines (`engine.ts:2315`, `flow-activation-ledger.test.ts:1024`): the
durable ledger row replaces the process-local `flowEnabled` map,
"retiring the objectstack-ai#10243 leak's mechanism rather than refining it"
(`docs/adr/0126-packaged-metadata-customization-model.md:339-340`),
which is what both lines say is the point. **`02b41232d`** (PR objectstack-ai#10996)
on 9 lines, the ones that say the map "measured" leaking: the recorded
measurement that tenant A's toggle answered 200 and tenant B and the
platform admin read the flow back off. **`266436a7f`** (PR objectstack-ai#11660) on 2
lines: it implements the maintainer ruling on `objectstack-ai#10243` (option A, toggle
joins the `manage_metadata` write set), which is the gate
`flow-activation-store.ts:67` says the difference "turned on", and it
wrote the "mitigating but not exculpating" record that
`flow-activation-ledger.test.ts:272` quotes. The runtime lane's anchors
for the same number |
| `objectstack-ai#14419` | 14/4 | 11/3 | `c5a7448d5` (PR objectstack-ai#14948): `create_record`
surfaces `engine.insert`'s classified `DUPLICATE_RECORD` code on the
node result, the engine copies it onto `$error`, and `try_catch`
preserves it across its own binding; deliberately scoped to
`create_record`. Its message's last line names `objectstack-ai#14419` as its card, its
diff names the number 13 times, and `git blame` puts 8 of the 11 lines
in it (the other 3 were written by later commits it precedes). The spec
lane's anchor for the same number |
| `objectstack-ai#13398` | 9/4 | 9/0 | `e238c79f0` (PR objectstack-ai#13592): the earliest text in
this repository that records the maintainer's published-sink ruling as
made — raising a log level by widening a published sink that declares no
`error` is "refused as actively harmful". Every line here states that
ruling ("forbids raising a site to `error` where doing so means GROWING
`error?` onto a published sink"). Stage 3's anchor, and the one the
stage-3 review recommended for this package |
| `objectstack-ai#16659` | 9/3 | 9/0 | `ecdfc9411` (PR objectstack-ai#17334): a time-triggered flow
declares its acting organization on its start node, the engine lifts it
onto the binding, and the triggers run the flow as it. `git blame` puts
the 4 `engine.ts` / `suspended-run-store.ts` lines in it. The 5 lines in
`notify-zero-delivery-visibility.integration.test.ts` were written by
`ae6dcf6a4`, an ancestor of `ecdfc9411`, in the future tense ("once
objectstack-ai#16659 lands"); they now name the landed commit (see Wordings). Stage
12's anchor |
| `objectstack-ai#13648` | 9/3 | 6/3 | `7307191db` (PR objectstack-ai#14388): the public `resume`
door normalises an absent signal to `{}`, and the chokepoints take a
non-optional signal, so a signal-less resume is held to the screen
contract. Its diff names `objectstack-ai#13648` 8 times; `git blame` puts 5 of the 6
lines in it |
| `objectstack-ai#13681` | 6/4 | 4/2 | `18d816a50` (PR objectstack-ai#14452): the spec half of the
contained-failure contract, which declares the run-level `failed`, the
loop iteration through `try` / `catch`, and row identity on `$error`.
Its subject names `objectstack-ai#13681`. The lines were written by the services
halves (`d30ccb9bd`, `b7225477b`), both descendants. The spec lane's
anchor for the same sentence ("one level up") |
| `objectstack-ai#17123` | 4/2 | 2/2 | `ae6dcf6a4` (PR objectstack-ai#17339): a `notify` node
reports `selected`, the recipients it addressed, so a zero-delivery run
stops reading like a run with nothing to notify. Its diff writes
`objectstack-ai#17123` 4 times, and `git blame` puts both lines in it. New to the
sweep |
| `objectstack-ai#8707` | 2/2 | 2/0 | `1408fe385` (PR objectstack-ai#8777): audit rows are stamped
from the record's own organization. Stage 7's anchor |
| `objectstack-ai#16709` | 2/1 | 1/1 | `8c7cca1ce` (PR objectstack-ai#16739): its item 1 pins the
restore verb's drop of a stale hot consumed-suspension copy, and it
created this test file. Stage 7's anchor |
| `objectstack-ai#10062` | 1/1 | 1/0 | `fa5d137ab` (PR objectstack-ai#12942): published `src` may
import only declared workspace dependencies; its diff moved this import
to `@objectstack/metadata-core`. Subject names it |
| `objectstack-ai#14390` | 1/1 | 1/0 | `9d7f7259f` (PR objectstack-ai#14603): both driver exits of
`engine.update` answer a unique violation with the `DUPLICATE_RECORD`
envelope. Subject names it. The runtime and rest lanes' anchor |
| `objectstack-ai#11504` | 1/1 | 1/0 | `f90e82024` (PR objectstack-ai#12611): registers
`FLOW_INPUT_SCHEMA_INVALID`, the line's subject; its diff names `objectstack-ai#11504`
5 times. The spec and runtime lanes' anchor |
| `objectstack-ai#8778` | 1/1 | 1/0 | `7901b2dd2` (PR objectstack-ai#8905): the stamp-only
`tenancy.organizationField` declaration the line names. Stage 6's anchor
|

Every cited sha matches exactly one commit (`git rev-parse
--disambiguate`, count 1 for each of the 15), and all 15 are ancestors
of the base (`merge-base --is-ancestor`, exit 0 for each; reverse leg,
base against each anchor, exit 1 for each; control legs exit 0: stage
1's landing `422db788a`, and the repository's root commit, which lies
deeper than every anchor; the history is complete,
`--is-shallow-repository` false, 15,178 commits). Each of the 14 numbers
answers 404 on the issues endpoint, which serves pull requests too, read
one by one.

No ADR, `scripts/adr-anchors/` file or other `docs/` page records the
decision of any of the 14 (a `docs/audits` census row names `objectstack-ai#10062` as
a gate's origin, and `docs/qa` checklist rows name `objectstack-ai#10243`; neither is
a decision record), except ADR-0126, which records the retirement
`objectstack-ai#10243`'s measurement led to (§7.2) and the operator gate (§5). The
`objectstack-ai#10243` lines that describe the measurement or the ruling cite those
commits; the two lines that describe the retirement cite the ADR.

## Wordings to check

- **Tag swaps in brackets or parentheses.** 「[objectstack-ai#16659]」 became 「[commit
ecdfc94]」 on 4 lines, 「[objectstack-ai#10062]」 became 「[commit fa5d137]」, and
every parenthesised `(#N)` became `(commit SHA)` in place.
- **Headings.** 「objectstack-ai#14419 —」, 「objectstack-ai#17123 —」, 「objectstack-ai#11060 —」, 「objectstack-ai#16709 item 1 —」 at
the head of a docblock or comment became 「Commit c5a7448 —」 and so on,
the form stage 13 used.
- **Two headers name the card or issue by its commit.**
`notify-zero-delivery-visibility.integration.test.ts:4` now opens 「The
card behind commit ae6dcf6:」, and
`flow-field-expression-scale.integration.test.ts:4` 「The end-to-end
oracle for the issue behind commit 8155855」. The docblocks below them
go on speaking of 「the card's reading」, 「the card's ⭐」 and 「the third
value-producing surface the issue names」; the headers keep that
antecedent. This landed as its own commit, `a602c4003`, and every
reading was re-run on it.
- **`objectstack-ai#13398`.** 「a objectstack-ai#13398-class raise: that ruling forbids」 became 「a
raise under the published-sink ruling (commit e238c79): that ruling
forbids」; 「outside objectstack-ai#13398's class」 became 「outside the sink ruling's
(commit e238c79) class」; 「(objectstack-ai#13398-class)」 became 「(the published-sink
ruling, commit e238c79)」.
- **`objectstack-ai#10243`.** 「the map objectstack-ai#10243 measured leaking」 became 「the map commit
02b4123 measured leaking」 (and alike on 9 lines); 「the whole point of
objectstack-ai#10243」 became 「the whole point of ADR-0126 §7.2」; 「the one objectstack-ai#10243
turned on」 became 「the one the toggle ruling (commit 266436a) turned
on」; 「objectstack-ai#10243 — the retired mechanism is GONE」 became 「ADR-0126 §7.2 —
the retired mechanism is GONE」.
- **`flow-activation-ledger.test.ts:271-272`.** 「the objectstack-ai#10243 map's "cold
boot reads enabled: true again" was recorded as
mitigating-but-not-exculpating」 became 「the retired map's … was recorded
(commit 266436a) as mitigating-but-not-exculpating」. The anchor moved
one line down, onto the verb it dates; `:272` is one of the three
changed lines that carried no dead number.
- **`crud-nodes.ts:439-441`, a statement that was stale when it
landed.** 「`engine.update` still leaks the raw driver error (objectstack-ai#14390, not
yet fixed) — those node results have nothing structured to surface yet」
became 「`engine.update` gained the same `DUPLICATE_RECORD` envelope for
a unique violation (commit 9d7f725), but those node results are
untouched here — this repair was scoped to `create_record` alone.」
`9d7f7259f` is an ancestor of `c5a7448d5`, the commit that wrote the
sentence, so the update door already carried the envelope when "not yet
fixed" landed; `c5a7448d5`'s message states the `create_record`-only
scope. `:439` and `:441` are the other two changed lines with no dead
number.
- **`objectstack-ai#16659` in the notify test, future tense.** 「Why objectstack-ai#16659 landing
does not close this」 became 「Why commit ecdfc94 does not close this」;
「the shape a scheduled flow has once objectstack-ai#16659 lands」 became 「the shape a
scheduled flow takes under commit ecdfc94」; 「as it fires once objectstack-ai#16659
lands」 became 「as it fires under commit ecdfc94」.
- **`objectstack-ai#13681`.** 「the measurement these tests reproduce (objectstack-ai#13681) found」
became 「the measurement behind commit 18d816a, reproduced here,
found」: the measurement was the card's, and `18d816a50` is the contract
that answered it.
- **`objectstack-ai#11060`.** 「the LOUD half of the objectstack-ai#11060 ruling」 became 「the LOUD
half of the ruling commit 8155855 records」; 「the exact silence objectstack-ai#11060
removes」 became 「the exact silence commit 8155855 removed」; 「before
objectstack-ai#11060 this wrote the field as undefined」 became 「before commit
8155855 …」.
- **`objectstack-ai#14419`.** 「a different door than objectstack-ai#14419's original bug」 became 「a
different door than the bug commit c5a7448 fixed」; 「the whole point of
objectstack-ai#14419」 became 「the whole point of commit c5a7448」.
- **`objectstack-ai#16709`.** 「objectstack-ai#16709 item 1 —」 became 「Commit 8c7cca1, item 1 —」:
the item numbering is that commit's own.

## The 17 sites left

- **Test strings, 16 sites on 16 lines**, all `describe` / `it` titles,
left as stages 1 to 13 left theirs:
`contained-failure-visibility.test.ts:184` and
`loop-dying-body-steps.test.ts:298` (`objectstack-ai#13681`);
`create-record-duplicate-code.test.ts:51`, `:133`, `:255` (`objectstack-ai#14419`);
`notify-zero-delivery-visibility.integration.test.ts:403`, `:535`
(`objectstack-ai#17123`); `screen-resume-signal-less.test.ts:81`, `:134`, `:187`
(`objectstack-ai#13648`); `template-functions.test.ts:44`, `:162`, `:202` and
`flow-field-expression-scale.integration.test.ts:83` (`objectstack-ai#11060`);
`flow-activation-ledger.test.ts:1027` (`objectstack-ai#10243`);
`stale-hot-consumed-suspension.test.ts:157` (`objectstack-ai#16709`).
- **One runtime string**: `builtin/template.ts:192`, the tail of the
unknown-function refusal ("(Before objectstack-ai#11060 this name was silently
rewritten to null …)"). A runtime string takes form D, not this card's
comment-only form C, and the shrink-only `doc-authoring-prose-id`
baseline already holds it (`template.ts`: `objectstack-ai#11060: 1`), so
`check:doc-authoring` sees no growth.
- No operator log string, assertion message, quoted maintainer ruling or
generated file in this package carries a dead number.
- **Outside `src`, listed and left, not edited in this stage:** the
shipping `README.md` names no dead number (`objectstack-ai#4336`, `objectstack-ai#4414`, both live).
`tsconfig.test.json` names the dead `objectstack-ai#13176` on 2 lines (5, 73) and the
dead `objectstack-ai#14916` on 1 line (54); its other numbers are live.
`vitest.config.ts` names only live numbers. The release-owned
`CHANGELOG.md` names 7 of the 14 numbers on 13 lines.

## Mechanical guard: no code token moves

The guard compares, base `8acdae9d8` against head, over all 27 touched
`.ts` files:

- **Reading 1**, the TypeScript parser's leaf nodes (a `forEachChild`
walk, so comments are trivia and JSDoc nodes are never visited). String
and template literals are therefore read in full.
- **Reading 2**, the full token stream in parser context (a
`getChildren` walk, so punctuation and keywords are included; JSDoc
nodes skipped).

Results:

- Real run at the final head `a602c4003`: 47,982 base leaf tokens, **0
files with a token change** on either reading (exit 0).
- Comment control in `engine.ts` (「which folds nothing and rejects
nothing.」 to 「which folds nothing and refuses nothing.」): 0 files
changed, as expected (exit 0).
- Positive control, a code token renamed in `engine.ts` (`function
applyResumeSignal(` to `function applyResumeSignalX(`): DIFFER on the
identifier (exit 1).
- Positive control, one digit changed inside a kept test title
(`flow-activation-ledger.test.ts:1027`, `objectstack-ai#10243` to `objectstack-ai#10244`): DIFFER on
the string literal (exit 1).

Every mutation went through `scripts/ablation-replace.mjs` (wrap mode)
under a shell trap that restores by absolute path, and each landed
(anchor 1 to 0, blob changed). Each restore was proven byte-identical to
the HEAD blob (`26e9be7dc174`, `e78e505fa3be`), with `git diff HEAD`
empty and a clean tree afterwards. The controls ran on `3511e88cc` and
again on `a602c4003`.

## Changeset

This change ships bytes, so a `patch` changeset for
`@objectstack/service-automation`
(`.changeset/20596-service-automation-provenance-anchors.md`) is
included. Its body is stage 4's (`plugin-security`, the other stage with
an ADR anchor), word for word, with the package name changed.

Measured on the built package (A3), after a full workspace build in
which this package was a cache miss (71 of 71 tasks, at `9b0d34213`,
which holds every source-line change): `files[]` is `dist`, `README.md`
and `CHANGELOG.md`, and the package is not private.

- The declaration files `dist/index.d.ts` and `dist/index.d.cts` carry
the rewritten docblocks at `engine.ts:354`, `:362`, `:529`, `:2112`,
`:2119`, `:2315`, `:2331`, `:5224`, `:7984` and
`flow-activation-store.ts:67` (e.g. `02b41232d` 4 times, `c5a7448d5`
twice, `ecdfc9411` and `7307191db` once each).
- The JS entries `dist/index.js` and `dist/index.cjs` carry the kept
comments at `engine.ts:2315`, `:2331`, `:3565`, `:3581`, `:5224`,
`:7984`, `notify-node.ts:449`, `suspended-run-store.ts:714` and
`sys-automation-run.object.ts:112`.
- The other rewrites are stripped by the bundle or sit in test files.
- Positive controls, unchanged lines beside the rewrites, land exactly
where their neighbours do: the line before `engine.ts:2112` and before
`:2119` once in each declaration file and 0 in the JS; the
`FlowActivationStore` docblock opener beside
`flow-activation-store.ts:67` once in each declaration file; and the
neighbours of three stripped rewrites (`crud-nodes.ts:438`,
`suspended-run-store.ts:952`, `template.ts:24`) 0 everywhere.
- A never-written negative phrase appears nowhere in `dist`.
- None of the rewritten numbers is left in `dist`; the one `objectstack-ai#11060` in
each JS entry is the kept runtime string at `template.ts:192`.

The two commits after `9b0d34213` add the changeset and change two
test-file comment lines, which the bundle does not include.

## Gates (final head `a602c4003`)

- **Citation judging, as CI runs it:** `pnpm check:issue-citations`
exits 0 (self-test, 114 cases, 8 batteries). `node
scripts/check-issue-citations.mjs` exits 0: the diff-scoped run judged
the 2 citations the change adds on its surface (the live `objectstack-ai#14095` and
`objectstack-ai#8287`, each on the line it already stood on), and both resolve.
- **Doc authoring:** `pnpm check:doc-authoring` exits 0 (the
sibling-package prose-id baseline holds, no growth).
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` at `a602c4003` (after a fresh fetch)
derived 63 commands. They are the 64 derived at dispatch less `pnpm
check:error-code-casing`, which the derivation now lists as a roster
family (below).
- Each ran with its exit code captured before any pipe, and all 63 exit
0; none exited 3.
- `--ran`, fed each command with its exit code, reports 63 run, 0 NOT
MEASURED (a derived zero), 0 unrun, and exits 0.
- A full `turbo run build` of `./packages/*` and `./packages/*/*` ran
first under the shared verify lock (71 of 71 tasks, exit 0), so no gate
hit an unbuilt workspace.
- The same 63 had also all exited 0 on `3511e88cc` before the referent
commit.
- **Roster families the derivation lists outside its commands** (their
rosters sit in directories this diff touches): `node
scripts/check-changeset-fixed.mjs`, `pnpm check:authz-resolver`, `pnpm
check:error-code-casing` and `pnpm check:filter-alias-parity`, each exit
0.
- **Tests and typecheck, under the verify lock, at `a602c4003`:**
- `pnpm --filter @objectstack/service-automation test`: 157 files pass
and 1,974 tests pass, every tracked test file under `src/`, the 16
touched ones included.
- `pnpm --filter @objectstack/service-automation typecheck` exits 0
(`tsc --noEmit` plus the test-layer check on `tsconfig.test.json`). `tsc
--listFiles` puts all 27 touched files in both programs.
- **Lint, as a proven narrowing:** eslint with inline config disabled,
over the 27 touched `.ts` files, gives 27 files, 0 errors and 0 warnings
(its `--format json` output). All 27 are in eslint's own population
(none reported ignored; a `dist` file, as the control, is ignored).
`eslint.config.mjs` never enables type-aware linting (no
`parserOptions.project`, as its own lines 327-328 state), so a comment
edit here cannot move the verdict on any untouched file. The repo-wide
`pnpm lint` is CI's run.
- **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of
the 28 changed files for control bytes finds none.

## Acceptance notes

- **The gate-invisible spellings, grepped as the claim asked** (objectstack-ai#20636).
At the base: `#N-word` 11 lines, of which the 5 `objectstack-ai#13398-class` lines
were dead and are rewritten here; the 6 left are live (`objectstack-ai#5912` twice,
`objectstack-ai#5048`, `objectstack-ai#5186`) or cross-repo (`hotcrm#548` twice). `#A/#B` 13 lines
of the number-slash-number shape, plus 6 lines where the slash follows
`ADR-0049` (`ADR-0049/objectstack-ai#1888`); every second number is live. `option #N`
none. URL-spelled none. So the claim's 11 / 13 / 0 / 0 hold, and at the
head the first is 6.
- **A sibling of the `option #N` position, dormant.**
`NON_CITATION_HEADS` also excuses a number after 「clause」, and
`suspended-run-store-consume-log-cause.test.ts:408` reads 「The
consequence clause objectstack-ai#6299 asked for」, a real citation of the live
`objectstack-ai#6299`. Across `packages/**/src` there are 4 such sites, all in test
files, a surface the gate defers anyway, so nothing dead hides there
today. Recorded for objectstack-ai#20636's family, not filed.
- **Two drifts left as they are, wording only.**
`notify-zero-delivery-visibility.integration.test.ts:72` still calls the
organization-less cron tick 「the shape production builds now」, written
before `ecdfc9411` landed; it carries no number and lost no referent
here. `suspended-run-store.test.ts:904` names
`tenancy.organizationField`, which `502f179cc` has since retired from
the authorable surface (stage 7 recorded the same drift in
`plugin-approvals`).
- **`update_record` still surfaces no `code`.** The corrected sentence
at `crud-nodes.ts:439-441` is now true that `engine.update` carries the
`DUPLICATE_RECORD` envelope while `update_record` does not surface it.
That is an observation with no reported pull, recorded here.
- **「The card」 phrases.** 134 comment lines in 50 files of this package
speak of 「the card」 or 「this card」. They carry no number and neither
instrument sees them. The two whose antecedent this diff would have
removed are handled above; the rest are unchanged, as in stages 8 to 13.
- **The census instrument did not truncate in this stage.** All three
enumerations read 187 pages at the newest frontier.
- **Anchors the next stages can reuse**, each checked here: `objectstack-ai#11060` →
`815585513`; `objectstack-ai#10243` → `02b41232d` (the measurement), `266436a7f` (the
ruling) or ADR-0126 §7.2 (the retirement), per line; `objectstack-ai#14419` →
`c5a7448d5`; `objectstack-ai#13648` → `7307191db`; `objectstack-ai#13681` → `18d816a50`; `objectstack-ai#17123` →
`ae6dcf6a4`; `objectstack-ai#14390` → `9d7f7259f`; `objectstack-ai#11504` → `f90e82024`; `objectstack-ai#10062` →
`fa5d137ab`; and the reused `objectstack-ai#13398` → `e238c79f0`, `objectstack-ai#16659` →
`ecdfc9411`, `objectstack-ai#16709` → `8c7cca1ce`, `objectstack-ai#8778` → `7901b2dd2`, `objectstack-ai#8707` →
`1408fe385`.
- **Base.** The branch is on `main` at `8acdae9d8`. `main` has since
moved five commits (`41dcf1188`, `96e724475`, `df67985b0`, `cfa931535`,
`5bed1f6ca`). None touches `packages/services/service-automation/src`,
`scripts/check-issue-citations.mjs` or `.changeset/config.json`, and
none is a path in this diff. Two of them move gate inputs
(`scripts/doc-authoring-prose-id.baseline.json`, whose change is
`driver-sql` rows only, and one `scripts/adr-anchors/` file for
`packages/spec`), so those families ran here against the base's copies;
this diff moves no code token and no runtime string, so nothing here can
interact with them. No merge was taken; the merge queue rebuilds on the
merged generation.

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

---------

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/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants