Skip to content

Commit 19db79b

Browse files
committed
docs(spec): state the prior behaviour truthfully — no diagnostic, not a warning
The changeset said an input the artifact pass newly refuses "previously composed with only a warning". Measured on main, that class of input produces no diagnostic at all: `composeStacks` calls `validateCrossReferences` zero times and `console.warn` zero times, and its one `warnMalformedCollectionKey` site fires on `declared.length !== arrays.length` — a collection key that is not an array. The newly-refused inputs carry well-formed arrays with a dangling reference, so that condition never holds. The clause conflated two populations and handed an upgrading reader a false self-test ("we never saw a warning, so this is not us") in text that ships verbatim into CHANGELOG.md. It now reads "where it previously composed with no diagnostic at all — the existing non-array warning covers a malformed collection key, not a dangling reference". Same stroke, the precision the review asked for: the no-op half of the invariant holds for an input that passed the strict parse AND did not opt in. An opted-in input also passed that parse but resolved against its own objects plus the names it listed, and checking a listed name against the real artifact is what this pass is for — so it can fail here by design. The qualifier is added in the changeset, in the `collectArtifactCrossReferenceErrors` docstring and in the fixture file's header, which carried the same sentence. Text only: no schema, no rule, no fixture and no docs page changes, and the changeset level stays `minor`. Claude-Session: https://claude.ai/code/session_01T3YsvpK1PvYf9n1YUhYP6W Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0c21631 commit 19db79b

3 files changed

Lines changed: 17 additions & 9 deletions

File tree

‎.changeset/artifact-scoped-cross-reference.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ Nothing else widens. `hooks[].object` and an app's own `navigation` `objectName`
2020

2121
The refusal moved rather than disappearing: in a composition of **two or more** packages, `composeStacks` now re-checks those two classes over the composed artifact, so a name `artifactObjects` claims and no package in the artifact defines is refused there, with the same `STACK_CROSS_REFERENCE_INVALID` code, the same `422`, and the same per-finding message. Only the header differs, naming the pass that refused it. `composeStacks` returns a single input untouched, so a one-package composition does not re-check the claim.
2222

23-
**What that changes about which inputs `composeStacks` accepts.** `defineStack` itself is unchanged for a stack that does not pass `artifactObjects` — every single-package app validates exactly as before. `composeStacks` is not: it applies the two artifact-scoped rules to **every** input carrying objects, not only the ones that opted in. For an input that passed the strict `defineStack` parse that is a no-op, so such an input cannot newly fail. For an input that **bypassed** the strict parse it is not: `defineStack(config, { strict: false })` returns before cross-reference validation runs, and a hand-built stack object never enters it, so these two rules have never been applied to it. Such an input carrying a dangling `permissions[].objects` key or `data[].object` is now refused at composition where it previously composed with only a warning. If you compose unparsed stacks, that is the one behavioural change to expect; a malformed `permissions` / `data` on such an input is still skipped with the existing non-array warning rather than raising.
23+
**What that changes about which inputs `composeStacks` accepts.** `defineStack` itself is unchanged for a stack that does not pass `artifactObjects` — every single-package app validates exactly as before. `composeStacks` is not: it applies the two artifact-scoped rules to **every** input carrying objects, not only the ones that opted in. For an input that passed the strict `defineStack` parse **and did not opt in**, that is a no-op, so such an input cannot newly fail — its references were already resolved against its own objects, which are a subset of the composed set. (An input that *did* opt in also passed the strict parse, but it resolved against its own objects plus the names it listed; checking a listed name against the real artifact is what this pass is for, so it can fail here by design.) For an input that **bypassed** the strict parse the no-op argument does not apply at all: `defineStack(config, { strict: false })` returns before cross-reference validation runs, and a hand-built stack object never enters it, so these two rules have never been applied to it. Such an input carrying a dangling `permissions[].objects` key or `data[].object` is now refused at composition where it previously composed with no diagnostic at all — the existing non-array warning covers a malformed collection key, not a dangling reference. If you compose unparsed stacks, that is the one behavioural change to expect, and there is no earlier warning to have noticed it by; a malformed `permissions` / `data` on such an input is still skipped with that non-array warning rather than raising.

‎packages/spec/src/stack-artifact-crossref.test.ts‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,16 @@
4444
* The compatibility statement that holds is not "nothing can newly fail". It
4545
* is two statements:
4646
*
47-
* - an input that passed the strict `defineStack` parse cannot newly fail at
48-
* composition — its references already resolved against its own objects, a
49-
* subset of the composed set;
47+
* - an input that passed the strict `defineStack` parse AND did not opt in
48+
* cannot newly fail at composition — its references already resolved against
49+
* its own objects, a subset of the composed set. An opted-in input also
50+
* passed that parse, but it resolved against its own objects plus the names
51+
* it listed, and checking a listed name is what this pass is for — the
52+
* `ArtifactPass` block below is that refusal;
5053
* - an input that BYPASSED the strict parse (`strict: false`, a hand-built
5154
* stack object) is checked for these two rules at composition for the FIRST
5255
* time, and a dangling reference in it is refused where it previously
53-
* composed.
56+
* composed with no diagnostic at all.
5457
*
5558
* The second is a narrowing, it is deliberate, and the three blocks at the
5659
* bottom of this file pin it as declared behaviour: the refusals themselves,

‎packages/spec/src/stack.zod.ts‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3804,10 +3804,15 @@ function assemblePackageBody(stack: ObjectStackDefinition): AssembledPackageBody
38043804
* narrower than "nothing can newly fail", and the narrower statement is the
38053805
* true one:
38063806
*
3807-
* - **An input that passed the strict `defineStack` parse cannot newly fail
3808-
* here.** Its references already resolved against its own objects, and its
3809-
* own objects are a subset of the composed set — so re-running the two rules
3810-
* over a superset is a no-op.
3807+
* - **An input that passed the strict `defineStack` parse and did NOT opt in
3808+
* cannot newly fail here.** Its references already resolved against its own
3809+
* objects, and its own objects are a subset of the composed set — so
3810+
* re-running the two rules over a superset is a no-op. The qualifier is not
3811+
* decoration: an input that DID opt in also passed the strict parse, but its
3812+
* references resolved against its own objects PLUS the names it listed, and
3813+
* a listed name is exactly what this pass exists to check. Redeeming that
3814+
* claim against the real artifact is the whole point, so an opted-in input
3815+
* can and does fail here — that is the `ArtifactPass` fixture, not a gap.
38113816
* - **An input that BYPASSED the strict parse is checked for these two rules
38123817
* here for the first time.** `defineStack(config, { strict: false })` returns
38133818
* before {@link validateCrossReferences} runs at all, and a hand-built stack

0 commit comments

Comments
 (0)