Repository navigation
fix(service-automation): boot-time flow precedence classifies contenders by the loader's set - #20880
Conversation
…ers by the loader's set resolveFlowPrecedence and describeFlowContender take the loader's-set reader (PackagedFlowSource); the boot pull hands it the engine's own packagedFlowOwner. A contender is packaged only when the set holds its name; with no reader nothing is packaged (the engine's fail-closed answer). Body stamps stay on the contender for display only. Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
… set Tenant-ranked contenders keep arrival order (the id tie-break stays within the packaged rank, as the precedence docblock states); the #11997 suite hands precedence the registry's artifact-view owner the way the boot pull does; a new suite pins the classification, the shadowing record, the fail-closed composition and the plugin wiring. Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
…s-set reader Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check4 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 46dae3a4ea8973588231b8eb165193ae9c1f2bd8 && git checkout 46dae3a4ea8973588231b8eb165193ae9c1f2bd8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9905e61ca2fddb43d266c23cdb12ced5019c1a74 6a028afb2bededfcb9ff96259e9ce0e832beb41d && git checkout -B drift-repro 9905e61ca2fddb43d266c23cdb12ced5019c1a74 && git merge --no-ff 6a028afb2bededfcb9ff96259e9ce0e832beb41d
node scripts/docs-audit/affected-docs.mjs --json 9905e61ca2fddb43d266c23cdb12ced5019c1a74 |
Contract reviewServed-tier: This is the record of record for PR #20880 at Inputs:
Disclosure is kept at the card's level: doors, roles, codes and statuses. The body's package-provenance stamps, the tenant marker and the artifact's envelope are named abstractly here. ① Derived judgments(a) One source — RIGHT. Rule 1 is now delivered for the last reader it names.
(b) The per-name limit, judged adversarially — UNREACHABLE at boot, so a named residual, not a FAIL.
(c) The tie-break — RIGHT, and no behaviour changes beyond what the PR says.
(d) Changeset and PR prose — ACCURATE, within the discipline, nothing undelivered.
Surface inventory: two barrel-exported functions gain an optional trailing parameter; the boot pull passes the engine's reader; no route, schema, query set or status changes; no governed path. ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
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-automationis a declared cross-lane touch (domain:services) from thedomain:cliseat, as it was on #20761.Base
72f8c3820(claim), head6a028afb2b.mainwas not merged in.What changed
flow-precedence.ts.resolveFlowPrecedence(items, logger?, packagedFlowOwner?)anddescribeFlowContender(item, packagedFlowOwner?)now take the reader as an optional last argument. Its type isPackagedFlowSource, the oneAutomationEngine.setPackagedFlowSourcealready takes.isCodeArtifactBody). The set's own lookup (SchemaRegistry.getArtifactItem) applies that same test to each entry.plugin.ts. The one call site passes the engine's ownpackagedFlowOwner, whichpackagedFlowReaderhas fed sinceinit(). Precedence and every other classification the engine makes now read one source. There is no second set.@objectstack/service-automationgets aminorbump, 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 declarednoon 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
SchemaRegistryfrom@objectstack/objectql.package.The same file after the change (at
6a028afb2b):runtime.alphawins overbeta.Tests (at
6a028afb2b)pnpm --filter @objectstack/service-automation test: 159 files, 1995 tests passed.pnpm --filter @objectstack/service-automation typecheck:tscis clean.check:test-typecheckis OK: the test layer compiles, with 0 debt.flow-precedence-loader-set.test.ts(11 pins):AutomationServicePluginboots three times: a held name (answered by a protocol stand-in'spackagedArtifactOwner), an unheld name, and no protocol service at all.flow-name-shadowing.test.ts(Packaged flow silently replaced by a same-named runtime flow: listItems returns both, the engine keys flows by bare name, and Map order decides the winner #11997): precedence now gets the registry's artifact-view owner. The owner is attached to the engine and read back throughpackagedFlowOwner, exactly as the pull does. The expectations are unchanged.flow-provenance-server-held.dogfood.test.ts(PR fix(automation): which flows are packaged is the loader's fact, and every flow written through an authoring door is tenant-authored (#20761) #20853's suite): 13 of 13 passed against a rebuiltdist/. 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 equalsHEAD, andgit diff HEADis empty. The subject resolves by relative import fromsrc/, sodist/is not on the path.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 base72f8c3820and covers 5 paths. That gave 61 commands. Every one ran, and every one exited 0.--ranwith exit codes: 61 derived, 61 run, 0 NOT MEASURED, 0 unrun.check:dual-build-cjs-loadsfirst answered PREREQUISITE NOT MET, because 8 packages were not built in this worktree. After they were built (all turbo cache hits), it exited 0.check:startup-registry-verdictandcheck:durability-log-levelare green.pnpm lint:--print-configresolves 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/JavaScriptfiles.--format json: 4 files, 0 errors, 0 warnings.parserOptions.projectorprojectService, 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
FlowContender's docblock inengine.tsstill describespackageas the per-body artifact test alone. That file is outside this card's surface. Carrier: the next PR that touches that type.Session:
session_01VvcEokUG1tvVxkceYfR5XB, branchclaude/issue-20864-precedence-loader-set.Generated by Claude Code