Skip to content

Commit 94d9672

Browse files
committed
chore(changeset): composeStacks refusal envelopes
Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
1 parent aef0da4 commit 94d9672

1 file changed

Lines changed: 57 additions & 0 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec): every `composeStacks` conflict refusal carries an ADR-0112 envelope — six new `STACK_COMPOSE_*` codes beside the `defineStack` family
6+
7+
`composeStacks` refuses six authored-entity conflicts, and until now every one of them threw
8+
`new Error(message)` with `code` and `status` both `undefined`. The `defineStack` family in the
9+
same file has carried the envelope since #15963, so `packages/spec/src/stack.zod.ts` held two
10+
refusal families that are the same thing to an author — a stack refused at authoring time,
11+
through the same callers — and two different things to a consumer branching on `error.code`.
12+
13+
Every message in the family carries the literal `composeStacks conflict:` prefix, which is how it
14+
is located: FIVE of the six raise inside helper functions 300-800 lines above `composeStacks`'
15+
own body, so reading the function the defect is named after finds one of them.
16+
17+
| refusal | raised by | code |
18+
| :--- | :--- | :--- |
19+
| a single-valued top-level key declared with different values by two stacks | `composeSingleValue` | `STACK_COMPOSE_KEY_CONFLICT` |
20+
| `functions` authored in the map form by one stack, the array form by another | `composeFunctions` | `STACK_COMPOSE_FUNCTIONS_SHAPE_CONFLICT` |
21+
| two stacks defining one handler name | `composeFunctions` | `STACK_COMPOSE_FUNCTION_CONFLICT` |
22+
| under `objectConflict: 'merge'`, an object-level collection other than `fields` declared differently | `refuseUnmergeableCollections` | `STACK_COMPOSE_COLLECTION_CONFLICT` |
23+
| the same object name in two stacks under the default `objectConflict: 'error'` | `mergeObjects` | `STACK_COMPOSE_OBJECT_CONFLICT` |
24+
| a cross-stack action key collision | `collectComposedActionKeyCollisions` | `STACK_COMPOSE_ACTION_KEY_COLLISION` |
25+
26+
Each carries `status: 422` — an unprocessable authored entity, not a server fault — and the
27+
findings the site collected in `issues`, one entry per finding. **Message text is byte-for-byte
28+
unchanged at every site**: this adds the machine-readable half, it rewords no sentence, and the
29+
message pins across the repo read the prose they always did.
30+
31+
One code per refusal site rather than a shared `STACK_COMPOSE_CONFLICT` catch-all — the
32+
granularity the `defineStack` family landed with, and the granularity the ADR-0112 ledger's
33+
boot-refusal class already had before it. The `STACK_COMPOSE_*` spelling says what the
34+
per-stack family's spellings cannot: the defect is a disagreement BETWEEN stacks, each of which
35+
is legal on its own, so the fix is in the composition rather than in one malformed stack.
36+
`STACK_CROSS_REFERENCE_INVALID` stays the deliberate exception in the other direction — its
37+
per-stack and artifact passes share one code because they are one rule family over two scopes.
38+
39+
All six are registered in `ERROR_CODE_LEDGER` under `@objectstack/spec`, under the ruling that
40+
every code shipped in `dist` is the published face, door or no door. No wire door raises them:
41+
`composeStacks` runs at authoring and boot time, and the reading was re-measured here — zero
42+
`composeStacks` call sites under `packages/runtime/src` + `packages/rest/src` (7 non-test
43+
occurrences, all doc comments or message prose in one file), with `defineStack` lighting the
44+
same probe 31 times across 8 files as the positive control.
45+
46+
Not narrowed: `composeStacks` accepts and refuses exactly the inputs it did before, and no export
47+
changes — the error classes stay module-local, as every member of the `defineStack` family is,
48+
because `packages/spec/src/index.ts` re-exports the module with `export *` and the ADR-0112
49+
contract is the `code` / `status` pair read structurally.
50+
51+
⛔ The seventh bare `Error` in that file is deliberately untouched:
52+
`composeStacks internal error: no source stack recorded for composed object …` is the code
53+
discovering its own bookkeeping is inconsistent, not an authored entity being refused. Filing it
54+
at 422 would tell an author their stack is invalid when the defect is ours. Whether it takes a
55+
500-class envelope of its own is a separate decision.
56+
57+
Clause-②: yes

0 commit comments

Comments
 (0)