Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/register-flow-conversion-conflict.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
---
"@objectstack/spec": patch
---

Register `FLOW_CONVERSION_CONFLICT` (409) in the ADR-0112 error-code ledger under
`@objectstack/metadata-protocol` (#9567). The code was already live on the wire —
`saveMetaItem`'s flow-conversion rename guard (`protocol.ts`) has thrown it since
ADR-0078 landed, already SCREAMING_SNAKE — but was invisible to
`check:dispatcher-error-vocabulary`'s scan because the site stamps it through a
cast (`(err as any).code = 'FLOW_CONVERSION_CONFLICT'`) rather than the bare-
identifier `assign` shape the scan matched at the time. This is an ordinary,
additive admission: no accept/reject behavior, no producer, and no wire shape
changes.
3 changes: 2 additions & 1 deletion content/docs/references/api/contract.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,7 +27,7 @@ const result = ApiErrorSchema.parse(data);

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **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) |
| **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) |
| **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) |
| **message** | `string` | ✅ | Readable error message |
| **category** | `string` | optional | Error category (e.g. validation, authorization) |
Expand DownExpand Up@@ -179,6 +179,7 @@ const result = ApiErrorSchema.parse(data);
* `FILE_NOT_FOUND`
* `FILTER_TOKEN_UNKNOWN`
* `FILTER_TOKEN_UNRESOLVED`
* `FLOW_CONVERSION_CONFLICT`
* `FLOW_DISABLED`
* `FLOW_FAILED`
* `FLOW_NO_START_NODE`
Expand Down
1 change: 1 addition & 0 deletions content/docs/references/api/error-code-ledger.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -284,6 +284,7 @@ const result = ErrorCode.parse(data);
* `FILE_NOT_FOUND`
* `FILTER_TOKEN_UNKNOWN`
* `FILTER_TOKEN_UNRESOLVED`
* `FLOW_CONVERSION_CONFLICT`
* `FLOW_DISABLED`
* `FLOW_FAILED`
* `FLOW_NO_START_NODE`
Expand Down
34 changes: 14 additions & 20 deletions packages/runtime/src/dispatcher-error-vocabulary.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -216,7 +216,12 @@ export const UNREGISTERED_CODE_SITES: readonly UnregisteredCodeSite[] = [
// UNIQUE_SCOPE_CONFIRMATION_REQUIRED at the marketplace install seam's
// `plugin-route` door (stamped through a constant in an object literal, the
// shape `objlit` could not see), and #9246 registered it under
// `@objectstack/cloud-connection`, ratcheting that row out the same way. A
// `@objectstack/cloud-connection`, ratcheting that row out the same way.
// #9460's further-widened scan then reported two sites at once —
// `FLOW_CONVERSION_CONFLICT` and `owd_widening_forbidden` — and #9567
// registered the former under `@objectstack/metadata-protocol`, ratcheting
// only that row out; `owd_widening_forbidden`'s lowercase spelling is a
// naming decision, not a plain admission, so it stays pending below. A
// future unswept producer lands here as an `unclassified-site` finding and
// gets a new row (then a spec-lane registration, then the row comes out
// again). ──
Expand DownExpand Up@@ -357,25 +362,14 @@ export const UNREGISTERED_CODE_SITES: readonly UnregisteredCodeSite[] = [
},

// ── pending registration [#9460]: found by the widened scan ────────────
// Both rows are the deliverable of #9460, not a regression: the scan could
// not SEE either site before it learned the two stamp positions below, so
// "no finding" meant "not looked at", which is the failure this gate
// exists to prevent.
{
code: 'FLOW_CONVERSION_CONFLICT',
file: 'packages/metadata-protocol/src/protocol.ts',
shape: 'assign',
door: 'rest',
verdict: 'pending-registration',
why:
'A live 409 from the metadata write path: the conversion pass refuses a body whose token is a ' +
"live name in the environment, and stamps `(err as any).code = 'FLOW_CONVERSION_CONFLICT'` " +
'beside `status = 409` before throwing. [#9460] The `assign` shape demanded a BARE identifier ' +
'to the left of `.code`, and this site writes a cast — `(err as any)` puts a `)` exactly where ' +
'the anchor wanted a word character — so the single most common way this repo stamps a code ' +
'onto a constructed error was invisible in the shape named for it. Registering it is the ' +
"`packages/spec` lane's call (#8846's batch); this row records the measurement.",
},
// This row is the deliverable of #9460, not a regression: the scan could
// not SEE the site before it learned the stamp shape below (a
// code-carrying helper), so "no finding" meant "not looked at", which is
// the failure this gate exists to prevent. Its sibling from the same
// #9460 batch, `FLOW_CONVERSION_CONFLICT`, ratcheted out via #9567 (see
// the running log above); this one stays pending because admitting it AS
// SPELLED is what ADR-0112 D1 forbids — the rename-or-keep-the-#9106-demote
// call is the spec lane's and is not resolved here.
{
code: 'owd_widening_forbidden',
file: 'packages/plugins/plugin-security/src/object-posture-gate.ts',
Expand Down
13 changes: 13 additions & 0 deletions packages/spec/src/api/error-code-ledger.zod.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -377,6 +377,19 @@ export const ERROR_CODE_LEDGER = {
'COMMIT_NOT_FOUND',
'CONCURRENT_UPDATE',
'DESTRUCTIVE_CHANGE', // change would drop data; needs an explicit opt-in
// [#9567] ADR-0078's rename guard refuses a metadata write whose `type`/
// `name` token is already a LIVE name owned by something else in the
// environment — persisting the un-renamed body would mint exactly the row
// that guard exists to prevent (`saveMetaItem`, `protocol.ts`, same posture
// as `duplicatePackage` / #4454). 409, not 422: the body may be perfectly
// valid — the refusal comes from environment state, so resubmitting the
// same body cannot help. Surfaced by the widened
// `check:dispatcher-error-vocabulary` scan (#9460, half 1); NOTE the
// producer's message prefix spells the code lowercase
// (`[flow_conversion_conflict]`) while the wire stamp is SCREAMING_SNAKE —
// a human-log inconsistency worth a glance, left as-is because the
// producer is outside this ledger's surface.
'FLOW_CONVERSION_CONFLICT',
'INVALID_METADATA',
'INVALID_REQUEST',
'ITEM_LOCKED', // _lock refuses the write/delete (ADR-0010 §3.3)
Expand Down
Loading