Skip to content

feat(spec): register FLOW_CONVERSION_CONFLICT in the ADR-0112 ledger (#9567 half 1) - #9582

Merged
os-steve merged 3 commits into
mainfrom
claude/issue-9567-flow-conversion-conflict-ledger
Aug 18, 2026
Merged

os-steve merged 3 commits into
mainfrom
claude/issue-9567-flow-conversion-conflict-ledger

Conversation

@os-steve

@os-steve os-steve commented Aug 18, 2026 •

Copy link
Copy Markdown
Collaborator

Part of #9567

This PR delivers half 1 only (the FLOW_CONVERSION_CONFLICT ledger admission). Half 2 (owd_widening_forbidden) is measurement-only below — no edits, no decision made — and the issue stays open for the maintainer's decision box.

Half 1 — FLOW_CONVERSION_CONFLICT ledger admission

Producer receipt — packages/metadata-protocol/src/protocol.ts:12674-12682:

const err = new Error(
    `[flow_conversion_conflict] ${request.type}/${request.name}: conversion refused — `
    + `'${first.token}' at ${first.path} is a live name in this environment `
    + `(${result.conflicts.length} conflict(s)). ${first.message}`
);
(err as any).code = 'FLOW_CONVERSION_CONFLICT';
(err as any).status = 409;

Already SCREAMING_SNAKE (ADR-0112 D1 satisfied), so this is an ordinary admission. Registered under @objectstack/metadata-protocol in packages/spec/src/api/error-code-ledger.zod.ts (alphabetically between DESTRUCTIVE_CHANGE and INVALID_METADATA), following the [#issue] … — em-dash clause comment style the #9411 landing (26e70fbff) set.

Message-prefix inconsistency (on-card, recorded per the dispatch note): the thrown message spells the code lowercase — [flow_conversion_conflict] — while the stamped .code is SCREAMING_SNAKE. The producer lives in packages/metadata-protocol, outside this ledger PR's surface, so left untouched; noted in the ledger comment for the next reader.

What the admission required, and what it did NOT (at dispatch time):

Half 2 — owd_widening_forbidden: MEASUREMENT ONLY (no edits made)

Question: does the existing #9106/#9232 demote ruling's scope already cover the REST meta door (PUT /api/v1/meta/object/:name)?

Answer: No — genuinely outside the ruled scope, confirmed by both the ADR text and the code.

  1. ADR-0112's own amendment text states the scope explicitly (docs/adr/0112-error-code-vocabulary-and-ledger.md:122):

    Scope. This amendment rules the doors served by resolveThrownHttpError — the runtime dispatcher exits … and the direct-mount REST package registrar. packages/rest's flat sendThrownError dialect, which puts code at the body's top level rather than in error.code, is a different envelope position and is out of scope here; it is measured and filed as finding: packages/rest's flat sendThrownError still puts a thrown error's code on the wire un-narrowed — ADR-0112's closure does not reach that door #9232 … Read that card before quoting "every door" at the flat dialect.

  2. The [Decision] The dispatcher's error.code has a limb authored by TENANTS at runtime — registration cannot close it, and ADR-0112 does not say what should happen there #9106 landing commit (2f65b1b42) touches zero files under packages/rest/ — only packages/runtime, packages/types, packages/spec (HttpDispatcher.errorFromThrown, dispatcher-plugin's errorResponseBase, endpoint-executor's endpointErrorAnswer).

  3. Traced the actual route: PUT /api/v1/meta/:type/:name (packages/rest/src/rest-server.ts:4949-5121) catches into handleRouteError (packages/rest/src/error-response.ts:1380), which calls resolveErrorResponse (same file, :1181). That function reads error.status/error.code directly and builds { error: safeMsg, code: error.code, … } — code at the body's top level, exactly the "flat sendThrownError dialect" the ADR names as out of scope. It never calls resolveThrownHttpError. (resolveThrownHttpError is used only in http-dispatcher.ts, dispatcher-plugin.ts, endpoint-executor.ts, package-routes.ts, and the hono adapter — never in error-response.ts.)

  4. packages/rest/src/meta-object-owd-gate.test.ts:391,412 pins exactly this: expect(res._json?.code).toBe('owd_widening_forbidden') — the raw lowercase code, unnarrowed, at the top level — confirming the flat dialect is what's live on this route today. This corrects the [finding] check:dispatcher-error-vocabulary cannot see a lowercase thrown code — plugin-security's live owd_widening_forbidden was never swept #9460 issue body's claim that the flat door already demotes it (the Two live wire codes are outside the ADR-0112 vocabulary, newly measured: FLOW_CONVERSION_CONFLICT (409) and owd_widening_forbidden (403) #9567 issue's own ⚠️ paragraph already flagged this; this PR independently re-derives the same conclusion from the ADR text, the [Decision] The dispatcher's error.code has a limb authored by TENANTS at runtime — registration cannot close it, and ADR-0112 does not say what should happen there #9106 diff, and the route's own code+test).

  5. #9232 ("packages/rest's flat sendThrownError still puts a thrown error's code on the wire un-narrowed") is open, is exactly this question, and already has an in-flight PR (#9459, open) attempting to close it as its own separate decision.

Conclusion: the REST meta door is genuinely outside the #9106 ruled scope, not merely un-swept within it. Bringing it under the demote rule is not declared-contract restoration — it's the still-undecided #9232/#9459 work. The A/B spelling fork for owd_widening_forbidden (rename to OWD_WIDENING_FORBIDDEN vs. keep lowercase and let a future flat-door demote answer it) is correctly left to the maintainer's decision inbox, per the PM's claim comment. No edits made for half 2; its pending-registration row is untouched by the addendum below (see there).

Reverse verification (half 1, at dispatch time)

Committed the ledger row, then git checkout HEAD~1 -- packages/spec/src/api/error-code-ledger.zod.ts to revert it and re-ran check:dispatcher-error-vocabulary:

  • Before revert (row present): 293 registered codes (240 ledger + 53 standard), 0 awaiting a ledger entry.
  • After revert (row absent): 292 registered codes (239 ledger + 53 standard), 0 awaiting a ledger entry — unchanged pass/fail, exactly as predicted (the site is invisible to the current scan shape either way; only the registered-code count moves).

Restored the row (git checkout HEAD -- …), re-confirmed 293 registered codes, 0 awaiting a ledger entry, error-code-ledger.test.ts 17/17 passing.

Tests / gates (initial pass, at head 57ac54b8d)

  • pnpm --filter @objectstack/spec test — 409 test files / 10937 tests passed (full package run; the ledger-scoped error-code-ledger.test.ts also re-run standalone: 17/17 passed).
  • pnpm --filter @objectstack/spec typecheck — passed (tsc --noEmit + scripts + test-typecheck, debt ledger unchanged at 55 files / 263 errors).
  • node scripts/check-dispatcher-error-vocabulary.mjs — OK, 293 registered codes, 0 pending.
  • node scripts/check-error-code-casing.mjs — OK, no lowercase codes in 4212 files.
  • node scripts/check-cross-package-test-inputs.mjs — OK.
  • node scripts/check-nul-bytes.mjs — OK.
  • pnpm --filter @objectstack/lint run check:doc-formula-expressions — OK (24 self-test cases + live pass).
  • pnpm --filter @objectstack/spec run check:empty-state / check:liveness / check:strictness-ledger / check:variant-docs — all OK, unaffected.
  • node scripts/docs-audit/check-affected-docs.mjs — self-test OK.
  • pnpm check:type-source-resolution, pnpm check:spec-parsed-alias, pnpm check:merge-driver (self-tests) — all OK.
  • pnpm --filter @objectstack/spec run check:docs — OK after gen:schema + gen:docs.
  • pnpm --filter @objectstack/spec run check:generated — all 13 generated artifacts up to date (ran after pnpm --filter @objectstack/spec build).
  • pnpm --filter @objectstack/spec run check:authorable-surface — OK (unrelated informational note about authorable-surface.base.json vs. the current anchor commit, shallow-checkout limitation, pre-existing).

Addendum — merge-queue interaction, fixed and re-verified (head 8a3b4b4ae)

#9460 landed on main as PR #9573 (c8e85fc5e) after this PR's CI ran, adding a pending-registration row for FLOW_CONVERSION_CONFLICT at packages/runtime/src/dispatcher-error-vocabulary.ts:365. The merge group (this PR's registration + main's new pending row) then tripped error-envelope.conformance.test.ts's #8087 pin: a pending-registration row for a code that is now registered fails that gate in the direction its own docs describe ("a pending-registration row whose code is registered fails the gate in the other direction").

Fix: merged origin/main into this branch (clean, no conflicts — fb0a442f0), then discharged FLOW_CONVERSION_CONFLICT's row per the exact #8846/#9246 precedent (5d4f3d5d8): delete the discharged row, fold a one-line note into the running ratchet log at the top of UNREGISTERED_CODE_SITES. owd_widening_forbidden — the #9460 batch's other new row — stays pending and untouched: its lowercase spelling is a naming decision for the maintainer (half 2 above), not a plain admission.

Local re-verification, all green:

  • node scripts/check-dispatcher-error-vocabulary.mjs — PENDING_LEDGER_REGISTRATION dropped 2 → 1 as expected (only owd_widening_forbidden remains); 293 registered codes, 17 unregistered code-stamping site(s), all classified.
  • error-envelope.conformance.test.ts (the The dispatcher emits unregistered error.code values verbatim — three suites pin bodies that ApiErrorSchema would reject #8087 pin, packages/runtime) — 51/51 passing.
  • error-code-ledger.test.ts (packages/spec) — 17/17 passing.
  • pnpm --filter @objectstack/spec run check:generated — all 13 artifacts still up to date after the merge (ran after rebuilding @objectstack/spec's dist/).
  • pnpm --filter @objectstack/spec run check:docs — OK, no doc drift from the merge.
  • node scripts/check-nul-bytes.mjs — OK.

Pushed as a plain (non-force) push: 57ac54b8d..8a3b4b4ae. New head: 8a3b4b4ae.


Generated by Claude Code

…9567 half 1)

`saveMetaItem`'s flow-conversion rename guard (protocol.ts:12674-12682) has
thrown a live 409 FLOW_CONVERSION_CONFLICT since ADR-0078 landed --
already SCREAMING_SNAKE, so this is an ordinary ADR-0112 D3 ledger
admission under @objectstack/metadata-protocol. It was invisible to
check:dispatcher-error-vocabulary's scan because the site stamps the code
through a cast rather than the bare-identifier `assign` shape the scan
matched.

Regenerated the two docs that mirror the ledger (content/docs/references/api/
contract.mdx, error-code-ledger.mdx) via gen:schema + gen:docs; check:generated
and check:docs both confirm no other artifact moved.

Half 2 of #9567 (`owd_widening_forbidden`) is measurement-only in this PR --
see the PR description -- so this PR does not close the issue.

Part of #9567
@github-actions

github-actions Bot commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/runtime, @objectstack/spec, touching 2 documentable anchor(s).

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

  • content/docs/api/error-catalog.mdx (via ERROR_CODE_LEDGER (symbol))
  • content/docs/api/error-handling-server.mdx (via ERROR_CODE_LEDGER (symbol))
  • content/docs/kernel/contracts/data-engine.mdx (via ERROR_CODE_LEDGER (symbol))

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

  • content/docs/releases/v17.mdx (via ERROR_CODE_LEDGER (symbol))

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
  • 4 name(s) were too generic to anchor anything (single lowercase words)

Coarse fallback — 119 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 e8dba8a8a2bb680eaa1ee62d91b0bc402bec9094 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json e8dba8a8a2bb680eaa1ee62d91b0bc402bec9094

⚠️ 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 e8dba8a8a2bb680eaa1ee62d91b0bc402bec9094 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32140228186 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (3/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/error-envelope.conformance.test.ts�[2m > �[22m#8087 — every code the dispatcher door can emit is parsed against ApiErrorSchema�[2m > �[22mevery pending code is still unre
    

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 6 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 18, 2026
…on row (#9567)

#9460 landed on main (PR #9573) while this branch's registration PR was in
the merge queue, adding a pending-registration row for
FLOW_CONVERSION_CONFLICT to dispatcher-error-vocabulary.ts's
UNREGISTERED_CODE_SITES. That collided with this branch's ledger admission
of the same code -- a registered code with a pending-registration row still
on file fails the #8087 conformance gate in the direction the row's own
docs describe ("a pending-registration row whose code is registered fails
the gate in the other direction").

Ratchets the row out following the #8846/#9246 precedent exactly: delete
the discharged row, fold a one-line note into the running log at the top of
UNREGISTERED_CODE_SITES. owd_widening_forbidden -- the #9460 batch's other
new row -- stays pending; its lowercase spelling is a naming decision for
the maintainer (#9567 half 2), not a plain admission, and is untouched.

node scripts/check-dispatcher-error-vocabulary.mjs: PENDING_LEDGER_REGISTRATION
2 -> 1 (only owd_widening_forbidden remains). error-envelope.conformance.test.ts
(the #8087 pin): 51/51 passing. error-code-ledger.test.ts: 17/17 passing.
check:generated: all 13 artifacts still up to date after the merge.

Part of #9567
@os-steve
os-steve enabled auto-merge August 18, 2026 13:51
@os-steve
os-steve disabled auto-merge August 18, 2026 13:52
@os-steve
os-steve added this pull request to the merge queue Aug 18, 2026
Merged via the queue into main with commit 07026cf Aug 18, 2026
30 checks passed
@os-steve
os-steve deleted the claude/issue-9567-flow-conversion-conflict-ledger branch August 18, 2026 14:27
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 tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants