Skip to content

Commit 07026cf

Browse files
os-steveclaude
andauthored
feat(spec): register FLOW_CONVERSION_CONFLICT in the ADR-0112 ledger (#9567 half 1) (#9582)
* feat(spec): register FLOW_CONVERSION_CONFLICT in the ADR-0112 ledger (#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 * fix(runtime): discharge FLOW_CONVERSION_CONFLICT's pending-registration 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 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 161b5ce commit 07026cf

5 files changed

Lines changed: 43 additions & 21 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
Register `FLOW_CONVERSION_CONFLICT` (409) in the ADR-0112 error-code ledger under
6+
`@objectstack/metadata-protocol` (#9567). The code was already live on the wire —
7+
`saveMetaItem`'s flow-conversion rename guard (`protocol.ts`) has thrown it since
8+
ADR-0078 landed, already SCREAMING_SNAKE — but was invisible to
9+
`check:dispatcher-error-vocabulary`'s scan because the site stamps it through a
10+
cast (`(err as any).code = 'FLOW_CONVERSION_CONFLICT'`) rather than the bare-
11+
identifier `assign` shape the scan matched at the time. This is an ordinary,
12+
additive admission: no accept/reject behavior, no producer, and no wire shape
13+
changes.

‎content/docs/references/api/contract.mdx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const result = ApiErrorSchema.parse(data);
2727

2828
| Property | Type | Required | Description |
2929
| :--- | :--- | :--- | :--- |
30-
| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +285 more>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) |
30+
| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +286 more>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) |
3131
| **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112, #9106) |
3232
| **message** | `string` | ✅ | Readable error message |
3333
| **category** | `string` | optional | Error category (e.g. validation, authorization) |
@@ -179,6 +179,7 @@ const result = ApiErrorSchema.parse(data);
179179
* `FILE_NOT_FOUND`
180180
* `FILTER_TOKEN_UNKNOWN`
181181
* `FILTER_TOKEN_UNRESOLVED`
182+
* `FLOW_CONVERSION_CONFLICT`
182183
* `FLOW_DISABLED`
183184
* `FLOW_FAILED`
184185
* `FLOW_NO_START_NODE`

‎content/docs/references/api/error-code-ledger.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ const result = ErrorCode.parse(data);
284284
* `FILE_NOT_FOUND`
285285
* `FILTER_TOKEN_UNKNOWN`
286286
* `FILTER_TOKEN_UNRESOLVED`
287+
* `FLOW_CONVERSION_CONFLICT`
287288
* `FLOW_DISABLED`
288289
* `FLOW_FAILED`
289290
* `FLOW_NO_START_NODE`

‎packages/runtime/src/dispatcher-error-vocabulary.ts‎

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,12 @@ export const UNREGISTERED_CODE_SITES: readonly UnregisteredCodeSite[] = [
216216
// UNIQUE_SCOPE_CONFIRMATION_REQUIRED at the marketplace install seam's
217217
// `plugin-route` door (stamped through a constant in an object literal, the
218218
// shape `objlit` could not see), and #9246 registered it under
219-
// `@objectstack/cloud-connection`, ratcheting that row out the same way. A
219+
// `@objectstack/cloud-connection`, ratcheting that row out the same way.
220+
// #9460's further-widened scan then reported two sites at once —
221+
// `FLOW_CONVERSION_CONFLICT` and `owd_widening_forbidden` — and #9567
222+
// registered the former under `@objectstack/metadata-protocol`, ratcheting
223+
// only that row out; `owd_widening_forbidden`'s lowercase spelling is a
224+
// naming decision, not a plain admission, so it stays pending below. A
220225
// future unswept producer lands here as an `unclassified-site` finding and
221226
// gets a new row (then a spec-lane registration, then the row comes out
222227
// again). ──
@@ -357,25 +362,14 @@ export const UNREGISTERED_CODE_SITES: readonly UnregisteredCodeSite[] = [
357362
},
358363

359364
// ── pending registration [#9460]: found by the widened scan ────────────
360-
// Both rows are the deliverable of #9460, not a regression: the scan could
361-
// not SEE either site before it learned the two stamp positions below, so
362-
// "no finding" meant "not looked at", which is the failure this gate
363-
// exists to prevent.
364-
{
365-
code: 'FLOW_CONVERSION_CONFLICT',
366-
file: 'packages/metadata-protocol/src/protocol.ts',
367-
shape: 'assign',
368-
door: 'rest',
369-
verdict: 'pending-registration',
370-
why:
371-
'A live 409 from the metadata write path: the conversion pass refuses a body whose token is a ' +
372-
"live name in the environment, and stamps `(err as any).code = 'FLOW_CONVERSION_CONFLICT'` " +
373-
'beside `status = 409` before throwing. [#9460] The `assign` shape demanded a BARE identifier ' +
374-
'to the left of `.code`, and this site writes a cast — `(err as any)` puts a `)` exactly where ' +
375-
'the anchor wanted a word character — so the single most common way this repo stamps a code ' +
376-
'onto a constructed error was invisible in the shape named for it. Registering it is the ' +
377-
"`packages/spec` lane's call (#8846's batch); this row records the measurement.",
378-
},
365+
// This row is the deliverable of #9460, not a regression: the scan could
366+
// not SEE the site before it learned the stamp shape below (a
367+
// code-carrying helper), so "no finding" meant "not looked at", which is
368+
// the failure this gate exists to prevent. Its sibling from the same
369+
// #9460 batch, `FLOW_CONVERSION_CONFLICT`, ratcheted out via #9567 (see
370+
// the running log above); this one stays pending because admitting it AS
371+
// SPELLED is what ADR-0112 D1 forbids — the rename-or-keep-the-#9106-demote
372+
// call is the spec lane's and is not resolved here.
379373
{
380374
code: 'owd_widening_forbidden',
381375
file: 'packages/plugins/plugin-security/src/object-posture-gate.ts',

‎packages/spec/src/api/error-code-ledger.zod.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,19 @@ export const ERROR_CODE_LEDGER = {
377377
'COMMIT_NOT_FOUND',
378378
'CONCURRENT_UPDATE',
379379
'DESTRUCTIVE_CHANGE', // change would drop data; needs an explicit opt-in
380+
// [#9567] ADR-0078's rename guard refuses a metadata write whose `type`/
381+
// `name` token is already a LIVE name owned by something else in the
382+
// environment — persisting the un-renamed body would mint exactly the row
383+
// that guard exists to prevent (`saveMetaItem`, `protocol.ts`, same posture
384+
// as `duplicatePackage` / #4454). 409, not 422: the body may be perfectly
385+
// valid — the refusal comes from environment state, so resubmitting the
386+
// same body cannot help. Surfaced by the widened
387+
// `check:dispatcher-error-vocabulary` scan (#9460, half 1); NOTE the
388+
// producer's message prefix spells the code lowercase
389+
// (`[flow_conversion_conflict]`) while the wire stamp is SCREAMING_SNAKE —
390+
// a human-log inconsistency worth a glance, left as-is because the
391+
// producer is outside this ledger's surface.
392+
'FLOW_CONVERSION_CONFLICT',
380393
'INVALID_METADATA',
381394
'INVALID_REQUEST',
382395
'ITEM_LOCKED', // _lock refuses the write/delete (ADR-0010 §3.3)

0 commit comments

Comments
 (0)