Repository navigation
plugin-auth: register the app:seeded backfill handler from the arming kernel:ready handler, so the guard #22312 added is structural and check:settings-bind-window can see it (child of #22316) #22328
Description
Activity
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iterate
on Oct 8, 2026 objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsClaim: PM loop round 3
Session:session_0115N1oNnQS5WqofZ2DzaT3q
Account:os-sales(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-22328-plugin-auth-seeded-hook-structural
Worktree:objectstack-issue-22328
Domain:domain:devx(inherited from the parent #22316; the file isdomain:services', declared on #6021)
Seat:domain:devx#1
File surface:packages/plugins/plugin-auth/src/auth-plugin.ts(theapp:seededregistration and, only if provably dead, thebackfillArmedflag), the package's tests only if a pin must name the new structure, and one patch.changeset/22328-*.mdfor@objectstack/plugin-auth(stop on breach; explain in the report)
Container & model:S,mode:subagent,model: opus — the default tier; dispatch-gates --tier: "no path-derived mandate", so the PM's per-card call (a small move inside an auth plugin's boot ordering, where behaviour must stay byte-identical)
Clause-②: no
Responsibility:plugin-auth's own registration shape | PR #22312's runtime flag already keeps behaviour correct; this makes it visible to the gate | no user-visible change; the widened gate (PR #22325) is what reaches it
Thread-read: none
Serial constraints cleared:no open PR touches packages/plugins/plugin-auth/src/auth-plugin.ts (PR #22312 merged, PR #22186 closed); PR #22325 (this seat, parent) lands after this card's PR— read at 2026-10-08T17:28Z
Generated by Claude Code
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 22328, "status": "done", "branch": "claude/issue-22328-plugin-auth-seeded-hook-structural", "pr": "https://github.com/objectstack-ai/objectstack/pull/22333", "session": "session_0115N1oNnQS5WqofZ2DzaT3q — the parent PM session (this run is its subagent)", "premise_still_valid": true, "summary": "auth-plugin.ts now registers the ADR-0093 D6 backfill's app:seeded handler from inside the kernel:ready handler that arms the pass, in the same synchronous step (arm, register, return runBackfill('kernel:ready')), instead of unconditionally from start(). PR #22325's widened gate (head 96d46ea02d) reads GREEN over this tree with no ledger entry, RED on unmodified origin/main 28bff18d0c (control), and RED again when the ablation moves the registration back. backfillArmed is KEPT, not dead: the default-org-created trigger (runBackfillOnDefaultOrg) still reaches runBackfill before arming from the objectql middleware during Phase 2 and from the bootstrap's own kernel:ready hook, which runs ahead of the arming one. Zone-2 assumptions MEASURED on real ObjectKernel and LiteKernel with a throwaway A/B probe (base vs fix, pass stubbed undecided): (1) holds — hook() pushes into the kernel's hooks map and trigger() reads it at dispatch time over the live array; a post-ready emit runs the pass in both shapes (1 then 2 passes); (2) holds — no emit the flag let through is lost (P1, P2 in-flight-with-blocker-before-slot, P5 in-budget awaited: identical). Two scheduling non-identities were measured in synthetic compositions, both from appending at ready time: P3 an emit still in flight across arming whose base slot was a pre-arming no-op now reaches the appended handler (base 1 pass, fix 2; the extra call is post-bind and stops at backfillDecided once decided); P4 for a post-ready emit the auth handler now runs after a start()-registered subscriber of a plugin that starts after auth (base auth,other; fix other,auth). Neither has an in-repo instance on os serve: the only start()-registered app:seeded subscriber besides auth is platform-objects, composed before AuthPlugin (serve.ts 3945 vs 4102); plugin-security and the CLI announcer subscribe in init(). No structural shape is truly identical (the kernel has no insert-at-position; a start()-time registration is what the gate rejects), so the ruling's shape is the smallest structural one. One existing unit test that pinned the start()-time registration now pins the new structure; both #22312 pins pass unchanged.", "tests": "All at head 9c8ed2b62e. Closure: os-verify-lock -c \"pnpm --workspace-concurrency=2 --filter '@objectstack/plugin-auth^...' build\" VERDICT command-exit 0 (29 of 81 projects), then pnpm --filter @objectstack/plugin-auth build. Typecheck: pnpm --filter @objectstack/plugin-auth typecheck VERDICT command-exit 0 (\"check:test-typecheck: OK — ... 10 file(s) / 94 error(s) / 23 pinned signature(s) held\"). Full suite: pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 VERDICT command-exit 0, \"Test Files 129 passed (129)\", \"Tests 2657 passed | 10 skipped (2667)\". Named, verbose: auth-settings-seeded-boot.pin.test.ts 2/2 (no Pre-bind READ; control reports exactly 1), auth-settings-ordering.pin.test.ts 5/5, auth-plugin.test.ts 'Membership backfill re-run on app:seeded (#2996)' 11/11 incl. the rewritten 'registers its app:seeded hook from the arming kernel:ready handler, never from start()', membership-policy-setting.test.ts — 4 files, 123 passed. Gate proof (PR #22325 script, md5 dbd45f65ff759cded2ddbcf7e349d026 = git show 96d46ea02d:scripts/check-settings-bind-window.mjs, run from a scratch root whose scripts/ symlinks this worktree's scripts except that file and whose packages and node_modules symlink this worktree): control origin/main 28bff18d0c exit 1 \"auth-plugin.ts:1591 ... [app:seeded-hook-from-start] ... 1 settings read(s) in the pre-bind window with no declaration covering them.\"; this tree exit 0 \"✓ settings bind-window: 4 declared / 0 self / 1 structurally upstream / 0 ledgered (73 plugin unit(s) scanned, 8 pre-bind hook(s) fired = pinned, provider 'com.objectstack.service.settings').\" Ablation (fix committed first; scripts/ablation-replace.mjs WRAP mode inside a bash script with trap restore EXIT INT TERM using git checkout HEAD -- absolute path): \"anchor 1 -> 0, blob 49113b925d97 -> 04decbf3fac2\", on-disk counts in-handler 1->0 / start-time 0->1, gate exit 1 at auth-plugin.ts:1603 [app:seeded-hook-from-start]; restore \"blob == HEAD (49113b925d97) and git diff HEAD is empty\", own check git hash-object 49113b925d9785632b45ea864d83f97d8a41b70f == HEAD blob, status clean, counts back to 1/0. No dist/build in the ablation path (the gate reads source). Behaviour A/B probe (throwaway zz-probe22328*.ts in plugin-auth/src, deleted before gate derivation, never committed): P0 handlers at start()-time emit/after boot base 1/1 fix 0/1 (both kernels); P1 post-ready emit passes 1->2 both shapes both kernels; P2 2=2; P5 1=1; P3 base 1 fix 2; P4 base auth,other fix other,auth.", "gates": { "head": "9c8ed2b62e", "derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack — 67 commands; 3 paths vs merge base 28bff18d0; 48 changed lines (+38/-10)", "reconciliation": "dispatch-gates --ran: \"✓ dispatch-gates --ran: 67 derived famil(ies) accounted for — 66 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3).\"", "results": [ "exit 0 node scripts/check-adr-0087-registration.mjs --base origin/main", "exit 0 node scripts/check-adr-0087-registration.mjs --self-test", "exit 0 node scripts/check-changeset-no-major.mjs --base origin/main", "exit 0 node scripts/check-changeset-no-major.mjs --self-test", "exit 0 node scripts/check-ci-filter-parity.mjs", "exit 0 node scripts/check-closing-keyword-parity.mjs", "exit 0 node scripts/check-closing-keyword-parity.mjs --self-test", "exit 0 node scripts/check-comment-mask-adoption.mjs", "exit 0 node scripts/check-comment-mask-adoption.mjs --self-test", "exit 0 node scripts/check-comment-mask-corpus.mjs", "exit 0 node scripts/check-dev-prereqs.mjs --self-test", "exit 0 node scripts/check-dts-emitted.mjs --self-test", "exit 0 node scripts/check-empty-changeset.mjs --base origin/main", "exit 0 node scripts/check-empty-changeset.mjs --self-test", "exit 0 node scripts/check-issue-citations.mjs", "exit 0 node scripts/check-keyed-text-bounds.mjs", "exit 0 node scripts/check-keyed-text-bounds.mjs --self-test", "exit 0 node scripts/check-platform-object-tenancy-census.mjs", "exit 0 node scripts/check-platform-object-tenancy-census.mjs --self-test", "exit 0 node scripts/check-plugin-teardown-shape.mjs", "exit 0 node scripts/check-plugin-teardown-shape.mjs --self-test", "exit 0 node scripts/check-registry-log-declared.mjs", "exit 0 node scripts/check-registry-log-declared.mjs --self-test", "exit 0 node scripts/check-rest-log-spy-declared.mjs", "exit 0 node scripts/check-rest-log-spy-declared.mjs --self-test", "exit 0 node scripts/check-system-context-census.mjs", "exit 0 node scripts/check-system-context-census.mjs --self-test", "exit 0 node scripts/check-tenant-audit-census.mjs", "exit 0 node scripts/check-tenant-audit-census.mjs --self-test", "exit 0 node scripts/check-undeclared-dep-imports.mjs", "exit 0 node scripts/check-undeclared-dep-imports.mjs --self-test", "exit 0 node scripts/docs-audit/check-affected-docs.mjs", "exit 0 node scripts/docs-audit/check-drift-comment.mjs", "exit 0 node scripts/pm/release-rehearsal-clone.mjs --self-test", "exit 0 node scripts/release-pending-publish.mjs --self-test", "exit 0 pnpm --filter @objectstack/spec run check:duration-unit-keys", "exit 0 pnpm check:auth-mount-ledger", "exit 0 pnpm check:changeset-gate-self-tests", "exit 0 pnpm check:cross-package-test-inputs", "exit 0 pnpm check:dispatcher-error-vocabulary", "exit 0 pnpm check:doc-authoring", "exit 0 pnpm check:driver-memory-census", "exit 0 pnpm check:dts-closure", "exit 3 pnpm check:dual-build-cjs-loads — NOT MEASURED: PREREQUISITE NOT MET (reads every publishable package dist/; only the plugin-auth closure is built locally). Targeted substitute: require('@objectstack/plugin-auth') resolves dist/index.js and loads, AuthPlugin: function, exit 0; the diff adds no import.", "exit 0 pnpm check:engine-double-contract", "exit 0 pnpm check:gitlink-declared", "exit 0 pnpm check:issue-citations", "exit 0 pnpm check:lean-entry-closure", "exit 0 pnpm check:logger-receiver-detach", "exit 0 pnpm check:nul-bytes (\"scanned 10305 text file(s) ... no raw ASCII control bytes\")", "exit 0 pnpm check:objectql-double-limit", "exit 0 pnpm check:objectui-changeset", "exit 0 pnpm check:org-identifier", "exit 0 pnpm check:page-declaration-shape", "exit 0 pnpm check:pm-changeset-deadline-census", "exit 0 pnpm check:published-files", "exit 0 pnpm check:query-options-erasure", "exit 0 pnpm check:refd-timer-probe", "exit 0 pnpm check:route-envelope", "exit 0 pnpm check:slot-lookup", "exit 0 pnpm check:sourcemap-no-sources-content", "exit 0 pnpm check:test-source-alias", "exit 0 pnpm check:tier-file-adoption", "exit 0 pnpm check:type-check-coverage", "exit 0 pnpm check:type-check-debt", "exit 0 pnpm check:watch-hint-literal", "exit 0 pnpm check:where-matcher", "exit 0 pnpm check:settings-bind-window (this tree's own gate, outside the derivation: \"4 declared / 0 self / 1 structurally upstream / 0 ledgered\")" ], "pr_body_gate": "PR_BODY=... node scripts/check-partof-closing-keyword.mjs: exit 0 (\"✓ check:partof-closing-keyword: this PR carries no Part-of/closing-keyword contradiction ...\")", "ci": "in_progress: draft PR #22333 at head 9c8ed2b62e just opened; not awaited (CI convergence is the PM's)." }, "line_budget": "n/a — the diff touches no skills/** and no line-ratcheted governed ledger", "files_changed": [ "packages/plugins/plugin-auth/src/auth-plugin.ts (+20 / -8: the app:seeded registration moved into the arming kernel:ready handler, its comment moved with it plus a structural note)", "packages/plugins/plugin-auth/src/auth-plugin.test.ts (+6 / -2: the test pinning the start()-time registration now pins the new structure)", ".changeset/22328-auth-seeded-hook-registered-at-ready.md (+12: patch for @objectstack/plugin-auth, Clause-②: no)" ], "deviations": [ "One existing package test changed (auth-plugin.test.ts, 'registers an app:seeded hook alongside kernel:ready' asserted the hook right after init()+start(), i.e. the old structure). Inside the claim's file surface ('the package's tests only if a pin must name the new structure'); the two #22312 pins are unchanged.", "Done-when 'Runtime behaviour is unchanged': no emit is lost and every in-repo composition measured is outcome-identical, but two scheduling differences exist in synthetic compositions (P3, P4 in summary). Reported, not hidden; no behaviour was altered to get green.", "Gate proof ran PR #22325's script from a scratch root (symlink mirror) rather than copying it into the worktree, so the derivation's change set never saw an untracked script. Not committed anywhere.", "check:dual-build-cjs-loads NOT MEASURED locally (exit 3, PREREQUISITE NOT MET: whole-workspace dist required). Declared narrowing with a targeted plugin-auth CJS load; CI runs the full gate.", "A temporary copy of base auth-plugin.ts and a probe test lived in packages/plugins/plugin-auth/src for one locked vitest run, then were deleted; git status clean before the gate derivation and the ablation.", "PR #22325's branch was fetched into the owned ref refs/os-dev-22328/pr22325 (shared refs namespace); deleted at cleanup.", "Attribution: commits carry the model-free pair (Claude-Session + Co-authored-by: Claude) and the PR body the AGENTS.md session-URL footer, not the harness reminder's model-named trailer or robot footer (AGENTS.md outranks it; the pre-push hook refuses a model identifier).", "Clause-②: no copied into the PR body as its second line, per the claim.", "Cleanup: node_modules and the worktree are removed right after this report is posted (post-stamped needs the worktree's node_modules)." ], "mcp_calls": "0 — no MCP GitHub calls (reads via gh api REST GETs)", "api_writes": "3 — all through the scripts/pm relay as objectstack-fleet[bot]: (1) fleet-write/dispatch pr_create = POST /repos/objectstack-ai/objectstack/dispatches, relay POST /repos/objectstack-ai/objectstack/pulls -> #22333, body read back identical (10446 bytes); (2) label-write --assign os-sales = one dispatch, relay POST /repos/objectstack-ai/objectstack/issues/22333/assignees, read back assignee os-sales (labels documentation, size/s, tests, tooling are the labeler's, untouched); (3) post-stamped --comment=22328 for this report = POST /repos/objectstack-ai/objectstack/issues/22328/comments. Plus 2 git pushes (empty-branch probe, then the commit), not REST.", "open_questions": [], "out_of_scope_findings": [ "carrier: 承接者:无 · noted, not filed — PR #22325's gate population is hook handlers; the objectql middleware path (registerMiddleware -> runEnsure -> runBackfillOnDefaultOrg -> runBackfill) can reach the settings read during Phase 2 and is guarded only by the runtime flag backfillArmed, which the gate cannot see. Not a defect (the flag holds at runtime) and no producer writes metadata here; recorded in the PR's Acceptance notes. Dedupe words: settings-bind-window middleware population · runBackfillOnDefaultOrg pre-bind · default-org-created backfill flag" ] }
Generated by Claude Code
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsACCEPT — PR #22333 (head
9c8ed2b62e) · 2026-10-08T18:57ZReviewed by
domain:devxseat 1 ·session_0115N1oNnQS5WqofZ2DzaT3q, against GitHub andorigin/main, not the report.Checklist: draft, base
main, first lineFixes #22328. A full body scan finds that one closing keyword and no other (the body also cites #22316, #22325, #22312, #11045 and #2996, with no verb beside any of them). The PR changes 3 files, +38/−10:auth-plugin.ts,auth-plugin.test.tsand one changeset. That is the claim's file surface.@objectstack/plugin-authis published (noprivate), and it carries apatchchangeset. Not governed (check-governed-merges --pr 22333: 0 of 3 paths). Noos-regenpath is touched.Clause-②: no, and the path limb is clean (nopackages/spec).Code, read by the seat at the PR head:
- The
app:seededregistration now sits inside the armingkernel:readyhandler, betweenbackfillArmed = trueandreturn runBackfill('kernel:ready'), in one synchronous step. This is the shape ruled in6065407005. - The reason for keeping
backfillArmedholds on the head:runEnsureis registered onkernel:readyat:1240, ahead of the arming handler at:1353.- The
objectqlmiddleware at:1252also callsrunEnsure, and both reachrunBackfillOnDefaultOrg(:1232) before arming. - So the flag is still the only guard on that path. The PR's Acceptance notes record that this middleware path is outside PR fix(check:settings-bind-window): judge the handlers of every hook fired before the bind, and follow promise continuations #22325's gate population.
OS_SKIP_MEMBERSHIP_BACKFILL=1still gates the whole block, so noapp:seededhandler is registered at all in that case.
Test: the start()-time pin became a pin of the new structure: 0
app:seededhandlers afterstart(), and exactly 1 afterkernel:ready. That assertion fails on the old structure. The two #22312 pins are unchanged.Changeset sentences checked against the diff:
- "Its handler used to be registered in
start()and kept from acting early by a runtime flag". - "The handler is now registered by that
kernel:readyhook, at the moment it arms the backfill, so it does not exist before the settings engine binds." - "An
app:seededthat fires before the backfill is armed still does nothing, and one that fires after it still re-runs the pass." - "
OS_SKIP_MEMBERSHIP_BACKFILL=1still registers noapp:seededhandler at all."
All four are true on the head. The third holds on every in-repo composition. The report's P3 (an emit still in flight across arming, behind a slower subscriber placed after the auth slot) is a synthetic composition with no in-repo instance. Its extra call runs after the bind and stops at
backfillDecided.Gate proof: the dev reports, with the commands in the PR body, that PR #22325's widened script goes red on
28bff18d0c, is green on this head with 0 ledgered entries, and goes red again under the ablation. The seat confirmed the red half on its own: PR #22325'sLint & Repo Gatesat96d46ea02d(job113445078033) fails with only that gate, on the same[app:seeded-hook-from-start]signature atauth-plugin.ts:1591. The green half becomes the seat's own reading when PR #22325 re-runs on amainthat carries this PR.CI, read by the seat at
9c8ed2b62e: all 34 check runs are complete, 31 of themsuccess, includingLint & Repo GatesandTypeScript Type Check. The 3 skips are all on the roster (check-expected-skips --pr 22333: OK).Deviations accepted: two scheduling non-identities, P3 and P4. Both come from appending at ready time, and neither drops an emit. On
os serve,PlatformObjectsPluginis composed beforeAuthPlugin, so no subscriber sits after the auth slot. Cloud-held compositions are NOT MEASURED, as the PR body says.check:dual-build-cjs-loadswas NOT MEASURED locally. CI runs it insideBuild Core, which issuccesson this head.Landing: ready + auto-merge now. PR #22325 then merges
mainand carriesFixes #22316.
Generated by Claude Code
- The
This card carries the
plugin-authhalf of #22316: one structural move inpackages/plugins/plugin-auth/src/auth-plugin.ts. The parent keeps the gate half, PR #22325 (scripts/check-settings-bind-window.mjs), which lands after this one.Filing gate: an in-flight derivative sub-issue of #22316 (
domain:devx,priority:p3inherited), filed and owned by the claiming seat (domain:devxseat 1, #6023,session_0115N1oNnQS5WqofZ2DzaT3q). Reader: that seat, which dispatches it directly.Why
os-dev-reporton tooling(check:settings-bind-window): the gate scores a plugin by its init/start bodies and kernel:ready handlers only, so a settings read from another Phase-2 hook (app:seeded) passed green while the showcase boot logged a Pre-bind READ #22316): onmainwith PR fix(plugin-auth): a seeded boot no longer reads the auth settings before the settings engine binds (#22257) #22312 merged, the gate reads RED atauth-plugin.ts[app:seeded-hook-from-start].app:seededbackfill calls no-ops through a runtime flag. Atorigin/main28bff18d0c,let backfillArmed = false(:1295) is set inside thekernel:readyhandler (:1353). Butctx.hook('app:seeded', () => runBackfill('app:seeded'))(:1364) is registered unconditionally fromstart(). A static walk cannot see a runtime flag.app:seededhandler from inside the armingkernel:readyhandler makes the widened gate read GREEN, with no ledger entry.Done when
app:seededhandler is registered from inside the armingkernel:readyhandler, so it cannot run before the bind by construction.auth-settings-ordering.pin.test.ts,auth-settings-seeded-boot.pin.test.ts), along with the package's suite. The developer judges whetherbackfillArmedis still needed and removes it only if it is provably dead.check:settings-bind-windowreads GREEN on a tree carrying this change.@objectstack/plugin-auth. ⛔ No behaviour, option or public-surface change.The decision behind this split is recorded on #22316.
Generated by Claude Code