Repository navigation
plugin-auth: one refused key in the auth settings pass drops every sibling setting, and says so only at warn #20412
Description
Activity
- addedbugSomething isn't workingSomething isn't workingarea:identityLogin and identity — sign-up, sessions, organization membership, SSOLogin and identity — sign-up, sessions, organization membership, SSO
on Sep 28, 2026 objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsClaim: PM loop round 1
Session:session_017B6YKCGu8CTY2KBWgwaHAs
Account:os-warren(the seat's linked user asGET /useranswers it; always the card's assignee)
Branch:claude/issue-20412-auth-settings-sibling-isolation
Worktree:objectstack-issue-20412
Domain:domain:services
Seat:domain:services#1
File surface:packages/plugins/plugin-auth/src/auth-plugin.ts(theapplySettingspass for theauthsettings namespace and its outercatch), new tests underpackages/plugins/plugin-auth/src/beside the existing auth-plugin settings tests,.changeset/20412-*.md. If the refusal can only be attributed insideAuthManager.applyConfigPatch,auth-manager.tsjoins the surface and the report says why. ⛔ Nopackages/spec, ⛔ noservice-settings. (Stop on breach; explain in the report.)
Container & model:M,mode:subagent,model: opus(dispatch-gates --tieron789b2ae5: no path-derived mandate, floor sonnet · default opus · ceiling fable)
Clause-②: no
Thread-read: none
Serial constraints cleared: read at 2026-09-28T09:29Z onorigin/main789b2ae5.auth-plugin.tslast moved in65352b7d(#20406, merged today, same-day churn). Zero of the 12 open PRs touchpackages/plugins/plugin-auth/. Among the 26 openpm:dispatchedcards, the only claim naming an auth or mail surface is #16506 (plugin-emailqueue-delivery test, thedomain:engineseat's), which is disjoint. The sibling #20413 (this seat, same round) edits onlyservice-settingstext anddocs/qa, so it is disjoint and pins nothing this card changes.Clause-②: no: the settings pass stops dropping accepted sibling keys when one key is refused. No key's own validation changes, and no published contract surface or error code is touched. This is a durability/consistency degradation (the console reads a setting as saved that the runtime never applied), so AGENTS.md's degradation rule puts the refusal aterror.
Generated by Claude Code
objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsos-dev-report
{
"issue": 20412,
"status": "done",
"branch": "claude/issue-20412-auth-settings-sibling-isolation",
"pr": "#20429",
"session": "session_017B6YKCGu8CTY2KBWgwaHAs — subagent, the dispatching PM session's id (mode:subagent)",
"premise_still_valid": true,
"summary": "Implemented the seat ruling that a refused key does not take its accepted siblings with it. AuthPlugin's auth settings pass (auth-plugin.ts applySettings) is now applied in pieces split along applyConfigPatch's two entry validators. Each settings key that lands in emailAndPassword (assertAudienceConfig) or plugins (assertScimAdminCoherence) is applied alone. mfa_required=true stays one piece with the twoFactor plugin it turns on. Every other key goes out in one application the manager does not validate. Each piece has its own try; a refusal logs ONCE at error, naming the key, stating that the standing value keeps ruling and that the console shows the stored one, followed by the manager's remedy-bearing message, as the audience block beside it does. The outer catch now sees only a whole-pass failure (the namespace read) and moved from warn to error ('auth settings NOT APPLIED'), because that is the line the card names. The ruling's premise held: attribution needs no validation copied into the plugin, and per-key verdicts equal the whole-block verdicts on this tree. The landing point is therefore the plugin, with no auth-manager.ts change. PM mechanism assumptions held: applySettings starts at line 1452 on 40b315b with one patch under a warn catch, and the card's remaining triggers are live. Measured on unmodified main 40b315b, 5 new pins red and the control green: the console door (posture opened through the console, then a console-stored require_email_verification false), stack-config open plus a console false, email_domain plus an env false, and the SCIM/admin plugins refusal all left session.expiresIn undefined. Per AGENTS.md (a new durability seam joins DURABILITY_CRITICAL_CALLEES in the fixing PR), applyConfigPatch was added to the gate vocabulary. That pulled in the census's by-value copy and one engine-double ledger row for the new test; see deviations.",
"tests": "Final head 950c40c. (1) Measurement on unmodified main 40b315b: 'pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 src/auth-settings-refusal-isolation.test.ts' gave 'Tests 5 failed | 1 passed (6)', failing on 'expected undefined to be 259200' x3, 'expected undefined to be 604800' x1 and 'expected [] to have a length of 1' (read-failure level). (2) After the fix, the new file plus audience-posture-setting, auth-plugin, membership-policy-setting and auth-settings-ordering.pin: 'Test Files 5 passed (5) / Tests 129 passed (129)'. (3) Full package: 'pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2' gave 'Test Files 115 passed (115) / Tests 2460 passed (2460)' at 846b285; later commits touch only scripts/ ledgers and the census copy. (4) 'pnpm --filter @objectstack/plugin-auth run typecheck' after building plugin-auth: exit 0, 'check:test-typecheck: OK'. The new test is in the tsconfig.test.json program (--listFiles: 1). The first typecheck attempt exited 2 on examples/basic-usage.ts TS2307 because plugin-auth's own dist was absent: a prerequisite, not a red gate; re-run after the build was green. (5) Ablation leg 1, the old whole-pass drop put back: auth-plugin.ts was restored from 40b315b (tree only). On-disk proof: blob dd862c8d equals the base blob, old-warn count 1, applyPiece count 0. Pins 'Tests 5 failed | 1 passed (6)'. check:durability-log-level red: '1 durability report(s) degrade quietly … auth-plugin.ts:1799'. Restore: git checkout HEAD, blob 45e547d5 equals the HEAD blob, git diff HEAD empty. (6) Ablation leg 2, isolation kept but the refusal logged at warn, via scripts/ablation-replace.mjs (anchor 1 to 0, blob 45e547d5 to 0a104f02): pins 'Tests 4 failed | 2 passed (6)', each at 'expected [] to have a length of 1' (2 for the plugins test). Gate red at auth-plugin.ts:1767. Restored: blob equals HEAD, git diff HEAD empty. Neither ablation needed a build: the test imports AuthPlugin by a relative source path. (7) eslint --no-inline-config --format json on the 4 lintable changed files: 4 files, 0 errors, 0 warnings. Population from eslint.config.mjs files glob '**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}' minus NEVER_LINTED, with none of the 4 reported ignored. Invariance: no parserOptions.project or projectService anywhere, so type-aware linting is off. (8) Derived union at 950c40c: 86 commands, 84 exit 0, 2 exit 3 (NOT MEASURED, see gates). 'dispatch-gates --ran' prints '86 derived famil(ies) accounted for — 84 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)', 0 UNRUN. (9) Extra, because the diff edits their inputs: check:optional-error-sink, check:startup-registry-verdict, measure-return-propagating-durability-seams, measure-durability-swallow-family --self-test (all families), and metadata-protocol sys-metadata-repository.draft-drain.test.ts (reads the gate file; 11/11). All exit 0. check:durability-log-level: 38 seams (was 36), all loud.",
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "3 — each sent as one fleet-write relay repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches), executed as objectstack-fleet[bot]: (1) pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft, #20429; relay run 36413021977 success); (2) label-write --assign os-warren: POST /repos//issues/20429/assignees (relay run 36413143343 success; zero label writes, read-back matches); (3) this os-dev-report comment: POST /repos//issues/20412/comments. git push: 5 pushes of the branch (not REST).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted, not filed · observation, pre-existing, order unchanged by this PR: the verification piece is judged against the standing posture before the same pass's audience declaration applies (the audience block's comment records this order as deliberate). A single save that closes open to invite_only AND stores require_email_verification false logs one REFUSED line for that key, although the effective requirement already matches (invite_only resolves an undeclared value to off) and the next pass accepts the stored false. Before this PR the same save dropped the whole pass at warn. In the PR's Acceptance notes."
],
"gates": {
"head": "950c40c5",
"derived": 86,
"ran_exit_0": 84,
"not_measured": [
"pnpm check:dual-build-cjs-loads — exit 3 PREREQUISITE NOT MET: reads every workspace package's dist, 39 absent in this worktree; targeted probe: plugin-auth dist/index.js (require) and dist/index.mjs (import) both load, AuthPlugin is a function",
"pnpm check:type-check-debt — exit 3 PREREQUISITE NOT MET: ledgered packages runtime, service-cluster, service-job have no built type entry; diff touches none of them; plugin-auth typecheck + check:test-typecheck green"
],
"unrun": 0,
"reconciliation": "dispatch-gates --ran: 86 derived famil(ies) accounted for — 84 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)",
"ci": "in_progress — not awaited"
},
"line_budget": {
"changed_lines": 514,
"additions": 477,
"deletions": 37,
"files": 6,
"human_merge_threshold": 5000,
"verdict": "under"
},
"deviations": [
"file surface widened beyond the claim (auth-plugin.ts + tests + changeset), all under scripts/: scripts/check-durability-degradation-log-level.mjs (applyConfigPatch added to DURABILITY_CRITICAL_CALLEES, per the AGENTS.md rule that a new seam joins the vocabulary in the PR that fixes it), scripts/measure-durability-swallow-family.mjs (the census's by-value gate-vocabulary copy; check:swallow-census-controls reds on the drift otherwise), scripts/engine-double-contract.pinned.json (one row via check-engine-double-contract --write for the new test's findOne double, as the gate itself prescribed). No packages/spec, no service-settings, no auth-manager.ts.",
"outer catch lifted from warn to error ('[auth] auth settings NOT APPLIED'): after the split it sees only a whole-pass failure (the namespace read). This is the line the card names as the defect, levelled by AGENTS.md's durability/consistency question.",
"attribution form conflict, noted: the harness reminder asked for a model-named Co-Authored-By trailer and a different PR footer. Commits carry AGENTS.md's model-free pair (Claude-Session + 'Co-authored-by: Claude'); the PR body ends with AGENTS.md's session-URL footer. The pre-push hook accepted all 5 pushes.",
"check:pm-dispatch-gates was run detached (its battery took 1026.8s on the shared box, beyond the foreground cap) and read from its log at 950c40c: exit 0, 'dispatch-gates self-test: 1976 cases pass'. The process finished before this report."
],
"files_changed": [
".changeset/20412-auth-settings-sibling-isolation.md",
"packages/plugins/plugin-auth/src/auth-plugin.ts",
"packages/plugins/plugin-auth/src/auth-settings-refusal-isolation.test.ts",
"scripts/check-durability-degradation-log-level.mjs",
"scripts/engine-double-contract.pinned.json",
"scripts/measure-durability-swallow-family.mjs"
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsACCEPT: PR #20429 at
950c40c5(Fixes #20412)domain:servicesseat (#6021) ·session_017B6YKCGu8CTY2KBWgwaHAs· 2026-09-28T11:05Z. This reviews report5868596370against GitHub andorigin/main.Review items
-
PR shape. Draft, base
main, headclaude/issue-20412-auth-settings-sibling-isolation(the claim's branch), assigneeos-warren. The first line isFixes #20412, with no other closing keyword in the body.Clause-②: nois line-start in the body and in the changeset. -
The ruling, executed. The seat's ruling (claim
5867200492, dispatch) was: a refused key must not take its accepted siblings with it, and each refusal is logged once aterror, naming the key. The ruling's premise was attribution without copied validation, and the report measured it held. Theauthpass is split alongapplyConfigPatch's two entry validators (assertAudienceConfigforemailAndPassword,assertScimAdminCoherenceforplugins), withmfa_requiredkept together with itstwoFactorplugin. The outer catch now sees only a whole-pass failure and moves toerror. There is noauth-manager.tschange, so the plugin is the landing point. -
Pins and their failure proofs.
- On unmodified
mainthe new file reads5 failed | 1 passed (6), withsession.expiresInleftundefinedacross the four live triggers. - Leg 1 (the whole-pass drop put back) turns the pins red 5 of 6 and
check:durability-log-levelred. - Leg 2 (isolation kept, level
warn) turns them red 4 of 6. - Both restores are proven by blob equality with
HEAD. The fullplugin-authsuite is 2460/2460.
- On unmodified
-
Surface beyond the claim, amended in this round. Three
scripts/files:check-durability-degradation-log-level.mjs:applyConfigPatchjoinsDURABILITY_CRITICAL_CALLEES. AGENTS.md: "found a new one, add it toDURABILITY_CRITICAL_CALLEESin the same PR that fixes it".measure-durability-swallow-family.mjs: the census's by-value copy of that vocabulary, whichcheck:swallow-census-controlsrequires to stay in step.engine-double-contract.pinned.json: one row for the new test'sfindOnedouble, written by the gate's own--write.
This strengthens an existing gate's vocabulary; it is not a new gate. Gate scripts anchor to
domain:spec. This cross-lane touch is declared here and in the round report. At this reading, no other open PR touches any of the three files. Claim surface amended: these three files joinauth-plugin.ts, the test and the changeset. -
Changeset.
@objectstack/plugin-authpatch,Clause-②: no. Right: a published package's runtime behaviour moves (accepted settings now apply), and no key, option or error code changes. A contract review is owed on the.changesetprose. It runs atCONTRACT_REVIEW_TIERbefore readiness. -
Out-of-scope finding. A single save that closes
opentoinvite_onlyand storesrequire_email_verification: falselogs one REFUSED line for that key, because the verification piece is judged against the standing posture first; the next pass accepts it. This is pre-existing ordering that the audience block's own comment records as deliberate. Before this PR the same save dropped the whole pass. ⇒ Acceptance notes (carrier: none). Noreach:to a wrong answer at a public door, so no card. -
CI. At this reading on
950c40c5: 16 success, 3 skipped, 12 in progress, 0 red. Not ready until every check is green and the contract review passes. -
Report hygiene.
mcp_calls0.api_writes3, all through the relay. Two families are NOT MEASURED locally on declared prerequisites (check:dual-build-cjs-loads,check:type-check-debt), and CI answers both.
The round-1 contract review of PR #20421 (
5868073710) independently named this card's shape onmainas a live defect, a console-storedfalseunderopendropping the pass's siblings atwarn. This PR is its fix.
Generated by Claude Code
-
objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsLanded: PR #20429 →
7d630889(merge queue). The card closedcompleteddomain:servicesseat (#6021) ·session_017B6YKCGu8CTY2KBWgwaHAs· 2026-09-28T12:07Z.- Delivered on
origin/main:7d63088958(fix(plugin-auth): a refused auth setting no longer drops the settings saved with it). Record: ACCEPT5868627018, contract review PASS5868779058atCONTRACT_REVIEW_TIERon head950c40c5, every check green. - What changed for a user: when the settings console stores one value that the auth manager refuses, the other auth settings saved in the same pass (password policy, MFA, rate limits, session lifetime, social providers) now apply. The refused key is logged once at
error, by name.applyConfigPatchis now in the durability gate's vocabulary. - Board clean-up in this write: the closing keyword closed the card but left
pm:dispatchedand the assignee. Both are cleared here, per the release rule.
Release: session
session_017B6YKCGu8CTY2KBWgwaHAs· 因: delivered (Fixes #20412, merged) · 去向: closedcompleted.
Generated by Claude Code
- Delivered on
- added 2 commits that reference this issue
on Sep 29, 2026
Filing-gate: ① product defect with a named landing point, reach measured
Filed on the maintainer's instruction in session
session_d8bf7d34-4f31-407d-ac85-6cb849eec4bf(2026-09-28), verbatim: 「直接开卡吧」. The finding was surfaced by theos-devon #20389 (report comment5865880545) and escalated by the contract review on PR #20406 (comment5866574059).Defect
packages/plugins/plugin-auth/src/auth-plugin.ts,applySettings(read atmain7db1332f19): the mainthis.authManager.applyConfigPatch(patch)for theauthsettings namespace runs inside one outertry. Itscatchonly logsctx.logger.warn('Auth: failed to apply auth settings: ' + …).⇒ When
assertAudienceConfig(or any other entry validation) refuses ONE key in the pass, the whole patch is dropped. Password policy, MFA, rate limits, session expiry and social providers from that pass are not applied. The settings console still shows them as saved, and the only trace is awarn.The audience block right beside it already does the opposite, on purpose. It has its own
try, and on refusal logs aterror:[auth] audience settings REFUSED — the standing posture (…) keeps ruling.Reach (measured)
Measured by the dev on
maindb74b169dc: stack configaudience.posture: 'open',OS_AUTH_REQUIRE_EMAIL_VERIFICATION=falseandsession_expiry_days = 3. Result: the verification key is refused andsession.expiresInstaysundefined, with only thewarnabove.After #20406 merges, that particular trigger is gone. The path stays reachable through any other refused key:
email_domainplus an envfalse, a console-storedfalseunderopen, or apluginsSCIM/admin coherence refusal.Expected
A refused key does not take its siblings with it: either isolate the refused key and apply the rest, or refuse the pass loudly at
error, naming the key, so the console and the log cannot disagree with what is applied. Which of the two is the landing seat's call; the silentwarnis the defect.Reader
The
domain:servicesexecution seat, frompm:queue.Dedupe
Search (open + closed) on 2026-09-28:
"failed to apply auth settings"→ 2 hits,applySettings sibling auth settings dropped→ 2,auth settings pass refused key dropped→ 69. The only topical hits are #20389 / #20406 themselves, which name this finding as out of scope. No existing card.