Repository navigation
Commit 27bf358
fix(service-automation): boot-time flow precedence classifies contenders by the loader's set (#20880)
Fixes #20864
Clause-②: yes (widening)
Boot-time flow precedence now takes which same-named contender is the
packaged one from the loader's set, the reader the engine has held since
PR #20853, instead of from the flow bodies' package-provenance stamps.
This finishes rule 1 of the ruling recorded on #20761 (`5904938166`) for
the last reader it names, precedence.
`packages/services/service-automation` is a **declared cross-lane
touch** (`domain:services`) from the `domain:cli` seat, as it was on
#20761.
Base `72f8c3820` (claim), head `6a028afb2b`. `main` was not merged in.
## What changed
- **`flow-precedence.ts`.** `resolveFlowPrecedence(items, logger?,
packagedFlowOwner?)` and `describeFlowContender(item,
packagedFlowOwner?)` now take the reader as an optional last argument.
Its type is `PackagedFlowSource`, the one
`AutomationEngine.setPackagedFlowSource` already takes.
- A contender is packaged only when the set holds its name.
- Inside a name the set holds, the loader's entries are told apart from
a same-named tenant row by the registry's own per-entry artifact test
(`isCodeArtifactBody`). The set's own lookup
(`SchemaRegistry.getArtifactItem`) applies that same test to each entry.
- With no reader, nothing is packaged. This is the engine's fail-closed
answer.
- The set is asked once for each contested name. A name with one
contender asks nothing.
- **Tie-break.** The package-id tie-break now applies within the
packaged rank only, as the function's docblock always said. Two
tenant-ranked contenders keep their arrival order, so a body's own id
cannot win it the armed slot.
- **`plugin.ts`.** The one call site passes the engine's own
`packagedFlowOwner`, which `packagedFlowReader` has fed since `init()`.
Precedence and every other classification the engine makes now read one
source. There is no second set.
- **Changeset.** `@objectstack/service-automation` gets a `minor` bump,
and the changeset carries the one-line migration for direct callers.
## Clause-② reading
`yes (widening)`: two barrel-exported functions gain an optional
parameter. The claim declared `no` on the condition that no public
member is added. A new accepted argument widens the public signature, so
this line is re-declared, as the claim instructs.
No accept set shrinks. A body's claim of package provenance was never a
declared input to precedence. The stamps are server-derived read
decorations, which is the same negative boundary the PR #20853 record
judged for this rule.
## Premise, measured before the change (at `72f8c3820`)
These runs used a scratch vitest file (not committed) and a real
`SchemaRegistry` from `@objectstack/objectql`.
- **M1.** A body stamped with a package's provenance, for a name no set
holds, was classified `package`.
- **M2.** On pure input, a forged package id beside the loader's entry
took the armed slot by lexicographic order.
- **M3.** A real registry was filled with the loader's shapes and the
hydration's shapes (the tenant marker, and the artifact graft). Every
code-shaped listed entry had its name in the set: the invariant held for
all 4 code-shaped entries among the 6 listed. So, as the PR #20853
record read, this is not reachable from an authoring door at boot. The
change is defence in depth and completes the ruling.
The same file after the change (at `6a028afb2b`):
- M1 classifies the stamped body as `runtime`.
- M3 arms the same flows with the same receipts as before, for the
shapes measured both times:
- the grafted overlay's name arms the loader entry, by arrival order;
- `alpha` wins over `beta`.
- A plain overlay beside a loader entry still wins. That is the #11997
suite's case, and its expectations are unchanged.
## Tests (at `6a028afb2b`)
- **`pnpm --filter @objectstack/service-automation test`:** 159 files,
1995 tests passed.
- **`pnpm --filter @objectstack/service-automation typecheck`:** `tsc`
is clean. `check:test-typecheck` is OK: the test layer compiles, with 0
debt.
- **New `flow-precedence-loader-set.test.ts` (11 pins):**
- A stamped contender ranks tenant-authored when the set does not hold
its name, and the same contender ranks packaged when the set holds it.
- A stored tenant row stays tenant-authored inside a held name.
- The same two bodies rank by the set, not by their bytes.
- The shadowing record and the pull warning name the armed body in both
arrival orders.
- Tenant-ranked contenders keep arrival order.
- With no reader, the classification fails closed. A reader that answers
an empty owner fails closed too.
- Each contested name is asked about once.
- The real `AutomationServicePlugin` boots three times: a held name
(answered by a protocol stand-in's `packagedArtifactOwner`), an unheld
name, and no protocol service at all.
- **`flow-name-shadowing.test.ts` (#11997):** precedence now gets the
registry's artifact-view owner. The owner is attached to the engine and
read back through `packagedFlowOwner`, exactly as the pull does. The
expectations are unchanged.
- **Dogfood regression, `flow-provenance-server-held.dogfood.test.ts`
(PR #20853's suite):** 13 of 13 passed against a rebuilt `dist/`. The
dist marker count is 1 for each of the two changed spots.
## Ablations
The fix was committed first. Each mutation ran through
`scripts/ablation-replace.mjs`. For every one, the anchor went from 1
hit to 0, the blob changed, the restored blob equals `HEAD`, and `git
diff HEAD` is empty. The subject resolves by relative import from
`src/`, so `dist/` is not on the path.
- **A1, the classifier ignores the set (the stamps decide again):** 6 of
the 11 pins go red, 4 pure and 2 plugin boots.
- **A2, the boot pull stops passing the reader:** 1 pin goes red, the
held-name plugin boot.
- **A3, the id tie-break orders tenant-ranked contenders again:** 1 pin
goes red, the arrival-order pin.
All three went red in the predicted direction.
## Gates (at `6a028afb2b`)
- **`dispatch-gates --commands`,** run with no paths: the change set was
derived from merge base `72f8c3820` and covers 5 paths. That gave 61
commands. Every one ran, and every one exited 0.
- `--ran` with exit codes: 61 derived, 61 run, 0 NOT MEASURED, 0 unrun.
- `check:dual-build-cjs-loads` first answered PREREQUISITE NOT MET,
because 8 packages were not built in this worktree. After they were
built (all turbo cache hits), it exited 0.
- **Beyond that list:** `check:startup-registry-verdict` and
`check:durability-log-level` are green.
- **Lint, as a proven narrowing rather than `pnpm lint`:**
1. **The population comes from eslint's own config.** `--print-config`
resolves the lint config for each of the 4 changed TypeScript files (6
rules for the source files, 5 for the tests), and none is ignored. The
changeset falls outside the config's TypeScript/JavaScript `files`.
2. **The count comes from `--format json`:** 4 files, 0 errors, 0
warnings.
3. **Untouched files cannot change verdict.** No file's resolved config
sets `parserOptions.project` or `projectService`, so type-aware linting
is off. The custom rules read only the linted AST, plus a config-level
baseline that this diff does not touch.
## Acceptance notes
1. **Per-name limit.** The reader answers per name. Inside a held name,
a code-shaped entry that is not the loader's (a forged package id with
no tenant marker) still ranks packaged under its own id. It can win the
lexicographic order (M2, after the change). It is not reachable at boot:
every code-shaped registry entry is either the loader's or carries the
artifact's own grafted envelope. Closing it needs a per-entry answer
from the metadata protocol, which is outside this card's surface.
Carrier: none.
2. **Grafted stored rows.** A stored row of a held name hydrates with
the artifact's envelope grafted onto it, so it ranks packaged beside the
loader entry. Kernel phase order (the loader comes first) then arms the
loader body, and the receipt lists that package twice. This PR does not
change that: M3 was identical before and after. ADR-0126 §7.1 leaves the
shadow diagnostics to #11997.
3. **Stale docblock.** `FlowContender`'s docblock in `engine.ts` still
describes `package` as the per-body artifact test alone. That file is
outside this card's surface. Carrier: the next PR that touches that
type.
4. **No-reader compositions.** In a composition with no reader, two
packages that ship one bare flow name now keep arrival order instead of
package-id order, because nothing is packaged there. Every real
composition attaches the reader.
Session: `session_01VvcEokUG1tvVxkceYfR5XB`, branch
`claude/issue-20864-precedence-loader-set`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent f5c7b2c commit 27bf358
5 files changed
Lines changed: 407 additions & 34 deletions
File tree
- .changeset
- packages/services/service-automation/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 | + | |
Lines changed: 34 additions & 8 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
50 | 67 | | |
51 | 68 | | |
52 | 69 | | |
| |||
62 | 79 | | |
63 | 80 | | |
64 | 81 | | |
65 | | - | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
66 | 85 | | |
67 | 86 | | |
68 | 87 | | |
| 88 | + | |
69 | 89 | | |
70 | | - | |
| 90 | + | |
71 | 91 | | |
72 | 92 | | |
73 | 93 | | |
| |||
182 | 202 | | |
183 | 203 | | |
184 | 204 | | |
185 | | - | |
186 | | - | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
187 | 210 | | |
188 | 211 | | |
189 | 212 | | |
| |||
201 | 224 | | |
202 | 225 | | |
203 | 226 | | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
204 | 230 | | |
205 | 231 | | |
206 | 232 | | |
207 | | - | |
| 233 | + | |
208 | 234 | | |
209 | 235 | | |
210 | 236 | | |
211 | 237 | | |
212 | 238 | | |
213 | 239 | | |
214 | | - | |
| 240 | + | |
215 | 241 | | |
216 | | - | |
| 242 | + | |
217 | 243 | | |
218 | 244 | | |
219 | 245 | | |
| |||
222 | 248 | | |
223 | 249 | | |
224 | 250 | | |
225 | | - | |
| 251 | + | |
226 | 252 | | |
227 | 253 | | |
228 | 254 | | |
| |||
Lines changed: 240 additions & 0 deletions
| 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 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 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 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
0 commit comments