You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
service-settings: the settings REST family (registerSettingsRoutes) answers an anonymous caller from its own authorization logic instead of the 401 floor; ADR-0056 D2 and ADR-0138 D2 require 401 — the fourth door blocking ADR-0138 criterion 2 #23142
Filing gate: ① a product defect with a named landing and a measured reach, reach: an anonymous HTTP request, measured on a booted reference deployment by the anonymous-sweep conformance suite of PR #23137 (os-dev-report6137316611 on #22146, at bf3ba9957). Route-level readings stay private; this card is at function level, as #22430, #22431 and #22432 were. Readers who act: triage, to route (packages/services/* is domain:services in the pm-dispatch domain table); then the owning seat. Blocks: ADR-0138 acceptance criterion 2 (#22146), and PR #23137, whose suite is red on this family alone.
Filed by domain:spec seat 2 (#18549) · os-justin · session session_01BRQ9Mp1yH7R1az6MaUM18H, from the #22146 dev report (out-of-scope finding 1). ⛔ Not a claim; grade and lane are triage's.
What happens
packages/services/service-settings/src/settings-routes.ts#registerSettingsRoutes (:107 on origin/mainc9b5b24b0), mounted by SettingsServicePlugin, opens no handler with an anonymous floor (shouldDenyAnonymous or the shared !userId && !isSystem → 401 decision). An unauthenticated request to the family is answered by the family's own authorization logic, with a status class other than 401. On the measured boot no settings value was served.
Governing text
ADR-0056 D2 (Accepted): the platform denies anonymous callers by default, one shared !userId && !isSystem → 401 decision.
An anonymous request to every route of the settings REST family answers 401 with the error envelope, before any settings logic runs; a signed-in caller is served as today. A reject-path test asserts code and status.
MCP search_issues on objectstack-ai/objectstack, closed included: settings service REST routes anonymous caller 401 registerSettingsRoutes shouldDenyAnonymous → 5 hits: #22430 and #22432 (closed, the OpenAPI and /i18n doors), #10349, #7911 and #7981 (closed, other surfaces). None is the settings family. Tree read: git grep shouldDenyAnonymous origin/main -- packages/services/service-settings/src → 0 hits; control registerSettingsRoutes in the same directory → 1 hit.
Triage: first grade, bug · security · priority:p1 · target:v18 · domain:services · area:access, into pm:queue. The settings family takes the shared anonymous floor
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-13T03:56Z. ⛔ Not a claim, ⛔ not a dispatch.
Claim: PM loop round 7 · 2026-10-13T04:18Z
Session: session_01FzQy26KznSa5j2gnf3AzsE
Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-23142-settings-anonymous-floor
Worktree: objectstack-issue-23142
Domain: domain:services
Seat: domain:services#2 (seat post #21118)
File surface: packages/services/service-settings/src/settings-routes.ts (one shared anonymous floor for the family), its tests under packages/services/service-settings/, one .changeset/23142-*.md (stop on breach; explain in the report)
Container & model: M (one shared floor, mounted once, with a per-route pin sweep), mode:subagent, model: default — dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate … The tier stays the PM's per-card judgment call"
Clause-②: no — it narrows the anonymous accept set to the ADR-0056 D2 floor; no published schema or export moves.
Responsibility: service-settings' REST family (registerSettingsRoutes, the platform's own code) opens no anonymous floor | none: the family runs its own authorization logic before any shared floor | any anonymous HTTP caller of the settings REST family; the measured boot served no settings value (PR #23137's sweep)
Thread-read: 6137894223
Serial constraints cleared: no pm:dispatched claim declares service-settings, and no open PR touches it (read at this claim). The last landing on the file was bbe03f4068 (#20836), not today. PR #23137 (domain:spec, the anonymous sweep) is red on this family alone and turns green when this lands. Its pins are not touched here.
{
"issue": 23142,
"status": "done",
"branch": "claude/issue-23142-settings-anonymous-floor",
"pr": "https://github.com/objectstack-ai/objectstack/pull/23171",
"session": "session_01FzQy26KznSa5j2gnf3AzsE",
"premise_still_valid": true,
"summary": "registerSettingsRoutes (packages/services/service-settings/src/settings-routes.ts) now mounts all four routes of the settings REST family through one door, mountFamilyRoute. The door resolves the caller with contextFromRequest (unchanged), relays an authorization-store outage as its 503, then asks the shared ADR-0056 D2 decision shouldDenyAnonymous from @objectstack/core and answers a context with no userId with 401 UNAUTHENTICATED (ANONYMOUS_DENY_STATUS / _CODE / _MESSAGE through the family's sendError envelope), before any settings logic runs. Each route became a { serve, refuse } pair holding its old try body and catch arm byte for byte, so the floor and the outage relay are each mounted once for the family, not copied per handler; isSystem is not read and the path is not passed, so a moved basePath inherits no allow-list exemption. H1 measured true at 02bec6ca86 (0 shouldDenyAnonymous hits in the package, control registerSettingsRoutes 1); H2 followed: the shared decision before the family's own logic, envelope written the way service-datasource's admin family and rest's package family write it. A signed-in caller is served as before (signed-in control per route), nothing that was refused is now admitted, and no consumer was found that reads the family anonymously (console settings views and the timezone prompt are signed-in; every in-repo dogfood caller sends a bearer token). Producer = the registrar, as the card said.",
"tests": "All at HEAD 3bb73c0cd1 unless noted. (1) pnpm --filter '@objectstack/service-settings^...' build under os-verify-lock: VERDICT command-exit 0; then the package build: exit 0, check-dts-emitted 2/2. (2) pnpm --filter @objectstack/service-settings test: 'Test Files 43 passed (43) / Tests 788 passed (788)', VERDICT command-exit 0; base 02bec6ca86 read 42 files / 755 tests. (3) pnpm --filter @objectstack/service-settings typecheck (tsc --noEmit): VERDICT command-exit 0; tsc --listFiles compiles 43 test files including the new settings-routes.anonymous-floor.pin.test.ts. (4) New pin file: census read off the registration (exactly 4 routes, equal to SETTINGS_ROUTE_LEDGER, no middleware); every captured route x 5 anonymous context shapes + a forged identity header, each asserting status 401 + error.code UNAUTHENTICATED + success false + shared message + no data, against a service that records and throws on any call (asserted untouched); a basePath the shared allow-list would exempt (non-vacuity asserted) still refused; signed-in control per route 200 with the caller's own context object reaching the service. (5) Fixture triage: 7 existing test files re-judged (signed-in fixtures gained a userId; pins of the retired anonymous 403/404/empty-list answers now assert 401; the capability refusals re-driven by a signed-in caller holding no capability). (6) Ablation, committed first, under the lock: scripts/ablation-replace.mjs removed the floor call (anchor 1 -> 0, blob c4f12f1dd2b9 -> 6e12080a10ea, on-disk floor-call count before/during/after 1/0/1, trap restore on EXIT INT TERM against an absolute path). Mutated suite: 'Test Files 5 failed | 38 passed (43) / Tests 38 failed | 750 passed (788)', 'AssertionError: expected 500 to be 401' / 'expected 403 to be 401' / 'expected 200 to be 401'. Of the new file's 30 cases, 25 went red (every anonymous case) and 5 stayed green (census + 4 signed-in controls); re-homed pins also red: tenancy posture 6, settings-routes 4, envelope 2, config-change audit 1. Restore proven by bytes: blob after restore == HEAD blob c4f12f1dd2b9, git diff HEAD empty. No build/dist leg: the pins import the subject relatively from src. (7) Gates: see the gates field. Narrowed CJS/ESM load of the changed package: require(dist/index.cjs) and import(dist/index.js) both load.",
"gates": "Re-derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) at tree 3bb73c0cd1: 65 commands, the same set as the PM's reading at gates.txt. All 65 were run, unlocked as check:* gates; the run ended 'ALL-DONE 3bb73c0cd1'. Results: 64 exit 0; 1 exit 3, pnpm check:dual-build-cjs-loads, which printed 'PREREQUISITE NOT MET ... some package has no dist/' (it reads every package's dist/ and only this package's dependency closure was built). That gate is NOT MEASURED, left to CI and narrowed per item (7) of tests. Reconciled with --ran plus a record of the recorded exit codes: 'Run reconciliation — 65 derived, 64 run, 1 NOT-MEASURED, 0 UNRUN' ('1 DERIVED from a recorded exit 3'). Gates read directly: check:adr-0087-registration '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition' (not-required (no-migration-prescription)); check:route-envelope exit 0; check:error-status-conformance exit 0; check:nul-bytes OK (11176 files); check:type-check-debt and check:type-check-coverage exit 0.",
"line_budget": "changed lines 607 (+509 / -98) across 10 files vs the human-merge threshold 3000: under (dispatch-gates reading). No skills/** or governed path touched.",
"files_changed": [
".changeset/23142-settings-anonymous-floor.md (+18)",
"packages/services/service-settings/src/settings-routes.ts (+132 -60)",
"packages/services/service-settings/src/settings-routes.anonymous-floor.pin.test.ts (+241, new)",
"packages/services/service-settings/src/settings-routes.test.ts (+53 -10)",
"packages/services/service-settings/src/settings-admission-tenancy-posture.test.ts (+26 -12)",
"packages/services/service-settings/src/envelope.conformance.test.ts (+20 -7)",
"packages/services/service-settings/src/settings-routes.authz-outage-relay.test.ts (+8 -2)",
"packages/services/service-settings/src/config-change-audit.test.ts (+6 -4)",
"packages/services/service-settings/src/localization-timezone-help.pin.test.ts (+3 -2)",
"packages/services/service-settings/src/settings-crypto-fail-closed.test.ts (+2 -1)"
],
"deviations": [
"Clause-② line copied verbatim from claim 6138137601 into the PR body and the changeset. It carries no direction arm, although its own prose says the change narrows. The changeset declares the narrowing through its BREAKING banner and the bang summary, with minor under the launch-window convention and an ADR-0087 disposition, as the sibling doors did. See open_questions.",
"The harness attribution reminder asked for a model-named Co-Authored-By trailer and a robot-emoji PR footer. Per the role file, the commits carry the model-free pair (Claude-Session + Co-authored-by: Claude) and the PR body ends in the session-URL footer.",
"Label writes: zero. The dispatch named no label, and skip-changeset does not apply because the package publishes. No label-write call was made.",
"Scope: all edits stayed in the claimed file surface. The 7 existing service-settings test files besides the new pin are 'its tests' that leaned on an anonymous caller.",
"Every heavy run went through os-verify-lock with slot issue-23142. The gate battery ran under nohup with foreground waits on tail --pid, and its runner has exited."
],
"mcp_calls": "0",
"api_writes": "2 relay strokes = 2 POST /repos/objectstack-ai/objectstack/dispatches (session token), executed by fleet-write.yml as objectstack-fleet[bot]: stroke 1 (run 38350659780, success) = POST /repos/objectstack-ai/objectstack/pulls (draft #23171) + POST /repos/objectstack-ai/objectstack/issues/23171/assignees (zhuangjianguo; body read back byte-identical); stroke 2 = POST /repos/objectstack-ai/objectstack/issues/23142/comments (this os-dev-report). Plus git push of the branch (not REST). Reads: gh api REST single-card and PR reads only.",
"open_questions": [
{
"question": "Should the PR body's declaration line carry the narrowing arm, reading 'Clause-②: no (narrowing) — ...'? The claim's line has no arm, but the change is an accept-set narrowing of a published export's behaviour. A host that calls registerSettingsRoutes with its own contextFromRequest returning no userId is now refused 401. Siblings #22430, #22431 and #22432 declared 'no (narrowing)'.",
"options": [
"A: the seat edits PR #23171's Clause-② line to 'Clause-②: no (narrowing) — ...' (one PR-body PATCH, the seat's to write). check-changeset-no-major then reads a declared breaking change; the changeset is already minor, so nothing else moves.",
"B: leave the line as claimed. Breaking-ness is still carried by the changeset's BREAKING banner and bang, which check:adr-0087-registration already reads green, but the PR body's declaration names no direction."
],
"recommendation": "A. It keeps the declared direction equal to the shipped one and matches the three sibling doors; the arm is the carrier the gates read, and the prose banner is the weaker one."
}
],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed. content/docs/protocol/kernel/config-resolution.mdx § HTTP Surface describes the family's capability gating but not the anonymous 401 floor in front of it. It is true but incomplete, and outside the claimed file surface.",
"carrier: PR #23137 · noted, not filed. The dogfood authorization conformance matrix (packages/qa/dogfood/test/authz-conformance.matrix.ts) draws its population from the REST and runtime route ledgers only. The settings family's floor is therefore ratcheted by its package-local pins and by #23137's live sweep, not by that matrix.",
"carrier: 承接者:无 · noted, not filed (inference, NOT MEASURED). The registrar does not consult the ADR-0069 auth-policy gate, and resolveAuthzContext does not apply it. Whether a session held by an auth policy reaches this family through host middleware was not measured."
]
}
Seat ACCEPT: PR #23171 at 3bb73c0cd · contract review PASS
domain:services seat 2 · session_01FzQy26KznSa5j2gnf3AzsE · 2026-10-13T05:43Z, under claim 6138137601 (thread-read 6137894223). The dev's report is 6138827602. Checked on GitHub and on origin/main, not from the report.
Scope: 10 files, +509 / -98, all under .changeset/ and packages/services/service-settings/src/, inside the claimed surface. check-governed-merges --pr 23171 reads 0 of 10 paths governed, under the 3000-line threshold.
The card's acceptance, executed:
mountFamilyRoute is the family's one door, on all four routes the ledger declares. It calls the shared shouldDenyAnonymous and answers 401 UNAUTHENTICATED before SettingsService is consulted.
The authorization-store outage relay stays ahead of the floor, so an outage still answers the declared 503.
A signed-in caller's answer is unchanged, pinned per route.
Tests and ablation (the dev's runs, reconciled by the record):
service-settings 788 of 788 across 43 files; the base had 755 tests across 42.
With the floor call removed, 38 cases went red: 25 of the new file's 30, plus 13 re-homed pins. The 5 controls stayed green. The restore was proven by blob identity.
Contract review: the contract-review-tier record 6139053386 is PASS on this head, written after every check-run had completed. It judges four things:
the accept-set change and floor exactness, neither wider nor narrower;
that nothing refused before is now admitted;
the order of outage relay and floor;
the seven-file fixture triage.
CI at 3bb73c0cd (latest run per name): 40 names, 32 success, 8 skipped, 0 red.
Writes:mcp_calls 0.
Open question answered: option A. The PR body line reads Clause-②: no (narrowing) — …. The changeset's own line still has no arm. That is a carrier drift, not a blocker: per the record, neither gate reads that arm, and the BREAKING banner and the bang are what reach the CHANGELOG.
Out-of-scope findings and carriers
content/docs/protocol/kernel/config-resolution.mdx § HTTP Surface: dropped. The page is true as written and claims no anonymous access; an incomplete page is not a filing-gate class, and no PR is editing it.
Landed · PR #23171 merged through the merge queue at 2026-10-13T06:05Z as 20949e7a7c, and that commit is on origin/main (read 2026-10-13T06:07Z). domain:services seat 2 · session_01FzQy26KznSa5j2gnf3AzsE, under claim 6138137601, contract review PASS 6139053386 and ACCEPT 6139109957.
On main:settings-routes.ts mounts all four ledgered routes through mountFamilyRoute, which calls the shared shouldDenyAnonymous and answers 401 UNAUTHENTICATED before any settings logic. The authorization-store outage relay stays ahead of the floor.
Queue path:ready_for_review, then auto_merge_enabled, then added_to_merge_queue at 2026-10-13T05:46Z. The PR's only closing keyword was Fixes #23142, and no other card closed in the window.
Board: this card closed completed by that keyword. The seat's write cleared pm:dispatched and the assignee; security, priority:p1, domain:services, target:v18 and area:access stay.
Publication: a merge to main does not publish. @objectstack/service-settings ships .changeset/23142-settings-anonymous-floor.md (minor, BREAKING narrowing) at the next version release.
Activity
objectstack-fleet commentedon Oct 13, 2026
Triage: first grade,
bug·security·priority:p1·target:v18·domain:services·area:access, intopm:queue. The settings family takes the shared anonymous floorTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-13T03:56Z. ⛔ Not a claim, ⛔ not a dispatch.Unblocks: #22146
RestServer.registerOpenApiEndpoints) answer an anonymous caller with the whole object model; ADR-0056 D2 and ADR-0138 D2 require 401 #22430, service-storage: a file with no scope and no field owner is downloadable by an anonymous caller who has its id; the download should require a signed-in caller, whileacl: 'public_read'stays anonymous (ADR-0104) #22431 and runtime: the/i18ndispatcher domain answers an anonymous caller —handleI18nRequestmakes noshouldDenyAnonymouscall, unlike every other dispatcher domain (ADR-0056 D2); with an objectui companion for the Console sign-in page #22432, all p1security). It is also the one door that keeps ADR-0138 criterion 2 (design(v18): the complete guest model in one ADR — identity, doors, grants channel, organization, public-site binding, disclosure, rate limits, and each declared guest key's fate (ADR-0090 D9 enforce-or-remove) #22146, p1) and PR test(dogfood): the anonymous door sweep — ADR-0138 criterion 2 as an in-repo conformance test #23137's sweep red, and a wall takes the level of the card it blocks. The measured boot served no settings value. This is a missing floor, not a measured leak.shouldDenyAnonymous, the ADR-0056 D2!userId && !isSystem → 401) before any settings logic runs.packages/services/service-settings/src/settings-routes.ts(domain:services); rationale: one floor, shared.objectstack-fleet commentedon Oct 13, 2026
Claim: PM loop round 7 · 2026-10-13T04:18Z
Session:
session_01FzQy26KznSa5j2gnf3AzsEAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-23142-settings-anonymous-floorWorktree:
objectstack-issue-23142Domain:
domain:servicesSeat:
domain:services#2(seat post #21118)File surface:
packages/services/service-settings/src/settings-routes.ts(one shared anonymous floor for the family), its tests underpackages/services/service-settings/, one.changeset/23142-*.md(stop on breach; explain in the report)Container & model:
M (one shared floor, mounted once, with a per-route pin sweep),mode:subagent,model: default — dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate … The tier stays the PM's per-card judgment call"Clause-②: no — it narrows the anonymous accept set to the ADR-0056 D2 floor; no published schema or export moves.
Responsibility:
service-settings' REST family (registerSettingsRoutes, the platform's own code) opens no anonymous floor | none: the family runs its own authorization logic before any shared floor | any anonymous HTTP caller of the settings REST family; the measured boot served no settings value (PR #23137's sweep)Thread-read: 6137894223
Serial constraints cleared: no
pm:dispatchedclaim declaresservice-settings, and no open PR touches it (read at this claim). The last landing on the file wasbbe03f4068(#20836), not today. PR #23137 (domain:spec, the anonymous sweep) is red on this family alone and turns green when this lands. Its pins are not touched here.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{ "issue": 23142, "status": "done", "branch": "claude/issue-23142-settings-anonymous-floor", "pr": "https://github.com/objectstack-ai/objectstack/pull/23171", "session": "session_01FzQy26KznSa5j2gnf3AzsE", "premise_still_valid": true, "summary": "registerSettingsRoutes (packages/services/service-settings/src/settings-routes.ts) now mounts all four routes of the settings REST family through one door, mountFamilyRoute. The door resolves the caller with contextFromRequest (unchanged), relays an authorization-store outage as its 503, then asks the shared ADR-0056 D2 decision shouldDenyAnonymous from @objectstack/core and answers a context with no userId with 401 UNAUTHENTICATED (ANONYMOUS_DENY_STATUS / _CODE / _MESSAGE through the family's sendError envelope), before any settings logic runs. Each route became a { serve, refuse } pair holding its old try body and catch arm byte for byte, so the floor and the outage relay are each mounted once for the family, not copied per handler; isSystem is not read and the path is not passed, so a moved basePath inherits no allow-list exemption. H1 measured true at 02bec6ca86 (0 shouldDenyAnonymous hits in the package, control registerSettingsRoutes 1); H2 followed: the shared decision before the family's own logic, envelope written the way service-datasource's admin family and rest's package family write it. A signed-in caller is served as before (signed-in control per route), nothing that was refused is now admitted, and no consumer was found that reads the family anonymously (console settings views and the timezone prompt are signed-in; every in-repo dogfood caller sends a bearer token). Producer = the registrar, as the card said.", "tests": "All at HEAD 3bb73c0cd1 unless noted. (1) pnpm --filter '@objectstack/service-settings^...' build under os-verify-lock: VERDICT command-exit 0; then the package build: exit 0, check-dts-emitted 2/2. (2) pnpm --filter @objectstack/service-settings test: 'Test Files 43 passed (43) / Tests 788 passed (788)', VERDICT command-exit 0; base 02bec6ca86 read 42 files / 755 tests. (3) pnpm --filter @objectstack/service-settings typecheck (tsc --noEmit): VERDICT command-exit 0; tsc --listFiles compiles 43 test files including the new settings-routes.anonymous-floor.pin.test.ts. (4) New pin file: census read off the registration (exactly 4 routes, equal to SETTINGS_ROUTE_LEDGER, no middleware); every captured route x 5 anonymous context shapes + a forged identity header, each asserting status 401 + error.code UNAUTHENTICATED + success false + shared message + no data, against a service that records and throws on any call (asserted untouched); a basePath the shared allow-list would exempt (non-vacuity asserted) still refused; signed-in control per route 200 with the caller's own context object reaching the service. (5) Fixture triage: 7 existing test files re-judged (signed-in fixtures gained a userId; pins of the retired anonymous 403/404/empty-list answers now assert 401; the capability refusals re-driven by a signed-in caller holding no capability). (6) Ablation, committed first, under the lock: scripts/ablation-replace.mjs removed the floor call (anchor 1 -> 0, blob c4f12f1dd2b9 -> 6e12080a10ea, on-disk floor-call count before/during/after 1/0/1, trap restore on EXIT INT TERM against an absolute path). Mutated suite: 'Test Files 5 failed | 38 passed (43) / Tests 38 failed | 750 passed (788)', 'AssertionError: expected 500 to be 401' / 'expected 403 to be 401' / 'expected 200 to be 401'. Of the new file's 30 cases, 25 went red (every anonymous case) and 5 stayed green (census + 4 signed-in controls); re-homed pins also red: tenancy posture 6, settings-routes 4, envelope 2, config-change audit 1. Restore proven by bytes: blob after restore == HEAD blob c4f12f1dd2b9, git diff HEAD empty. No build/dist leg: the pins import the subject relatively from src. (7) Gates: see the gates field. Narrowed CJS/ESM load of the changed package: require(dist/index.cjs) and import(dist/index.js) both load.", "gates": "Re-derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) at tree 3bb73c0cd1: 65 commands, the same set as the PM's reading at gates.txt. All 65 were run, unlocked as check:* gates; the run ended 'ALL-DONE 3bb73c0cd1'. Results: 64 exit 0; 1 exit 3, pnpm check:dual-build-cjs-loads, which printed 'PREREQUISITE NOT MET ... some package has no dist/' (it reads every package's dist/ and only this package's dependency closure was built). That gate is NOT MEASURED, left to CI and narrowed per item (7) of tests. Reconciled with --ran plus a record of the recorded exit codes: 'Run reconciliation — 65 derived, 64 run, 1 NOT-MEASURED, 0 UNRUN' ('1 DERIVED from a recorded exit 3'). Gates read directly: check:adr-0087-registration '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition' (not-required (no-migration-prescription)); check:route-envelope exit 0; check:error-status-conformance exit 0; check:nul-bytes OK (11176 files); check:type-check-debt and check:type-check-coverage exit 0.", "line_budget": "changed lines 607 (+509 / -98) across 10 files vs the human-merge threshold 3000: under (dispatch-gates reading). No skills/** or governed path touched.", "files_changed": [ ".changeset/23142-settings-anonymous-floor.md (+18)", "packages/services/service-settings/src/settings-routes.ts (+132 -60)", "packages/services/service-settings/src/settings-routes.anonymous-floor.pin.test.ts (+241, new)", "packages/services/service-settings/src/settings-routes.test.ts (+53 -10)", "packages/services/service-settings/src/settings-admission-tenancy-posture.test.ts (+26 -12)", "packages/services/service-settings/src/envelope.conformance.test.ts (+20 -7)", "packages/services/service-settings/src/settings-routes.authz-outage-relay.test.ts (+8 -2)", "packages/services/service-settings/src/config-change-audit.test.ts (+6 -4)", "packages/services/service-settings/src/localization-timezone-help.pin.test.ts (+3 -2)", "packages/services/service-settings/src/settings-crypto-fail-closed.test.ts (+2 -1)" ], "deviations": [ "Clause-② line copied verbatim from claim 6138137601 into the PR body and the changeset. It carries no direction arm, although its own prose says the change narrows. The changeset declares the narrowing through its BREAKING banner and the bang summary, with minor under the launch-window convention and an ADR-0087 disposition, as the sibling doors did. See open_questions.", "The harness attribution reminder asked for a model-named Co-Authored-By trailer and a robot-emoji PR footer. Per the role file, the commits carry the model-free pair (Claude-Session + Co-authored-by: Claude) and the PR body ends in the session-URL footer.", "Label writes: zero. The dispatch named no label, and skip-changeset does not apply because the package publishes. No label-write call was made.", "Scope: all edits stayed in the claimed file surface. The 7 existing service-settings test files besides the new pin are 'its tests' that leaned on an anonymous caller.", "Every heavy run went through os-verify-lock with slot issue-23142. The gate battery ran under nohup with foreground waits on tail --pid, and its runner has exited." ], "mcp_calls": "0", "api_writes": "2 relay strokes = 2 POST /repos/objectstack-ai/objectstack/dispatches (session token), executed by fleet-write.yml as objectstack-fleet[bot]: stroke 1 (run 38350659780, success) = POST /repos/objectstack-ai/objectstack/pulls (draft #23171) + POST /repos/objectstack-ai/objectstack/issues/23171/assignees (zhuangjianguo; body read back byte-identical); stroke 2 = POST /repos/objectstack-ai/objectstack/issues/23142/comments (this os-dev-report). Plus git push of the branch (not REST). Reads: gh api REST single-card and PR reads only.", "open_questions": [ { "question": "Should the PR body's declaration line carry the narrowing arm, reading 'Clause-②: no (narrowing) — ...'? The claim's line has no arm, but the change is an accept-set narrowing of a published export's behaviour. A host that calls registerSettingsRoutes with its own contextFromRequest returning no userId is now refused 401. Siblings #22430, #22431 and #22432 declared 'no (narrowing)'.", "options": [ "A: the seat edits PR #23171's Clause-② line to 'Clause-②: no (narrowing) — ...' (one PR-body PATCH, the seat's to write). check-changeset-no-major then reads a declared breaking change; the changeset is already minor, so nothing else moves.", "B: leave the line as claimed. Breaking-ness is still carried by the changeset's BREAKING banner and bang, which check:adr-0087-registration already reads green, but the PR body's declaration names no direction." ], "recommendation": "A. It keeps the declared direction equal to the shipped one and matches the three sibling doors; the arm is the carrier the gates read, and the prose banner is the weaker one." } ], "out_of_scope_findings": [ "carrier: 承接者:无 · noted, not filed. content/docs/protocol/kernel/config-resolution.mdx § HTTP Surface describes the family's capability gating but not the anonymous 401 floor in front of it. It is true but incomplete, and outside the claimed file surface.", "carrier: PR #23137 · noted, not filed. The dogfood authorization conformance matrix (packages/qa/dogfood/test/authz-conformance.matrix.ts) draws its population from the REST and runtime route ledgers only. The settings family's floor is therefore ratcheted by its package-local pins and by #23137's live sweep, not by that matrix.", "carrier: 承接者:无 · noted, not filed (inference, NOT MEASURED). The registrar does not consult the ADR-0069 auth-policy gate, and resolveAuthzContext does not apply it. Whether a session held by an auth policy reaches this family through host middleware was not measured." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Seat ACCEPT: PR #23171 at
3bb73c0cd· contract review PASSdomain:servicesseat 2 ·session_01FzQy26KznSa5j2gnf3AzsE· 2026-10-13T05:43Z, under claim6138137601(thread-read6137894223). The dev's report is6138827602. Checked on GitHub and onorigin/main, not from the report.Checklist
main, mergeableclean. The first line isFixes #23142, thenClause-②: no (narrowing) — …. The only closing keyword is on service-settings: the settings REST family (registerSettingsRoutes) answers an anonymous caller from its own authorization logic instead of the 401 floor; ADR-0056 D2 and ADR-0138 D2 require 401 — the fourth door blocking ADR-0138 criterion 2 #23142..changeset/andpackages/services/service-settings/src/, inside the claimed surface.check-governed-merges --pr 23171reads 0 of 10 paths governed, under the 3000-line threshold.mountFamilyRouteis the family's one door, on all four routes the ledger declares. It calls the sharedshouldDenyAnonymousand answers401 UNAUTHENTICATEDbeforeSettingsServiceis consulted.503.service-settings788 of 788 across 43 files; the base had 755 tests across 42.@objectstack/service-settings: minor, thefix(service-settings)!:summary, the BREAKING banner, and ADR-0087not-required (no-migration-prescription). It matches the three sibling doors (rest: the API-description endpoints (RestServer.registerOpenApiEndpoints) answer an anonymous caller with the whole object model; ADR-0056 D2 and ADR-0138 D2 require 401 #22430, service-storage: a file with no scope and no field owner is downloadable by an anonymous caller who has its id; the download should require a signed-in caller, whileacl: 'public_read'stays anonymous (ADR-0104) #22431, runtime: the/i18ndispatcher domain answers an anonymous caller —handleI18nRequestmakes noshouldDenyAnonymouscall, unlike every other dispatcher domain (ADR-0056 D2); with an objectui companion for the Console sign-in page #22432).6139053386is PASS on this head, written after every check-run had completed. It judges four things:3bb73c0cd(latest run per name): 40 names, 32 success, 8 skipped, 0 red.mcp_calls0.Open question answered: option A. The PR body line reads
Clause-②: no (narrowing) — …. The changeset's own line still has no arm. That is a carrier drift, not a blocker: per the record, neither gate reads that arm, and the BREAKING banner and the bang are what reach the CHANGELOG.Out-of-scope findings and carriers
content/docs/protocol/kernel/config-resolution.mdx§ HTTP Surface: dropped. The page is true as written and claims no anonymous access; an incomplete page is not a filing-gate class, and no PR is editing it.settings-route-ledger.ts:Acceptance notes. The carriers are PR test(dogfood): the anonymous door sweep — ADR-0138 criterion 2 as an in-repo conformance test #23137's live sweep, which covers this family now, and ADR-0138's card E2 under design(v18): the complete guest model in one ADR — identity, doors, grants channel, organization, public-site binding, disclosure, rate limits, and each declared guest key's fate (ADR-0090 D9 enforce-or-remove) #22146, which the ADR names as the matrix's owner. A pointer goes to design(v18): the complete guest model in one ADR — identity, doors, grants channel, organization, public-site binding, disclosure, rate limits, and each declared guest key's fate (ADR-0090 D9 enforce-or-remove) #22146 with the landing note.Landing:
pr_ready+automerge_enablethrough the relay. This seat follows it to MERGED.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Landed · PR #23171 merged through the merge queue at 2026-10-13T06:05Z as
20949e7a7c, and that commit is onorigin/main(read 2026-10-13T06:07Z).domain:servicesseat 2 ·session_01FzQy26KznSa5j2gnf3AzsE, under claim6138137601, contract review PASS6139053386and ACCEPT6139109957.main:settings-routes.tsmounts all four ledgered routes throughmountFamilyRoute, which calls the sharedshouldDenyAnonymousand answers401 UNAUTHENTICATEDbefore any settings logic. The authorization-store outage relay stays ahead of the floor.ready_for_review, thenauto_merge_enabled, thenadded_to_merge_queueat 2026-10-13T05:46Z. The PR's only closing keyword wasFixes #23142, and no other card closed in the window.completedby that keyword. The seat's write clearedpm:dispatchedand the assignee;security,priority:p1,domain:services,target:v18andarea:accessstay.domain:spec,pm:blockedon this card) and its PR test(dogfood): the anonymous door sweep — ADR-0138 criterion 2 as an in-repo conformance test #23137's sweep can now re-run againstmain; that unblock is its holder's to make. The seat leaves one pointer there for ADR-0138's card E2.maindoes not publish.@objectstack/service-settingsships.changeset/23142-settings-anonymous-floor.md(minor, BREAKING narrowing) at the next version release.Generated by Claude Code