Commit f1a1028
* feat(runtime): every top-level collection read gains a `packages[]` path
ADR-0130 D4 / option B, reader program 2/4 (#15005). Nothing here changes what
any command emits — the artifact stays additive; each reader simply learns to
find its collections under `packages[]` as well as at the top level.
`resolveArtifactCollections` (`@objectstack/core`, beside
`resolveArtifactPackageOrder`) is the one resolution: top level first and
whole, then every package body's items the top level did not already claim, in
`resolveArtifactPackageOrder`'s order. On a bundle without `packages[]` it
returns the argument itself, so every single-package artifact and every
`defineStack()` config is bit-identical.
Readers taught: `AppPlugin` (datasources, datasourceMapping, objects, jobs,
seed data, translations, the ADR-0057 security block, the job handler
context's bundle), the three exported collectors, `mergeRuntimeModule`'s
declaration half, `createStandaloneStack`'s surfaced keys, and
`resolve-project-database`'s project-DB tier.
#15004's acceptance pin shrinks from 24 ledgered losses to 1 — the from-source
`appSecurityPluginOptions` row card #15007 owns.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
* chore(changeset): the option-B reader half for @objectstack/core and @objectstack/runtime
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
* test(runtime): type the option-B job handler double's argument
`check:test-typecheck` reads packages/runtime/tsconfig.test.json, which
includes this file — the mock's inferred `() => Promise<undefined>` refused
the `(jobCtx) => …` implementation `mockImplementation` supplies.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
* refactor(runtime): keep the option-B collection reader package-private, and refuse mixed spellings
Contract review of PR #15261 rejected publishing this resolution from
`@objectstack/core`: the "three consumers in packages that cannot import
each other" premise is false (`cli -> runtime`, `cli -> plugin-security`
and `runtime -> plugin-security` all exist today; only
`plugin-security -> runtime` would cycle), and the two sibling reader
cards landed their own private `packages[]` walks, so there is exactly
one consumer. Maintainer decision 2026-09-04: those two land as they are
and this card publishes nothing.
- Move `artifact-collections.ts` and its tests to `@objectstack/runtime`,
where every call site already lives. `packages/core/src/index.ts` is
restored byte-for-byte, so `@objectstack/core` has no source change in
this PR at all. The module is not named by
`packages/runtime/src/index.ts`, so neither symbol reaches a published
surface.
- Drop the partly-flattened support claim and its test. #14512 ruled
"Not D (a partly flattened artifact is a new permanent shape)", and on
such an artifact the top level's name claims are applied to every
package body, so a second package's same-named permission set or object
extension is dropped. The module header now records that instead of
promising the opposite.
- Refuse a collection key spelled both ways rather than skipping one.
`functions` is `z.union([z.record(...), z.array(...)])`, so two packages
can each be schema-valid and disagree; the previous code let
`contributions[0]` pick the shape and `continue`d past the rest, losing
a whole package's collection in both directions with nothing thrown.
New ADR-0112 envelope `MIXED_ARTIFACT_COLLECTION_SHAPE` (422), matching
what `composeStacks` already does with the same mix, classified in
`dispatcher-error-vocabulary.ts`.
- Document the dependency-cycle throw in `@throws`: it is a bare `Error`
from `resolvePluginOrder`, so a caller matching on `err.code` /
`err.status` does not match it.
- Changeset drops `@objectstack/core` entirely; `@objectstack/runtime`
stays `patch` because no published surface widens.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
* test(cli): record the option-B ledger reaching ZERO, and re-verify it there
`OPTION_B_LOSSES` had 23 rows on `origin/main` after #15007 landed (it
deleted the one row no artifact-side change could reach). All 23 belong
to this card, so merging it empties the ledger — which is the state the
pin's own header calls "the program is done".
An empty ledger is also the state that could go vacuous, so it is
re-verified rather than asserted: with `resolveArtifactCollections`
neutered to the identity function and `@objectstack/runtime` REBUILT —
the pin reaches that package through its `exports` map, so `dist/` is
what it measures — the pin goes red naming exactly 23 rows, byte-for-byte
the set the ledger carried before. Restored and rebuilt, 7 passed.
⛔ The set-equality assertion, the subsystem coverage and the anti-vacuity
controls are untouched; the only edits are the ledger's own rows and the
two docblocks that described them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
* test(runtime): pin `packages: []` returning by identity, which no test covered
The three identity controls the design rests on are the additive artifact
returning by reference, `packages: []` returning unchanged, and an artifact
with no `packages` key short-circuiting. The first and third were pinned;
the second was not, and it is the one that is not obvious: `[]` IS an
array, so such an artifact walks the whole resolution — package order over
zero entries, then every package-owned key merged against no contributions
— and still has to come back as the SAME object. Any key that came back a
fresh copy would trip `{ ...artifact }` and hand every reader downstream a
different object than it was given.
Asserted with and without collections present, so the identity is not an
artifact of the one key that happened to be there.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
* test(cli): re-anchor the BASELINE anti-vacuity floor, which the empty ledger made vacuous
`expect(additive.rows.length).toBeGreaterThanOrEqual(OPTION_B_LOSSES.length)`
was a real bound only while the ledger was non-empty. This PR emptied it,
so the line became `rows.length >= 0` — true of every array, including an
empty one. It was dead code wearing a control's comment, and it took with
it the fourth direction this file's header claims: "the probe itself
quietly measuring less ⇒ RED".
Re-anchored to the probe's MEASURED row count rather than deleted, because
none of the three controls that survive an empty ledger covers this one:
the `registryObjectsFromArtifact` CONTROL asserts two object names, and the
two coverage tests assert that the five boundaries and #15006's four sites
are represented — none of them notices rows disappearing.
30 is measured, not remembered: with the line temporarily written
`toBe(-1)` the run reports `expected 30 to be -1`. Verified live at the
boundary in the same session — a floor of 31 goes RED on the same fixture
with the new message, so the assertion is not satisfied by construction.
Both legs proved on disk by hash and restored to a byte-identical file.
`>=` rather than `toBe` keeps the shrink-only direction the ledger uses: a
row added to the probe stays green, a row that stops being measured is red.
⛔ The set-equality assertion, the `registryObjectsFromArtifact` control,
the five-boundary coverage test and the #15006 four-site coverage test are
untouched, and the ledger is not touched to serve this line.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent c4d1354 commit f1a1028
10 files changed
Lines changed: 1233 additions & 71 deletions
File tree
- .changeset
- packages
- cli/test
- runtime/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
Lines changed: 64 additions & 31 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
69 | | - | |
| 69 | + | |
70 | 70 | | |
71 | 71 | | |
72 | 72 | | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
73 | 81 | | |
74 | 82 | | |
75 | 83 | | |
| |||
129 | 137 | | |
130 | 138 | | |
131 | 139 | | |
132 | | - | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
133 | 152 | | |
134 | | - | |
135 | | - | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
136 | 168 | | |
137 | | - | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
147 | | - | |
148 | | - | |
149 | | - | |
150 | | - | |
151 | | - | |
152 | | - | |
153 | | - | |
154 | | - | |
155 | | - | |
156 | | - | |
157 | | - | |
158 | | - | |
159 | | - | |
160 | | - | |
161 | | - | |
| 169 | + | |
162 | 170 | | |
163 | 171 | | |
164 | 172 | | |
| |||
230 | 238 | | |
231 | 239 | | |
232 | 240 | | |
233 | | - | |
234 | | - | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
235 | 268 | | |
236 | 269 | | |
237 | 270 | | |
| |||
0 commit comments