Repository navigation
automation: boot-time flow precedence still classifies its contenders from body stamps, not the loader's set (the remainder of #20761's ruling rule 1) #20864
Description
Activity
- addedbugSomething isn't workingSomething isn't workingpriority:p1High: required for production / M2High: required for production / M2area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsand removed
on Sep 30, 2026 objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 of the
domain:cliseat's sessionsession_01VvcEokUG1tvVxkceYfR5XB(batch3):priority:p1·security, the in-flight-derived sub-issue of #20761 (closed by PR #20853 as76bd58fac4). It carries ruling rule 1's remaining reader, boot-time flow precedence.
Session:session_01VvcEokUG1tvVxkceYfR5XB
Account:huangyiirene
Branch:claude/issue-20864-precedence-loader-set
Worktree:objectstack-issue-20864
Domain:domain:cli
Seat:domain:cli#1
File surface (thedomain:servicesfiles are declared cross-lane, as on #20761):packages/services/service-automation/src/flow-precedence.ts:resolveFlowPrecedenceclassifies each contender through the loader's-set reader the engine now holds (packagedFlowOwner/isPackagedFlow, fed bypackagedFlowReader), not throughdescribeFlowContender's reading of the body's stamps.packages/services/service-automation/src/plugin.ts: the one call site (about:1020on72f8c3820) hands precedence that reader.- Pins beside them: a stamped contender whose name the loader's set does not hold ranks as tenant-authored; a loader-set contender ranks as packaged; the shadowing report names the right armed contender.
.changeset/20864-*.mdfor@objectstack/service-automation.- ⛔ No other reader. The rest of rule 1 landed with fix(automation): which flows are packaged is the loader's fact, and every flow written through an authoring door is tenant-authored (#20761) #20853. ⛔ No
packages/spec,objectqlormetadata-core. - ⛔ No request body, header or field spelling in the PR or any comment (disclosure inherited from automation: a flow created through the authoring door can assert package provenance, and the ADR-0126 guards and the activation ledger then treat it as package-shipped #20761).
(stop on a breach outside these; explain in the report)
Container & model:S,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable). One classifier and one call site, but a security boundary, so the default tier. The contract review runs atCONTRACT_REVIEW_TIER.
Clause-②: no
Clause-② reading: no public member is added, and no accept set moves. Precedence stops trusting a body claim the platform never made, which is the negative boundary of clause ②, as the fix(automation): which flows are packaged is the loader's fact, and every flow written through an authoring door is tenant-authored (#20761) #20853 record judged for the same rule. If the diff adds an exported member, the dev re-declaresyes (widening)on the PR's line 2.
Thread-read: none
Serial constraints cleared:#20761 closed with PR #20853 (76bd58fac4), so nothing else is in flight on area:access (#20790 is needs-user-decision). No open PR touches packages/services/service-automation, and no open claim names it (read in this act, main 72f8c3820).
Generated by Claude Code
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 20864, "status": "done", "branch": "claude/issue-20864-precedence-loader-set", "pr": "https://github.com/objectstack-ai/objectstack/pull/20880", "session": "session_01VvcEokUG1tvVxkceYfR5XB", "premise_still_valid": true, "summary": "Boot-time flow precedence now classifies its same-named contenders by the loader's set: resolveFlowPrecedence(items, logger?, packagedFlowOwner?) and describeFlowContender(item, packagedFlowOwner?) take the PackagedFlowSource reader, a contender is packaged only when the set holds its name (inside a held name the registry's own per-entry artifact test, isCodeArtifactBody, tells the loader's entries from a same-named tenant row), and with no reader nothing is packaged (the engine's fail-closed answer). The plugin's one call site passes the engine's own packagedFlowOwner (fed by packagedFlowReader since init), so precedence and every other reader ask one source; the package-id tie-break now applies within the packaged rank only, so a body's own id no longer orders tenant-ranked contenders (they keep arrival order). Premise measured before the change at 72f8c3820 (scratch vitest, real SchemaRegistry): a body stamped as a package's for a name no set holds ranked package; on a real registry filled with the loader's and the hydration's shapes every code-shaped entry's name was in the set, so as the #20853 record read it is not reachable at boot (defence in depth). After the change the same registry shapes arm the same flows with the same receipts.", "tests": "All at head 6a028afb2b. pnpm --filter @objectstack/service-automation test: Test Files 159 passed, Tests 1995 passed. pnpm --filter @objectstack/service-automation typecheck: tsc clean; check:test-typecheck OK (test layer compiles, 0 debt). New src/flow-precedence-loader-set.test.ts: 11 pins (8 pure incl. fail-closed and one-question-per-contested-name, 3 boots of the real AutomationServicePlugin: held name via a protocol stand-in's packagedArtifactOwner, unheld name, no protocol service). src/flow-name-shadowing.test.ts (#11997) now hands precedence the registry artifact-view owner through engine.packagedFlowOwner as the pull does; expectations unchanged. Dogfood regression packages/qa/dogfood/test/flow-provenance-server-held.dogfood.test.ts: 13/13 passed against a rebuilt dist (turbo build of the dogfood closure, 63 tasks; dist marker grep counts 1 and 1 for both changed spots). Ablations (fix committed first; each via scripts/ablation-replace.mjs in WRAP mode plus a shell EXIT/INT/TERM restore trap on absolute paths; subject imported relatively from src so no dist on the path): A1 classifier ignores the set → 6 of 11 pins red; A2 boot pull stops passing the reader → 1 red (held-name plugin boot); A3 id tie-break orders tenant-ranked contenders → 1 red (arrival-order pin). Each: anchor 1 → 0, blob changed (a0495f243037 → 4212e0f53183, 0cbd6808a8aa → 46745880e437, a0495f243037 → d33a4cb2e9a3), restored blob == HEAD blob and git diff HEAD empty; final shell check re-proved both files' blobs equal HEAD. Direction: red as predicted in all three.", "mcp_calls": "0 — no MCP GitHub tool called (reads were single-card REST reads with curl; writes went through scripts/pm tools)", "api_writes": "3 — three POST /repos/objectstack-ai/objectstack/dispatches strokes through the fleet-write relay, executed as objectstack-fleet[bot]: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (#20880, draft forced, 8716 bytes sent = stored); (2) label-write --assign huangyiirene → POST /repos/objectstack-ai/objectstack/issues/20880/assignees (read back: assignee huangyiirene; size/m was added by the size labeler, not this write); (3) post-stamped → POST /repos/objectstack-ai/objectstack/issues/20864/comments (this os-dev-report). Plus git push (not REST). No label writes: the dispatch named none and the diff carries a changeset.", "open_questions": [], "out_of_scope_findings": [ "carrier: none (承接者:无) · noted, not filed — per-name reader limit: inside a name the set holds, 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 and can win the lexicographic order (after-change measurement M2). Not reachable at boot: every code-shaped registry entry is the loader's or carries the artifact's grafted envelope (M3). Closing it needs a per-entry answer from metadata-protocol's packagedArtifactOwner, outside this card's surface. PR Acceptance note 1.", "carrier: none (承接者:无) · noted, not filed — a stored row of a held name hydrates with the artifact's envelope grafted, ranks packaged beside the loader entry, and kernel phase order arms the loader body; the receipt lists the package twice. Unchanged by this PR (M3 identical before and after); ADR-0126 §7.1 leaves shadow diagnostics to #11997. PR Acceptance note 2.", "carrier: the next PR touching FlowContender in packages/services/service-automation/src/engine.ts · noted, not filed — its docblock still describes the package source as the per-body artifact test alone; engine.ts is outside this card's file surface. PR Acceptance note 3." ], "gates": "Local, at 6a028afb2b: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths; merge base 72f8c3820, 5 paths) → 61 commands, all run, every exit 0; --ran with exit-coded lines → 61 derived, 61 run, 0 NOT-MEASURED, 0 UNRUN. check:dual-build-cjs-loads first exited 3 PREREQUISITE NOT MET (8 unbuilt packages), measured exit 0 after building them (41 turbo tasks, all cache hits). The PM's pre-dispatch list (55 lines) is a subset; the 6 added families (engine-double-contract, objectql-double-limit, query-options-erasure, type-check-coverage, type-check-debt, where-matcher) all ran green. Extra: check:startup-registry-verdict and check:durability-log-level exit 0. Lint as a proven narrowing (not pnpm lint): population from eslint --print-config on the 4 changed TS files (6 rules src, 5 tests, none ignored; the changeset is outside the TS/JS files glob); --format json: 4 files, 0 errors, 0 warnings; invariance: no resolved config sets parserOptions.project or projectService and the custom rules read only the linted AST plus an untouched config-level baseline. CI on PR #20880 at 6a028afb2b, one read at report time: 31 check-runs, 9 success, 3 skipped, 19 in_progress, 0 failing → in_progress.", "deviations": [ "Clause-② re-declared 'yes (widening)' (claim said 'no'): two barrel-exported functions gain an optional parameter; the claim instructed this re-declaration if the public surface grew. Changeset is minor.", "The package-id tie-break was restricted to the packaged rank (tenant-ranked contenders keep arrival order). It sits in flow-precedence.ts (claimed file) and aligns the code with the function's own docblock; without it a body's own id would decide which tenant-ranked contender is armed. Consequence stated in PR Acceptance note 4: a composition with no reader orders two packages of one bare name by arrival.", "Commit trailers are the model-free pair and the PR footer is the session-URL form, per AGENTS.md and the os-dev contract, not the harness attribution block.", "Two temporary measurement test files (src/zz-measure-20864.tmp.test.ts) were created in the worktree and deleted after each run; never committed.", "The worktree is removed (node_modules deleted first, git worktree remove without force)." ], "files_changed": [ ".changeset/20864-precedence-loader-set.md", "packages/services/service-automation/src/flow-precedence.ts", "packages/services/service-automation/src/plugin.ts", "packages/services/service-automation/src/flow-name-shadowing.test.ts", "packages/services/service-automation/src/flow-precedence-loader-set.test.ts" ] }
Generated by Claude Code
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsACCEPT: PR #20880 at
6a028afb(boot-time flow precedence classifies contenders by the loader's set, the last reader of #20761's ruling rule 1)domain:cliseat ·session_01VvcEokUG1tvVxkceYfR5XB· 2026-09-30T15:24Z · ⛔ no request body, header or field spelling- Contract review of record:
5914326577on the PR,CONTRACT_REVIEW_TIER, head6a028afb, PASS.- Precedence now asks the same one source every other rule-1 reader asks: the protocol's
packagedArtifactOwner, reached through the engine'spackagedFlowOwnerand asked once per contested name. It fails closed with no reader. - With this PR, automation: a flow created through the authoring door can assert package provenance, and the ADR-0126 guards and the activation ledger then treat it as package-shipped #20761's rule 1 is delivered for every reader it names.
- Precedence now asks the same one source every other rule-1 reader asks: the protocol's
- Seat verification on adoption: the record, the changeset, the PR body and every added test title carry 0 provenance-field spellings (scanned). A local
git merge-treeagainst the currentorigin/mainis clean. - Checklist:
- Draft, base
main, first lineFixes #20864, andClause-②: yes (widening). The dev re-declared it from the claim's conditionalnobecause two barrel-exported functions gain an optional parameter; the record judges it right. - 5 files, +407 / −34, all on the claim's surface. Changeset:
@objectstack/service-automationminor. - NOT governed. 34 check-run names: 31 success and 3 skipped.
- Draft, base
- The record's residuals, none blocking:
- Residual 1, unreachable at boot. Inside a name the set holds, a code-shaped entry that is not the loader's could still rank packaged. The record traced every producer of the precedence input after 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 and found none that makes that shape. It is a hardening follow-up (a per-entry answer from the protocol,
domain:engine's surface). Acceptance notes; not filed, since it has no reach. - Residual 2, an unmeasured source reading, carried by this seat. For a packaged name that also has a stored row (only pre-lock rows or the writable hatch can hold one now), the
kernel:readysync may re-arm the stored body after the boot pull armed the loader's. The filing gate asks for a measured reach, so this seat measures it on a real boot at its next free slot (a measure-only dispatch) and files it if it reproduces. It is recorded on the seat post so the wait is visible. - The
describeFlowContenderdocblock overstates the in-name limit; it folds into Residual 1. - When two packages ship one bare name, the activation attribution names the set's owner while precedence arms the lexicographic first. Pre-existing, ADR-0048 §3.4. Noted.
- Residual 1, unreachable at boot. Inside a name the set holds, a code-shaped entry that is not the loader's could still rank packaged. The record traced every producer of the precedence input after 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 and found none that makes that shape. It is a hardening follow-up (a per-entry answer from the protocol,
- Out-of-scope findings: the stale
FlowContenderdocblock inengine.tsrides the next PR touching that type. Noted, not filed. - Next: land through the queue. At the merge, the seat closes this card if the
Fixesdoes not, and removespm:dispatched.
Generated by Claude Code
- Contract review of record:
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsLanded: PR #20880 →
27bf358ec7(boot-time flow precedence classifies contenders by the loader's set)domain:cliseat ·session_01VvcEokUG1tvVxkceYfR5XB· 2026-09-30T15:57Z · ⛔ no request body, header or field spelling- Landing reading:
27bf358ec7a3494cc58f9d010a7820dec68c52ffis onorigin/mainas a single-parent squash overf5c7b2c75. Content:git diff --statof the PR head6a028afbagainst27bf358eover the PR's 5 files is empty.packages/services/service-automation/src/flow-precedence.tsonorigin/mainnamespackagedFlowOwneron 13 lines, against 0 at the parent. - Queue note:
automerge_enableanswered success at 15:25Z, but the PR did not enter the queue for 13 minutes while it readclean. A disable and re-enable in one relay stroke enqueued it within 20 s. This is the second instance today of the same platform reading, after PR docs(plugin-dev): re-anchor the dead tracker citations in packages/plugins/plugin-dev/src to the commits that decided them #20767 this morning; it is recorded on the seat post. - Closure: the merge did not close this card by itself. The seat closes it as
completedand removespm:dispatchedin the same act. With it, every reader that automation: a flow created through the authoring door can assert package provenance, and the ADR-0126 guards and the activation ledger then treat it as package-shipped #20761's ruling rule 1 names asks the loader's set. - Still carried by this seat: Residual 2 of the record
5914326577, the unmeasuredkernel:readysync over a stored row of a packaged name, per the ACCEPT5914340253.
Generated by Claude Code
- Landing reading:
objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsResidual 2 reproduced on a real boot, and filed as #20913
domain:cliseat ·session_01VvcEokUG1tvVxkceYfR5XB· 2026-09-30T17:46Z · ⛔ no request body, header or field spelling · ⛔ no state change (this card stays closed)This closes the commitment in ACCEPT
5914340253.- The run: a measure-only run on
cf684c98eb(the showcase composition, a database file, cold boots) reproduced Residual 2 of the review record5914326577. - What it found: for a packaged flow name with an environment-wide active stored row, the boot pull arms the loader's body, then the
kernel:readysync re-arms the stored body. A record-triggered run executed the stored body's step, while the boot receipt still reports the loader's body as armed. - Reach: a row written under the operator's writable-metadata hatch survives the hatch being closed, and so does a row written straight into the store.
- Filed as automation: at
kernel:readythe flow sync re-arms a stored row's body over the loader's for a packaged flow name, after the boot pull armed the loader's, so the stored body runs while the receipt says the package's is armed #20913, unlabelled, for triage, with the readings, the file:line chain and the reach. - This card's delivery is unchanged: PR fix(service-automation): boot-time flow precedence classifies contenders by the loader's set #20880 made precedence read the loader's set. automation: at
kernel:readythe flow sync re-arms a stored row's body over the loader's for a packaged flow name, after the boot pull armed the loader's, so the stored body runs while the receipt says the package's is armed #20913 is thekernel:readysync, which reads no precedence at all.
Generated by Claude Code
- The run: a measure-only run on
- added 2 commits that reference this issue
on Oct 7, 2026
This card carries the precedence half of #20761's ruling rule 1. #20761 keeps nothing: PR #20853 delivers the rest of stage 2, and the card closes when it lands. An in-flight-derived sub-issue: it carries the parent's
domain:cliandpriority:p1and goes straight topm:queue. Filed by thedomain:cliexecution seat (#6024, sessionsession_01VvcEokUG1tvVxkceYfR5XB). Part of #20761.The ruling it completes
Ruling B + A, recorded by triage in
5904938166, rule 1: "The engine's classification (the §7.3 guards, the toggle door, precedence) reads that set, not the stamps a flow body carries."What remains
f0de8fe69): boot-time precedence (resolveFlowPrecedenceinpackages/services/service-automation/src/flow-precedence.ts) still classifies its contenders withdescribeFlowContender, that is, from the registry bodies' stamps. Every other reader the rule names now asks the loader's set throughpackagedFlowOwner/isPackagedFlow.Direction
Route the precedence classifier through the same reader the engine now holds (
setPackagedFlowSource/packagedFlowOwner), so the one server-held fact decides every reader rule 1 names.packages/services/service-automation/src/flow-precedence.tsand its caller inengine.ts(domain:servicesfiles, declared cross-lane as on automation: a flow created through the authoring door can assert package provenance, and the ADR-0126 guards and the activation ledger then treat it as package-shipped #20761).Dedupe
It is new: the residual was first named by the PR #20853 record at
f0de8fe69. The #20761 thread is its only prior mention.Generated by Claude Code