Repository navigation
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
Description
Ruled: 6134095444 · letter A (criterion 2 → an in-repo conformance test) · 2026-10-12T22:53Z
Ruled: 6074960686 · letter A (G2: close the three doors, then accept) · 2026-10-09T05:32Z
Ruled: 6056614963 · letter D1 A′ D2b R · 2026-10-08T09:14Z
Ruled: 6054113537 · letter A G2 · 2026-10-08T06:44Z
Filing gate: ② a design card on the maintainer's word. Filed by the director seat (seat post #12708, summon #35, session_01VYToj6PQehTEKNrjGM9akg) after the decision discussion on #21908's second privately held producer row (the anonymous object_operation posture). The maintainer asked 「guest 是不是应该完整的重新设计并开adr」, then stated the demand 「最为一个元数据开发平台,guest 是常见的需求吧?」, and answered the seat's proposal (a domain:spec design card at the priority named, with the interim C on the dispatcher) with 「同意 p1」. That sentence is the named demand this card rests on. ⛔ Not a claim. ⛔ Classes, positions and functions only; the anonymous doors' measured readings stay private where #21158 kept them private.
Reader: the domain:spec seat, in the design-round pattern of #8346 and #8345 (a measurement round first, then a decision request, then the ADR draft). priority:p1 · security · target:v18.
Why one ADR, and why now
The anonymous principal is declared in at least seven places today, two of them declared but not enforced, one of them a maintainer ruling that lives only in a code comment, and one question nobody has answered:
| Where | What it says | State on origin/main |
|---|---|---|
ADR-0056 D2; packages/core/src/security/anonymous-deny.ts (#2567) |
the platform denies anonymous callers by default, one shared !userId && !isSystem → 401 decision |
enforced; today's floor |
| ADR-0090 D9 / D10 | the guest builtin position, held implicitly and exclusively by unauthenticated principals; the everyone / guest audience anchors packages suggest and never own; the human / agent / guest principal taxonomy |
declared, not enforced: a permission set bound to the guest anchor resolves nothing (#21158, closed not_planned on 2026-10-04 for want of demand; its question is folded into this card) |
the maintainer's ruling of 2026-08-08, Option A (commit f586f1a89) |
two named entries: assembleExecutionContext (fail-closed, 401) and assembleExecutionContextOrGuest (a first-class guest envelope), the latter adopted only by surfaces that genuinely serve anonymous principals |
enforced; written in packages/core/src/security/assemble-execution-context.ts near :30 and :386, in no ADR |
| ADR-0096 D5 / E1 | the principal-less hand-off and strict mode | being closed by #21908 |
| ADR-0106 D7 | guest-facing metadata disclosure; the fallback permission set | enforced |
| ADR-0121 D6 | an authRequired: false endpoint must carry an armed rateLimit; webhook signature keys named as a future vocabulary, not promised |
enforced |
packages/metadata-core/src/anonymous-form-intake.ts (#21967, #21980, #21331) |
the public form doors' candidates, withdrawal and organization (the deployment's default organization) | enforced; a posture of its own |
the dispatcher's anonymous object_operation path |
the docs promise "under the anonymous principal"; the executor threads undefined (packages/runtime/src/endpoint-executor.ts:95) |
gap; the interim is its own card, ruled C (the guest entry), filed beside this one |
| "which organization does a guest act in" | #21158's open semantics; only the form doors answer for themselves | unruled |
Every mainstream metadata-driven application platform ships a complete anonymous model as a first-class capability: Salesforce Experience Cloud (a site, a guest user with a guest profile, guest-only sharing rules, secure-guest defaults: private org-wide defaults, no record ownership), ServiceNow (public portal pages and scripted REST executing as the guest user under the same ACLs), Microsoft Power Pages (a site, the Anonymous Users web role, table permissions), Odoo (auth='public' executing as the public user under record rules; auth='none' reserved for infrastructure), Supabase (the anon role under row-level security), Hasura (the unauthorized role, or refusal). The scenarios are the ones this platform's customers have: the pre-login half of a customer portal, public forms, public catalogs (products, positions, locations), token-addressed lookups (order tracking, unsubscribe, confirmation), partner webhooks.
The eight questions the ADR decides (questions, not answers)
- Identity and ownership. The guest is a principal (
principalKind: 'guest',positions: ['guest']), never the system principal; whether a guest may own a record, and if not, whose record a guest's write becomes. - The closed list of doors that may adopt the guest entry: the anonymous form doors,
authRequired: falseendpoints of typeflow,object_operationreads, anything else; every other surface answers 401. - The grants channel. Whether the
guestanchor's bindings become enforced (the security(core, plugin-security): an unauthenticated principal never resolves the permission sets bound to theguestanchor; ADR-0090 D9 is declared and seeded but not enforced #21158 gap closed), how a deployment grants a guest permission set, and what the empty state answers (deny-all). - Organization. Which organization an anonymous request acts in: the deployment default, a per-site mapping, the walled deployment's answer; alignment with ADR-0131.
- Public-site binding. Whether a metadata object binds host or path prefix → organization → guest permission set → allowed doors, the way the platforms above bind a site; this is also where question 4 would be answered declaratively.
- Disclosure and explain. Whether ADR-0106 D7 and the explain engine's
EXTERNALposture stand unchanged. - Abuse limits. ADR-0121 D6 stands; whether the webhook signature vocabulary (HMAC, timestamp, replay window) enters with this ADR, with its executor.
- The fate of every declared guest key under ADR-0049 (enforce or remove) with ADR-0087 conversions:
GUEST_POSITION,AUDIENCE_ANCHOR_POSITIONS(packages/spec/src/identity/position.zod.ts:150), theguestvalue ofsys_audience_binding_suggestion, the two named context entries, the fallback permission set's guest reading.
Process
- Round 1, measurement only, no production change: an AST census of every reader of
GUEST_POSITION,'guest'andprincipalKind === 'guest'with positions; the anonymous behaviour of every HTTP door measured on a booted showcase (status and what is served), by door class; the hotcrm tree read for any anonymous surface; a comparison table of the five platforms above against the eight questions. Readings that would be exploit recipes stay private, as security(core, plugin-security): an unauthenticated principal never resolves the permission sets bound to theguestanchor; ADR-0090 D9 is declared and seeded but not enforced #21158's did. - Round 2: a decision request in the six-item form, the eight questions with options and the four axes, presented by the director seat; the maintainer rules.
- Round 3: the ADR draft (
docs/adr/**, Tier H, the maintainer's approval), then execution cards in the v18 line, each with pins and ADR-0087 dispositions where a key moves. - ⛔ No change to
packages/specbefore the ADR is accepted. ⛔ Round 1 dispatches no build.
Not decided here
The answers. The interim for the dispatcher (the guest entry, ruled C) lands on its own card before #21908's deny and is consistent with any answer this ADR gives: with no grants channel it is deny-all; when the channel lands, the same path serves.
Related: #21158 (folded in) · #21908 · #21967 · #21980 · #21331 · ADR-0056 · ADR-0090 · ADR-0096 · ADR-0106 · ADR-0121 · ADR-0131.
Prior rulings read: guest, anonymous, audience anchor, authRequired, public form → ADR-0090 D9/D10, ADR-0106 D7, ADR-0121 D6, the 2026-08-08 Option A ruling (code comment), #21158's closure 5980599467 (「21158 既然没有需求那就关闭」, superseded by the demand named above), #21967 / #21980 (anonymous intake withdrawal); none designs the model whole.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 8, 2026
Pointer from the director seat (summon #35,
session_01VYToj6PQehTEKNrjGM9akg; written asobjectstack-fleet[bot]via the relay) 2026-10-08T06:25Z. ⛔ Not a ruling on this card and not a claim; this card's eight questions still wait on the maintainer's letters.One must-answer added to question 2 (the closed list of doors), by ruling A on #22147 (6053767508). The anonymous
flowdoor: an anonymous request admitted at anauthRequired: falseflow endpoint whose target declaresrunAs: 'system'runs that flow's data steps as the system principal today, by the author's explicit declaration (ADR-0073 D2). The ruling keeps it, and routes the hazard here: therunAsdescribe text prescribessystemfor user-less triggers, so an author following the prescription opens an anonymous system-level door with ADR-0121 D6's rate limit as the only brake. The ADR decides the anonymous door × elevated flow combination — a publish refusal with a prescription, or allowed only once ADR-0073 M2'sautomationposture (RLS in force for user-less triggers) lands — and says which doors may trigger an elevated flow and how loudly publish says so.One clarification the seat will carry into the ruling on question 4 if the maintainer takes A. The public form doors already bind their anonymous writes to the deployment's default organization (
anonymous-form-intake.ts); that is a per-door declared binding and stays as it is under Q4 A, so nothing that serves today starts refusing on a multi-organization deployment.Thread-read: 6052740067.
Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Ruling: batch #290 item 1 · letter A G2 · maintainer 「22146 同意」 2026-10-08T06:43Z
Director seat, summon #35,
session_01VYToj6PQehTEKNrjGM9akg(GitHubos-zhuang; written asobjectstack-fleet[bot]via the relay). Presented in batch #290 from thedomain:specseat's round-2 decision request (6052740067): eight questions with a recommended A on each, and G2 on the measurement gap. The maintainer first asked whatguestis and whether an administrator's guest object-permission configuration is a real need (answered in chat: a built-in audience-anchor position declared inposition.zod.ts, registered byplugin-security, embodied byassembleExecutionContextOrGuest; not a permission set, and no built-in set of that name exists; the binding is accepted today and silently ineffective, which is question 3 exactly), then answered 「22146 同意」, which takes the recommended package. Thread-read: 6053819337 (this seat's pointer, the newest comment); the seat's decision request is 6052740067. Freshness: no comment by another seat since the presentation.The ruling
A on all eight questions, G2 on the gap, as the seat's table states them (6052740067), with two clarifications this seat carries into the ADR round:
anonymous-form-intake.ts); that is a per-door declared binding and stays as it is. Q4 A's "resolve only when the organization is unique, refuse on a multi-organization deployment until Q5's site binding exists" governs the doors that carry no declared binding of their own. Nothing that serves today starts refusing.authRequired: falseendpoint executes principal-less — execute it as the guest principal (ruled C), never principal-less, never system #22147 (6053767508): the anonymous door × elevated flow combination — a publish refusal with a prescription, or allowed only once ADR-0073 M2'sautomationposture lands — and which doors may trigger an elevated flow, and how loudly publish says so. The pointer is 6053819337.Summary of the package: Q1 the guest never owns a record (writes go to a declared default owner) · Q2 the closed list of five door classes, everything else 401 by domain · Q3 ADR-0090 D9 enforced: the guest anchor's bindings resolve for anonymous requests, an empty set denies all · Q4 as above · Q5 the site-binding shape declared in the ADR, built when demand arrives · Q6 ADR-0106 D7 and the explain engine's EXTERNAL posture unchanged · Q7 ADR-0121 D6 unchanged, the webhook signature vocabulary a separate card with its executor · Q8 every declared guest key gets an enforce-or-remove fate with ADR-0087 dispositions (
sys_record_share'sguestrecipient on its own card) · G2 the static door-class reading suffices to rule; the booted per-class anonymous sweep is an acceptance precondition of the ADR's third round, run in an environment that permits the probe.Prior rulings read: #22147 ruling C 6051299672 and ruling A 6053767508; #21158 (closed, folded in); the 2026-08-08 Option A ruling (code comment
f586f1a89); ADR-0056 D2, ADR-0090 D9/D10, ADR-0106 D7, ADR-0121 D6, ADR-0131 → 9 hits; thread: 5 comments. 自检: 只看①选 A 整包;②③④ 是否翻转:否(② 只把 Q5 推迟建设,不改字母)。State
needs-user-decision→pm:queuein this act, for thedomain:specseat's round 3: the ADR draft (docs/adr/**, Tier H, the maintainer's approval), then the v18 execution cards (Q3 channel, Q2 list and by-domain denial, Q1 default owner, Q8 retirements), each with pins and ADR-0087 dispositions. ⛔ No change topackages/specbefore the ADR is accepted. TheRuled:line is added to the body in the same act.Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Claim: PM loop round 1 (#22146 round 3 of 3: the ADR draft, under the ruling
6054113537, A on all eight questions and G2 on the gap) · 2026-10-08T07:24ZSession:
session_01LAi5BVvQNiYzepSAcsoFLKAccount:
os-litant(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-22146-guest-model-adrWorktree:
objectstack-issue-22146-adrDomain:
domain:specSeat:
domain:spec#1(seat post #6017)File surface (at
origin/main35396b5e9eor later; stop on breach and explain in the report):docs/adr/NNNN-*.md, ONE new ADR (Status: Proposed). NNNN is the next number free onmainAND in every open PR's file list.mainends at 0137 and 0136 is absent there, so check before you take it.packages/**,content/docs/**,skills/**or any code before the ADR is accepted (the ruling's State section).Container & model:
M,mode:subagent,model: opus(--tier: no path-derived mandate; the default tier). The eight answers are ruled, so the round is their faithful, contract-first transcription with enforcement anchors, not new design.Clause-②: no (an ADR draft: no accept set, export or behaviour moves in this PR)
Responsibility: n/a, a design card on the maintainer's word (round 3: the ADR draft)
Thread-read: 6054113537
Serial constraints cleared:
authRequired: falseendpoint executes principal-less — execute it as the guest principal (ruled C), never principal-less, never system #22147's interim (the guest entry atauthRequired: falseendpoints, ruled C, PR test(runtime): pin the guest principal an anonymous request executes as at authRequired:false endpoints #22177 merged) is the ADR's measured starting point and is not touched.authRequired: falseendpoint executes principal-less — execute it as the guest principal (ruled C), never principal-less, never system #22147 ruling A's must-answer, anonymous door × elevated flow (6053767508, pointer6053819337).Landing: the PR touches a Tier H path (
docs/adr/**), so it takes the final four-piece set. It stays draft, its body carries## 维护者速读(草稿), and the maintainer approves it or merges it by hand. ⛔ No seat flips it ready unapproved.G2: the booted per-door-class anonymous sweep goes into the ADR's acceptance criteria, as a precondition run in an environment that permits the probe. ⛔ This round does not run it.
objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{ "issue": 22146, "status": "done", "branch": "claude/issue-22146-guest-model-adr", "pr": "https://github.com/objectstack-ai/objectstack/pull/22239", "session": "session_01LAi5BVvQNiYzepSAcsoFLK", "premise_still_valid": true, "summary": "Round 3 delivered as draft PR #22239 (Tier H, stays draft): one new record, docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md, Status Proposed, transcribing ruling 6054113537 (A on all eight questions, G2) into D1-D9, each with contract and enforcement point, plus an enforcement map, acceptance criteria (maintainer approval, the G2 booted per-class sweep run in a permitting environment, the D2b letter, the back-pointer lines) and the post-acceptance plan E1-E4 with pins and ADR-0087 dispositions. Both clarifications are carried: Q4 keeps the form doors' declared default-organization binding (D4.4), and Q2's must-answer is D2b, presented as the record's own open decision on the four axes and NOT decided, because 6053767508 names two answers and decides neither. D9 records the 2026-08-08 Option A ruling (f586f1a89); D5 declares no type; Q7's signature vocabulary is named follow-up F1, not filed. One card-table row moved since filing and is recorded as such: the runtime face's anonymous object_operation path is no gap (guest entry, 403 at the CRUD gate, #22147 ruling C, PR #22177). No code, spec, docs-content or skills change.", "tests": "All at HEAD 3d72157a4e (merge of main a87d8be299 into 73f031bdb7; rerun in full after the merge). Claim-time gate list, 19 commands, every exit captured before any pipe: all 0. check-adr-symbol-anchors: '2223 anchors across 141 records resolve' (positive control: 141 = 140 .md at base 7b926f7600 + this record; the shared extractor reads 56 anchors from the new record, 52 symbol, none a line number). check-adr-links: '714 relative link destination(s) under docs/adr/ resolve'. check:adr-anchors: OK (every ADR citation resolves). check:pm-prior-rulings: self-test 155 cases pass; the tool's --card 22146 read: 16 ADR hits, thread 1 ruling (6054113537). check:nul-bytes: OK; separate control-byte grep on the new file: exit 1 (none). check:doc-formula-expressions: first run exit 3 = PREREQUISITE NOT MET (formula/lint not built), not a measurement; built the closure under os-verify-lock (VERDICT command-exit 0, twice: before and after the merge), rerun exit 0. Re-derivation: dispatch-gates --commands --repo objectstack-ai/objectstack at 3d72157a4e = the same 19 families; --ran reconciliation with recorded exit codes: '19 run, 0 NOT-MEASURED (a DERIVED zero)'. No ablation (no code). NOT MEASURED: the G2 booted sweep (ruled an acceptance precondition; no driver or probe written); the platform doc links (proxy answers 403 to CONNECT for those hosts; the URLs are round 1's); the cloud repo's anonymous surfaces.", "mcp_calls": "0", "api_writes": "3 REST writes from this container, all through scripts/pm tools via the fleet-write relay (each a POST /repos/objectstack-ai/objectstack/dispatches, HTTP 204, executed as objectstack-fleet[bot]): (1) pr_create via with-fleet.sh --via dispatch -> POST /repos/objectstack-ai/objectstack/pulls (draft forced; #22239; body read back byte-identical, 10412 bytes); (2) label-write.mjs --issue 22239 --add skip-changeset --assign os-litant -> POST /issues/22239/labels + POST /issues/22239/assignees (read-back matches; documentation and size/l were added by labelers, preserved); (3) this os-dev-report via post-stamped.mjs -> POST /issues/22146/comments. Plus git push x3 (not REST): the empty-branch claim marker, the ADR commit 73f031bdb7, the merge 3d72157a4e. No MCP write tool. The card's assignee was not written.", "gates": "At 3d72157a4e, each :: exit: node scripts/check-adr-links.mjs :: 0 · --self-test :: 0 · node scripts/check-adr-symbol-anchors.mjs :: 0 · --self-test :: 0 · node scripts/check-ci-filter-parity.mjs :: 0 · node scripts/check-closing-keyword-parity.mjs :: 0 · --self-test :: 0 · node scripts/check-comment-mask-corpus.mjs :: 0 · pnpm --filter @objectstack/lint run check:doc-formula-expressions :: 0 (after closure build; first run 3 = prerequisite) · pnpm check:adr-anchors :: 0 · pnpm check:cross-package-test-inputs :: 0 · pnpm check:doc-authoring :: 0 · pnpm check:driver-memory-census :: 0 · pnpm check:gitlink-declared :: 0 · pnpm check:nul-bytes :: 0 · pnpm check:pm-governed-merges :: 0 · pnpm check:pm-prior-rulings :: 0 · pnpm check:refd-timer-probe :: 0 · pnpm check:watch-hint-literal :: 0. Re-derived list identical (19); --ran: 19 run, 0 NOT-MEASURED. CI: in_progress, not awaited.", "line_budget": "648 changed lines vs merge base a87d8be29 (+648 / -0, 1 file), as dispatch-gates prints it; under the 5000 human-merge threshold.", "files_changed": [ "docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md" ], "deviations": [ "Number 0138, not 0136. main tops out at 0137 with 0136 absent; all 20 open PRs' file lists checked (paged to the end for #22142 at 104 files and #21988 at 229): only #22198 touches docs/adr (0048). 0136 was handed to a decision once: unmerged PR #18480 added 0136-declared-journeys-as-priority-anchor.md and #18985 renumbered its own record to 0137 over it; check-adr-anchors.mjs computes next-free as max+1 = 0138.", "No back-pointers written. House practice adds a status-line continuation ('· **Amended** (date, ADR-NNNN Dk) — ...', ADR-0042/0046/0131) when an amendment is in force, and no record carries one pointing at a Proposed record; the exact lines for ADR-0090 (D9) and ADR-0056 (D2) are written into the record's Acceptance criteria to land with the accepting change. ADR-0106 D7, ADR-0121 D6, ADR-0096 and ADR-0135 get none (unchanged; ADR-0135 is a cloud ADR-0024 mirror that adds no clause).", "Merged main a87d8be299 mid-round after the derivation flagged a stale tree (5 commits); anchors and all 19 gates re-run on the merged head.", "Attribution: commit trailers are the AGENTS.md model-free pair and the PR body ends with the AGENTS.md session-URL footer, not the harness reminder's model-named trailer / footer (os-dev.md: the harness reminder yields).", "Label: skip-changeset applied under pr-automation.yml's own criterion (its failure text: 'if it releases nothing ..., apply the skip-changeset label'); this PR adds and changes no .changeset file and docs/adr ships in no files[].", "Two readings beyond the ruled letters are named in the record's 'What the ruling did not settle' (and asked below): D1's empty state, and the D8 fate 'remove' for sys_record_share's guest recipient (derived from ADR-0090 D11 and the registered sharing-rule-recipient-reconcile entry; reopens on E4 if its census finds a consumer). The form doors' guest_portal set name, not on the card's Q8 list, was added to D8 with fate keep.", "Cleanup: worktree node_modules removed and git worktree remove (no --force) run right after this report is posted." ], "open_questions": [ { "question": "D2b (the record's open decision; ruling A on #22147, 6053767508, names two answers and decides neither): an anonymous authRequired:false flow door whose target flow declares runAs:'system' — which answer?", "options": [ "R — publish refusal with a prescription, both directions (the endpoint reads its flow's runAs; a flow set to system reads the anonymous endpoints targeting it). Prescription: an authenticated endpoint, the signed inbound-hook channel (ADR-0041), a public form, and runAs:'automation' once ADR-0073 M2 lands. Cost: one cross-item check in the publish path; a narrowing, so E2 carries one ADR-0087 semantic entry; closes any unmeasured external use.", "M — allowed only once ADR-0073 M2's automation posture lands: until M2, today's combination stands (ruling A on #22147) with a publish advisory; at M2, system behind an anonymous door becomes a refusal prescribing automation. Cost: two changes, the second waiting on an unscheduled milestone; the interim publish stays green." ], "recommendation": "R, on the four axes. Real need: at a87d8be299/7b926f7600 examples/ hold zero authRequired:false declarations and the repo's partner-webhook example takes the signed hook channel, which R does not touch. Long-term: R is final for system and M2 only adds automation to its prescription. AI-proofing: the runAs describe text prescribes system for user-less triggers, so R stops an AI at publish while M's advisory leaves publish green. Startup focus: R is one rule in an existing publish path; M is a staged transition with no named external-user evidence. Neither option reaches record-change flows fired by a guest-written row; that is named in the record." }, { "question": "D1's empty state (this record's reading, not a ruled letter): with no guest default owner declared, what does a guest insert into an owner-bearing object do?", "options": [ "A — the record's reading: an endpoint insert (a door that takes no guest insert today) is refused naming the missing declaration; the public form doors keep today's owner-unset intake and the form's authoring read states the absence (the Q4 principle 'nothing that serves today starts refusing', applied to Q1)", "B — refuse every guest insert without a declared owner, form doors included", "C — owner-unset everywhere, advisory only" ], "recommendation": "A: the form doors serve today (app-crm and app-showcase open public forms), so B breaks serving intake; C leaves the new endpoint doors silent, which axis 3 rejects; A is loud on every door that does not serve yet and breaks nothing that does." } ], "out_of_scope_findings": [ "carrier: none (card E2, once cut, edits that domain) · noted, not filed — the doc comment above RestServer.registerFormEndpoints (packages/rest/src/rest-server.ts) still says security is delegated to a guest_portal set carried on the context and that the middleware falls open with none registered; the publicFormGrant branch in SecurityPlugin is what admits today. Comment drift, no reach measured; recorded in the PR's Acceptance notes." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
✅ ACCEPT on the draft: PR #22239 at
3d72157a4e(round 3, ADR-0138, Proposed). Tier H: it waits on the maintainerdomain:specseat 1 (#6017) ·os-litant· sessionsession_01LAi5BVvQNiYzepSAcsoFLK· 2026-10-08T08:13Z · holder of claim6054856256; the review of record for the report on this card from round 3.Checklist (read on GitHub and in the PR's own diff):
main, assigneeos-litant, labelsskip-changeset(no.changesetrow;docs/adrships in no package). Line 1 isPart of #22146, line 2 isClause-②: no.docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md, +648 / −0.packages/**,content/docs/**orskills/**change, as the ruling's State section requires.check-adr-anchorscomputes next-free as max + 1. The dev checked every open PR's file list.6054113537, read section by section:6053767508names two answers and decides neither.removeforsys_record_share'sguestrecipient, derived from ADR-0090 D11 and the registeredsharing-rule-recipient-reconcileentry;guest_portalset name, added to D8 askeep.3d72157a4e, every exit was 0, and--rangives 0 NOT-MEASURED.check-adr-symbol-anchors: 2223 anchors across 141 records resolve.check-adr-links: 714 links resolve.check:pm-prior-rulings: OK.RestServer.registerFormEndpointsstill describes aguest_portal-only delegation. That is comment drift, recorded in the PR's Acceptance notes.The landing shape (Tier H, the final four-piece set):
needs-user-decisionon the PR, and the final 维护者速读 posted on the PR.The PR stays draft. The maintainer approves it or merges it by hand. ⛔ No seat flips it ready unapproved. The card stays
pm:dispatchedwith this seat until the PR lands, and E1–E4 are cut only after acceptance.objectstack-fleet commentedon Oct 8, 2026
Ruling: batch #290 item 1 · supplement · letters D1 A′ and D2b R · maintainer 「D1 A′ D2b R」 2026-10-08T09:11Z
Director seat, summon #35,
session_01VYToj6PQehTEKNrjGM9akg(GitHubos-zhuang; written asobjectstack-fleet[bot]via the relay). Round 3's draft, PR #22239 (ADR-0138, Proposed, head3d72157a4e, seat ACCEPT 6055683775, 维护者速读 6055701071), asked the maintainer for two letters: D2b (R or M) and D1's empty-state reading. The maintainer asked for the record in business terms (given in chat, decision by decision), then challenged D1's organization-level default owner, verbatim: 「访客写进来的记录归本组织声明的一个内部人,这个没必要吧,应该是每个业务场景在元数据级别自己处理吧」. The seat agreed — the record's own Consequences paragraph already names the clash between a platform stamp and an owner-assigning object hook — and put the revised shape as A′; the maintainer answered 「D1 A′ D2b R」. Thread-read: 6055683775. Freshness: no comment since the seat's ACCEPT.D1 A′ — the invariant stays; the platform declares no default owner
Four axes, in business terms: ① ownership policy is business logic that differs per scenario (territory, queue, round-robin, product line); an organization-level constant is wrong in most deployments and pre-empts the scenario's own hook. ② The mechanism that does the work on Salesforce and ServiceNow is the assignment rule; an organization default is only their fallback. ③ A′ leaves no silent stamp that hides a missing assignment. ④ No new declaration, no empty-state rule, no site-binding element.
D2b R — a publish refusal with a prescription
Publish refuses an
authRequired: falseendpoint oftype: 'flow'whose target flow declaresrunAs: 'system', in both directions: the endpoint's publish reads its target'srunAs, and a flow's publish that setsrunAs: 'system'reads the anonymous endpoints that target it. The prescription names what serves each scenario instead: an authenticated endpoint for a partner that can hold a credential; the signed inbound-hook channel (ADR-0041) for a partner webhook; a public form for anonymous intake; andrunAs: 'automation'once ADR-0073 M2 lands. Which doors may trigger an elevated flow directly: none. How loudly publish says so: a refusal. Card E2 implements it, with the registered ADR-0087 semantic entry a narrowing of the accept set owes. Until E2 lands, ruling A on #22147 (6053767508) governs. ⛔ Not taken: M — an advisory interim with no date, which an automated authoring loop reads as done. The indirect path (record-change flows fired by a guest-written row) stays as the record states it: outside both options, recorded, not decided.Prior rulings read: 6054113537 (A on eight questions, G2); #22147 ruling A 6053767508; ADR-0073 D2/D3/M2; ADR-0041; ADR-0087; ADR-0049 → 6 hits; thread: 9 comments. 自检: 只看①选 A′ 与 R;②③④ 是否翻转:否。
State
pm:dispatchedwith thedomain:specseat (claim 6054856256). PR docs(adr): ADR-0138 the guest model (Proposed) — doors, grants channel, organization, ownership, and every guest key's fate #22239 gets one revision round before approval: D1 rewritten to A′; D5's shape without the default owner; D2b closed as R with the enforcement text above and removed from "What the ruling did not settle"; the E2 and E3 rows and the Consequences paragraph updated; then the seat re-posts the 维护者速读 and the maintainer approves (Tier H). ⛔ The maintainer clicks nothing until the revision is up.Ruled:line is added to the body in this act. A pointer is posted on PR docs(adr): ADR-0138 the guest model (Proposed) — doors, grants channel, organization, ownership, and every guest key's fate #22239.Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{ "issue": 22146, "status": "done", "branch": "claude/issue-22146-guest-model-adr", "pr": "https://github.com/objectstack-ai/objectstack/pull/22239", "session": "session_01LAi5BVvQNiYzepSAcsoFLK", "premise_still_valid": true, "summary": "Revision round on PR #22239 applied at 2d69b2b714 (draft kept; head read back), per ruling supplement 6056614963 (maintainer 「D1 A′ D2b R」) and its State section. D1 is A′: points 1, 2 and 4 kept, the organization-level default owner (draft points 3, 5, 6) replaced by 'ownership of a guest-written row is the business scenario's own metadata; the platform stamps nothing; a forged owner is refused; empty state = owner unset, as the form doors today, authoring advisory, no publish refusal'. D5's shape lost its default-owner row. D2b is closed as R with the enforcement text (publish refusal in both directions, prescription, no door triggers an elevated flow directly, indirect path recorded and undecided); the four-axis table stays as reasoning; M and the first-drafted default owner are under Alternatives considered. TL;DR and enforcement-map rows for D1/D2b, E2 (R with a registered ADR-0087 semantic entry) and E3 (the stamp's guest branch, no new key, no disposition) updated; the owner-assigning-hooks Consequence withdrawn; 'What the ruling did not settle' lost D2b and D1's empty state; acceptance criterion 3 stated as met citing 6056614963; the supplement added to Decided by. The PR body was NOT edited by this run: see deviations; the replacement body is in pr_body_replacement for the seat to write.", "tests": "All at HEAD 2d69b2b714 (revision on a merge of main 73a0a6bf1d). Claim-time gate list, 19 commands, every exit captured before any pipe: all 0. check-adr-symbol-anchors: '2225 anchors across 141 records resolve' (141 = 140 at base + this record). check-adr-links: '714 relative link destination(s) under docs/adr/ resolve' (the renamed D2b heading and the new 'D2b on the four axes' heading are intra-file fragments, slugged by hand to GitHub's rule). check:doc-formula-expressions: 0, after rebuilding the formula+lint closure under os-verify-lock post-merge (VERDICT command-exit 0; main brought a packages/lint change). check:nul-bytes: OK; separate control-byte grep on the record: exit 1 (none). Re-derivation at 2d69b2b714 (no stale-tree warning): the same 19 families; --ran with recorded exit codes: '19 run, 0 NOT-MEASURED (a DERIVED zero)'. Residue sweep on the record for 'default owner', 'Option M', 'open', 'choose', the old fragments: the only hits are the verbatim first-ruling quote, the supplement notes, the Alternatives entries and the M column of the reasoning table. NOT MEASURED (unchanged): the G2 booted sweep (acceptance precondition), the platform doc links (proxy 403), the cloud repo's anonymous surfaces.", "mcp_calls": "0", "api_writes": "1 REST write from this container: this os-dev-report via scripts/pm/post-stamped.mjs through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches -> POST /issues/22146/comments, as objectstack-fleet[bot]). Plus git push x1 (not REST): 3d72157a4e..2d69b2b714 (the merge ddaf31f98f and the revision). The budgeted PR body edit was NOT spent (deviations). No MCP write tool; no label, assignee or draft-state write.", "gates": "At 2d69b2b714, each :: exit: node scripts/check-adr-links.mjs :: 0 · --self-test :: 0 · node scripts/check-adr-symbol-anchors.mjs :: 0 · --self-test :: 0 · node scripts/check-ci-filter-parity.mjs :: 0 · node scripts/check-closing-keyword-parity.mjs :: 0 · --self-test :: 0 · node scripts/check-comment-mask-corpus.mjs :: 0 · pnpm --filter @objectstack/lint run check:doc-formula-expressions :: 0 · pnpm check:adr-anchors :: 0 · pnpm check:cross-package-test-inputs :: 0 · pnpm check:doc-authoring :: 0 · pnpm check:driver-memory-census :: 0 · pnpm check:gitlink-declared :: 0 · pnpm check:nul-bytes :: 0 · pnpm check:pm-governed-merges :: 0 · pnpm check:pm-prior-rulings :: 0 · pnpm check:refd-timer-probe :: 0 · pnpm check:watch-hint-literal :: 0. Re-derived list identical (19); --ran: 19 run, 0 NOT-MEASURED. CI: in_progress, not awaited.", "line_budget": "640 changed lines vs merge base 73a0a6bf1 (+640 / -0, 1 file), as dispatch-gates prints it; the revision commit itself is +103 / -111 on the record.", "files_changed": [ "docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md" ], "deviations": [ "PR body not edited. The dispatch budgeted one PR body edit; os-dev.md's standard clause reads 'PR 正文 dev 只写一次,在开 PR 那一笔,⛔ 不 PATCH;事后要改的报告点名改法,席位代写', and the same file states it outranks dispatch words on conflict and that a conflict is named, not resolved silently. So the edit is the seat's: pr_body_replacement below is the full new body (lines 1-2 and the session-URL footer unchanged; 13219 bytes; no fence, no angle bracket, no closing keyword next to an issue number). It updates the D1/D2b/D5 lines, replaces 'What the record leaves open' with 'What the revision changed' and 'What stays open', moves verification to 2d69b2b714, and rewrites the 维护者速读 in Chinese with 席位意见 empty and one action (approve). The same text is on disk at /tmp/claude-0/-home-user/fc7befed-d8ca-5741-8b80-79a41d4d0410/scratchpad/issue-22146-adr/pr-body-r2.md.", "Consistency edits beyond the ten listed items, each forced by a ruled change and named in the replacement body: the Status line (D2b no longer the open item); the Consumers line (the spec change is D2b's refusal, not a D1 key); D4 point 3 (no owner stamp left that needs the organization); D2's class 5 row (inside, never system behind this door); follow-up F3 (M2 now only extends R's prescription); a one-paragraph supplement note after the verbatim first-ruling summary in the TL;DR; the References.", "D2b's enforcement point is written as 'the publish path, in both directions; the cross-item read sits where both items are readable at publish, beside the per-endpoint policyGate, which reads one endpoint only' — not claimed to live inside policyGate, which reads a single endpoint.", "Merged main 73a0a6bf1d (13 commits, including #22198's ADR-0048 note and a packages/lint change) before editing; no 0138 appeared on main.", "PR #22239 now carries a needs-user-decision label set by another actor; not touched.", "Cleanup: worktree node_modules removed and git worktree remove (no --force) run right after this report is posted; the fetched ref refs/os-dev/issue-22146-main deleted." ], "open_questions": [], "out_of_scope_findings": [ "carrier: none (card E2, once cut, edits that domain) · noted, not filed — unchanged from round 3: the doc comment above RestServer.registerFormEndpoints (packages/rest/src/rest-server.ts) still describes the pre-publicFormGrant form-door semantics. Comment drift, no reach measured; in the PR's Acceptance notes." ], "pr_body_replacement": "Part of #22146\nClause-②: no\n\n## What this PR is\n\nRound 3 of #22146: one new decision record, `docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md`, **Status: Proposed**. It transcribes the maintainer's ruling [`6054113537`](https://github.com/objectstack-ai/objectstack/issues/22146#issuecomment-6054113537) (batch #290 item 1, 「22146 同意」: A on all eight questions and G2 on the gap, with the Q4 and Q2 clarifications) into nine decisions. Each one states its contract and its enforcement point. The revision round applies the ruling supplement [`6056614963`](https://github.com/objectstack-ai/objectstack/issues/22146#issuecomment-6056614963) (maintainer 「D1 A′ D2b R」): D1 is revised to A′, and D2b is closed as R.\n\n- **No code changes.** Nothing changes in `packages/**`, `content/docs/**` or `skills/**`. The ruling places every code change after acceptance, as execution cards E1 to E4 (the record's *Execution plan*).\n- **Tier H.** The diff touches `docs/adr/**`, so this PR stays draft and lands only by the maintainer's hand.\n- **The card stays open** for the execution cards, so line 1 is `Part of`.\n\n## What the record says, one line per decision\n\n- **D1, identity and ownership (A′).** The guest is a principal and never owns a record; a forged owner is refused; the audit names the guest as the actor. Who owns a guest-written row is the business scenario's own metadata: the door's declaration, the object's hooks, record-change flows and assignment rules. The platform stamps nothing and declares no default owner. Empty state: the owner stays unset, as the form doors do today, with the existing authoring advisory and no publish refusal. The enforcer is the guest branch of the owner-anchor stamp in `SecurityPlugin` (card E3).\n- **D2, the closed list of doors.** Exactly five door classes serve an unauthenticated request:\n - the public form doors;\n - share links;\n - the public book and doc reads;\n - `authRequired: false` endpoints of type `object_operation`;\n - `authRequired: false` endpoints of type `flow`.\n\n Everything else answers 401, decided once per domain by `shouldDenyAnonymous`. The control-plane allowlist, the signed inbound-hook channel and MCP are credentialed or infrastructure, so they are outside the guest model.\n- **D2b, anonymous door × elevated flow (R).** Publish refuses an `authRequired: false` flow endpoint whose target declares `runAs: 'system'`, in both directions, with a prescription: an authenticated endpoint, the signed inbound-hook channel, a public form, and `runAs: 'automation'` once ADR-0073 M2 lands. No door triggers an elevated flow directly. Card E2 implements it with a registered ADR-0087 semantic entry. Record-change flows fired by a guest-written row stay recorded and undecided.\n- **D3, the grants channel (ADR-0090 D9, enforced).** The `guest` anchor's bindings resolve for the guest, read through the one binding reader every position uses. An empty set denies all. There is no second channel: not the baseline, not `everyone`, and not the position-name fold. The binding tier is unchanged. The row scope runs on one pipeline, and E1 pins it per sharing model.\n- **D4, organization.** The guest's organization is resolved only when the deployment has a unique organization, using the same predicate as ADR-0131 D9. On a multi-organization deployment the request is refused until D5 exists. The question is asked only where the organization is needed.\n - **Clarification (1), carried into the record:** the form doors' declared default-organization binding stays as it is. Nothing that serves today starts refusing.\n- **D5, site binding.** The record gives the shape only: match, organization, guest grants and allowed doors. ⛔ It declares no metadata type and reserves no key. The binding is built when a named deployment needs it.\n- **D6 and D7.** ADR-0106 D7, explain's `EXTERNAL` floor and ADR-0121 D6 are unchanged. The webhook signature vocabulary goes to a follow-up card, F1. That card is named in the record and not filed.\n- **D8, guest keys.** Every declared guest key gets a fate and an ADR-0087 disposition.\n - `sys_record_share`'s `guest` recipient is to be **removed** on its own card, E4. The basis is ADR-0090 D11 and the already-registered `sharing-rule-recipient-reconcile` entry.\n - The form doors' `guest_portal` set name was not on the card's list. It is recorded too, with the fate keep.\n- **D9.** The record now holds the maintainer's ruling of 2026-08-08 (Option A, `f586f1a89`), which until now lived only in the module doc of `assemble-execution-context.ts`.\n\n## What the revision changed (supplement [`6056614963`](https://github.com/objectstack-ai/objectstack/issues/22146#issuecomment-6056614963))\n\n- **D1 → A′.** Points 3, 5 and 6 of the draft (the organization-level default owner, its cardinality, the empty-state refusal) are replaced: ownership is the scenario's own metadata, and the empty state is the owner left unset. Points 1, 2 and 4 are kept.\n- **D5.** The default-owner element is removed from the shape.\n- **D2b → R**, with its enforcement text. The four-axis table stays as the reasoning. M and the first-drafted default owner move to *Alternatives considered*.\n- **Execution plan.** E2 carries R and its registered ADR-0087 semantic entry. E3 shrinks to the stamp's guest branch (never the guest, never the system principal, a forged owner refused, the owner unset unless the scenario sets it): no new key and no disposition.\n- **Elsewhere.** The Consequences paragraph on owner-assigning hooks is withdrawn. *What the ruling did not settle* loses D2b and D1's empty state. Acceptance criterion 3 (D2b chosen) is met. The supplement is added to *Decided by*.\n- **Consistency edits the ruled changes forced:** the Status line, the Consumers line (the spec change is now D2b's refusal, not a D1 key), D4 point 3 (no owner stamp left to need the organization), D2's class 5 row, follow-up F3 (M2 now only extends R's prescription), and the References.\n\n## What stays open\n\n- **The indirect path:** record-change flows fired by a guest-written row are recorded and not decided.\n- **The spelling of D5's binding** belongs to the card that builds it.\n- **The guest's row scope** is stated as a contract; E1 pins its measured outcome.\n\n## How the number was chosen: 0138\n\n- `origin/main` at `73a0a6bf1d`, after this round's merge, tops out at 0137, and 0136 is absent. Still no open PR adds 0136 or 0138; #22198, the one ADR PR at the first count, has landed on ADR-0048.\n- **I checked every open PR's file list.** That is all 20 open PRs, paged to the end for #22142 (104 files) and #21988 (229 files). Only #22198 touches `docs/adr/`, and it touches `0048-cross-package-metadata-collision.md`. No open PR adds 0136 or 0138.\n- **0136 is not reused.** It was handed to a decision once: unmerged PR #18480 added `0136-declared-journeys-as-priority-anchor.md`, and #18985 renumbered its own record from 0136 to 0137 because of it. `scripts/check-adr-anchors.mjs` computes the next free number as the highest number plus one, which gives 0138.\n\n## Back-pointers: none in this PR, by house practice\n\n- **Where the lines go.** The house form for an amended record is a status-line continuation. ADR-0042, ADR-0046 and ADR-0131 carry lines of the form \"· **Amended** (date, ADR-NNNN Dk) — …\". These lines are written when the amendment is in force. No record in the registry carries such a line pointing at a Proposed record. So the exact lines for **ADR-0090** (D9) and **ADR-0056** (D2) are written into the record's *Acceptance criteria*, to land with the accepting change.\n- **Which ADRs get no line.** ADR-0106 D7, ADR-0121 D6 and ADR-0096 D5/E1 are left unchanged by this record, so they get none. **ADR-0135** gets none either: it mirrors cloud ADR-0024 and adds no clause of its own, and this record amends none of its decisions.\n\n## Verification at `2d69b2b714` (the revision, on a merge of main `73a0a6bf1d`)\n\nI ran every command in the claim-time gate list at the revision head. Each exit code was captured before any pipe.\n\n| Command | Exit |\n|:--|:--|\n| `node scripts/check-adr-links.mjs` (and `--self-test`) | 0, 0 |\n| `node scripts/check-adr-symbol-anchors.mjs` (and `--self-test`) | 0, 0 |\n| `node scripts/check-ci-filter-parity.mjs` | 0 |\n| `node scripts/check-closing-keyword-parity.mjs` (and `--self-test`) | 0, 0 |\n| `node scripts/check-comment-mask-corpus.mjs` | 0 |\n| `pnpm --filter @objectstack/lint run check:doc-formula-expressions` | 0 (see note) |\n| `pnpm check:adr-anchors` | 0 |\n| `pnpm check:cross-package-test-inputs` | 0 |\n| `pnpm check:doc-authoring` | 0 |\n| `pnpm check:driver-memory-census` | 0 |\n| `pnpm check:gitlink-declared` | 0 |\n| `pnpm check:nul-bytes` | 0 |\n| `pnpm check:pm-governed-merges` | 0 |\n| `pnpm check:pm-prior-rulings` | 0 |\n| `pnpm check:refd-timer-probe` | 0 |\n| `pnpm check:watch-hint-literal` | 0 |\n\n- **`check-adr-symbol-anchors`.** It reports \"2225 anchors across 141 records resolve\". Positive control: the record count is 141, which is the 140 at base plus this record. No anchor carries a line number.\n- **`check:pm-prior-rulings`.** The self-test passes 155 cases. The tool's `--card 22146` read returns 16 ADR decision hits and 1 ruling on the thread (`6054113537`).\n- **`check:doc-formula-expressions`.** In the first round its first run exited 3 (PREREQUISITE NOT MET: the closure was not built), which measured nothing. I rebuilt the `@objectstack/formula` and `@objectstack/lint` closure under the verify lock after this round's merge (VERDICT command-exit 0), and the gate exited 0.\n- **Re-derivation.** `dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `2d69b2b714` gives the same 19 families. The `--ran` reconciliation shows 19 run and 0 NOT-MEASURED, a zero derived from the recorded exit codes. The change set is one file, +640/−0, against merge base `73a0a6bf1`.\n\n## Not measured, and named\n\n- **The G2 sweep (the booted per-door-class anonymous sweep).** I did not run it, by the ruling: it is an acceptance precondition, to be run in an environment that permits the probe. This round wrote no driver and no probe.\n- **The platform documentation links.** I could not re-fetch them, because the container's proxy answers 403 to CONNECT for those hosts. The record carries the URLs that the measurement round recorded.\n- **The cloud repository's anonymous surfaces.** This round did not read them.\n\n## Changeset\n\nI read the Check Changeset job in `pr-automation.yml`. It has two exemptions: the `skip-changeset` label (read live, twice) and the Changesets release PR. Its failure text prescribes: \"if it releases nothing …, apply the 'skip-changeset' label\". This PR adds no `.changeset/*.md` and changes none, and `docs/adr/**` ships in no package's `files[]`. So the label is `skip-changeset`.\n\n## Acceptance notes\n\n- **A doc comment that describes the old form-door semantics.** The comment above `RestServer.registerFormEndpoints` in `packages/rest/src/rest-server.ts` still says that security is delegated to a `guest_portal` set carried on the context, and that the middleware falls open when none is registered. What admits a form submission today is the `publicFormGrant` branch in `SecurityPlugin`.\n - This is comment drift only, and no reach was measured. It is noted, not filed.\n - Carrier: none. Card E2, once cut, edits that domain.\n\n## 维护者速读(草稿)\n\n- **改了什么:** 按你「D1 A′ D2b R」的补充裁决修订 ADR-0138 草稿,其余内容不动。\n - D1:访客永不拥有记录、伪造的 owner 一律拒绝、操作留痕记在访客名下,这三条保留。\"本组织声明一个默认 owner\"整条删掉:访客写进来的记录归谁,由各业务场景在元数据里自己定(入口声明、对象钩子、记录触发流程、分配规则),平台不打任何默认值。没人设置时 owner 就空着,跟今天公开表单一样;表单的作者视图照常提示,发布不拒绝。\n - D2b:匿名入口不能触发以系统身份运行的流程。发布时直接拒绝,端点和流程两头都检查,并给出替代办法(带凭据的端点、签名 webhook 通道、公开表单,以及 M2 落地后的 automation)。\n - 站点绑定的形状里去掉\"默认 owner\";执行卡 E3 缩成只校验访客分支,不新增任何键。\n- **为什么改:** 你指出归属是业务场景的事,平台级默认值在多数部署里是错的,还会抢在对象自己的分配逻辑前面。D2b 选 R 之后,AI 照着 `runAs` 的说明写出\"匿名入口 + 系统身份流程\"时,发布就会被拦下。\n- **风险与代价(含回滚):**\n - 本 PR 仍只改一个文档文件,合并后运行时行为不变;回滚就是删掉这个文件。\n - D2b 的拒绝会让\"匿名端点指向系统身份流程\"这种写法从此发布不了。仓内没有这样的声明;仓外的部署本轮没有测。\n - 接受前提还剩一项:在允许探测的环境里做一次启动后的逐类匿名入口实测(G2)。\n- **席位意见:**\n- **你要做的:** 修订版就绪后,批准这份 ADR(Tier H,点 Approve 或亲手合并)。\n\n---\n_Generated by [Claude Code](https://claude.ai/code/session_01LAi5BVvQNiYzepSAcsoFLK)_\n" }Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
✅ ACCEPT on the revision: PR #22239 at
2d69b2b714(ADR-0138, Proposed), under the supplement6056614963domain:specseat 1 (#6017) ·os-litant· sessionsession_01LAi5BVvQNiYzepSAcsoFLK· 2026-10-08T09:29Z · holder of claim6054856256; the review of record for the revision report6056856908. It amends the ACCEPT6055683775.Read in the revision's diff (the record alone: +103 / −111; the PR's net diff against
mainis still the one file, +640 / −0):SecurityPlugin's owner-anchor stamp.policyGateand not inside it, which is accurate.2d69b2b714: all 19 exits are 0, and--ranshows 0 NOT-MEASURED.check-adr-symbol-anchors: 2225 anchors across 141 records.os-dev.md: the dev writes a PR body once; later edits are the seat's). The seat wrote it through the relay, read back identical (13219 bytes).The PR stays draft, with
needs-user-decision, and reviews remain requested fromos-zhuangandhotlong. The final 维护者速读 is re-posted on the PR.objectstack-fleet commentedon Oct 8, 2026
Landed: round 3, PR #22239 →
8cbe255ef6, ADR-0138 (Proposed). The card stays open for acceptance and the execution cardsdomain:specseat 1 (#6017) ·os-litant· sessionsession_01LAi5BVvQNiYzepSAcsoFLK· 2026-10-08T10:48Z · holder of claim6054856256, which this act releases.8cbe255ef6, which has one parent (de1610fc4e) and is an ancestor oforigin/main.docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.mdis blob-equal to the approved head2d69b2b714.os-zhuangon2d69b2b714.6055683775(the draft) and6056900852(the revision, under the supplement6056614963, 「D1 A′ D2b R」).6056913582.main: ADR-0138, Proposed, which records:runAs: 'system';6054113537.What acceptance still needs (the record's Acceptance criteria). Criterion 1, approval, and criterion 3, D2b, are met. Two remain:
guestanchor; ADR-0090 D9 is declared and seeded but not enforced #21158's did. This container's safety check stopped the round-1 driver, and the seat did not work around it; per the ruling it is not re-routed to another session either. Where it runs is the maintainer's to say.After acceptance, the seat cuts the execution cards named in the record's Execution plan:
sys_record_share'sguestrecipient.⛔ None of them is filed before acceptance.
Maintainer-action: run, or have run in an environment that permits the probe, the G2 booted per-door-class anonymous sweep ADR-0138 criterion 2 names — done when a comment on #22146 records a class-level result for each of D2's five door classes and the everything-else class
Release:
session_01LAi5BVvQNiYzepSAcsoFLK· why: round 3 is delivered and landed; what remains before the next seat act (the accepting change, then E1–E4) is an action a seat in this container cannot perform · to:pm:awaiting-maintainer, unassignedobjectstack-fleet commentedon Oct 9, 2026
Claim: PM loop round 1 (ADR-0138 acceptance criterion 2: the G2 booted per-door-class anonymous sweep) · 2026-10-09T04:17Z
Session:
session_01VZqqwTj2wsihZEbfT6yyYNAccount:
os-tesla(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-22146-g2-sweepWorktree:
objectstack-issue-22146Domain:
domain:specSeat:
domain:spec#1File surface: none. A measurement only: no tracked file is edited, no PR is opened, and the branch carries no commit (it is pushed empty as the claim's marker). The probe lives outside the repository and is not published.
Container & model:
M,mode:subagent,model: default tier(dispatch-gates --tier: no path-derived mandate for a surface of no paths; judged from the card: a measurement with no contract edit).Clause-②: no (a measurement; no accept set or public surface changes)
Responsibility:
n/a — not a defect cardThread-read: 6058198880
Serial constraints cleared: none. The run touches no file; it builds and boots one local reference deployment on a random high port through
os-verify-lock.sh, beside the #22130 re-sync round, which writes no file this run reads.Why the claim, with its source.
Maintainer-action:in6058198880reads: "run, or have run in an environment that permits the probe, the G2 booted per-door-class anonymous sweep ADR-0138 criterion 2 names". Where it runs was the maintainer's to say.6058198880)。 这个你可以处理吗?」 This session runs it.127.0.0.1, from this repository's own example app. No other host is probed.blockedwith the refusal as written, and nothing is worked around.pm:awaiting-maintainer→pm:dispatchedand assignsos-tesla. On the result, the card returns to the seat for the accepting change (criterion 4's back-pointers and the Status line), which is Tier H.objectstack-fleet commentedon Oct 9, 2026
os-dev-report
{ "issue": 22146, "status": "done", "branch": "claude/issue-22146-g2-sweep", "pr": null, "session": "session_01VZqqwTj2wsihZEbfT6yyYN", "premise_still_valid": true, "summary": "ADR-0138 acceptance criterion 2 (G2) was measured on a booted reference deployment, the showcase app, which this session started from origin/main abd254508b on a random high port and stopped itself. Classes 1 to 5 match D2. The everything-else class does NOT match D2. That is a security finding, kept at class level here; its detail is in the private readings file named under tests. Each class was read in both halves: the admitted request, plus a negative control (a declaration absent or not anonymous; a bad, revoked or wrong-audience token; a non-public book; an authRequired: true sibling door), with signed-in controls proving that each refused surface exists. PM mechanism assumption 2 is falsified in part: the showcase declares no authRequired: false endpoint, and neither does any other example app. So classes 4 and 5 were measured on uncommitted fixture declarations in the worktree, alongside a non-anonymous form and an org-audience book used as negative controls. All four files were restored to HEAD before the worktree was removed. The fixture diff is kept in the private scratch directory. Mechanism assumption 1 holds: E1 to E4 are unbuilt. The guest envelope reaches the declared object_operation door and is denied with no grant bound, which is D3's empty state as a side effect. D2b's publish refusal is not built: the boot accepted an anonymous flow door to a runAs: system flow. That door was never invoked. This is the record's own stated interim until E2. Nothing was committed and no PR was opened.", "g2_class_results": [ "1 · the public form doors (resolve and submit) · matches D2: yes · 2xx (404 for a non-anonymous or undeclared form) · the form's whitelisted fields and the one inserted record read back, with forged server-managed keys stripped", "2 · share links · matches D2: yes · 2xx (4xx for a wrong-audience, revoked or bogus token) · the one record, with its redacted fields absent", "3 · the public book and doc metadata reads · matches D2: yes · 2xx (401 for a non-public book or doc, a write, or a non-read route) · the public book, its tree and its docs only", "4 · authRequired: false endpoints of type object_operation · matches D2: yes · 403 at the declared door (401 for the authRequired: true sibling) · nothing: the guest envelope holds no grant, and no write landed", "5 · authRequired: false endpoints of type flow · matches D2: yes for admission and narrowness; D2b's publish refusal is not yet built (card E2, the record's own interim) · 2xx (400 when a runAs: user data step meets the guest; 401 for the authRequired: true sibling) · the flow's output only, no record", "everything else · matches D2: no" ], "tests": "Measurement only. No tracked change, so no gate family is owed: dispatch-gates --commands --repo objectstack-ai/objectstack at abd254508b exits 2, printing 'this branch changes nothing against origin/main (merge base abd254508) - nothing to derive'. Build: the showcase dependency closure, then the 8 remaining unbuilt workspace packages, both through os-verify-lock with slot issue-22146-g2, OS_SKIP_DTS=1 and workspace-concurrency 2. Both printed 'VERDICT command-exit 0' (held 68s and 54s, waited 0s). check-dev-prereqs then exited 0 ('67 package build artifacts present'). Boot: the showcase, by the backend-only recipe in AGENTS.md, with an ephemeral tempdir and the seeded dev admin. The boot banner reported 33 flows and 8 APIs, so the fixtures loaded. The probes were three scratch drivers, 126 requests in all, every one to 127.0.0.1 and to this one server only, one request per surface, and the rate limit was never exercised. Route-level readings (method, route, auth state, status, a one-line body summary for every request), the class verdicts with their reasoning, and the D3 and D9 behaviour seen are PRIVATE, in /tmp/claude-0/-home-user/3dc9b449-6dbf-5079-813e-e9191aba6a6a/scratchpad/issue-22146/g2-readings.md. Beside it are the raw JSON readings (g2-readings.json, -2, -3), the drivers (g2-driver*.mjs) and the uncommitted fixture diff (g2-fixture.patch). Teardown: the server's process group, the one this session started, was stopped. The port then had no listener and the fresh tempdir was gone. The fixture files were restored with git checkout HEAD, and git diff HEAD and git status --porcelain were both empty. The remote branch head is abd254508b, its base, with nothing committed.", "mcp_calls": "0", "api_writes": "1 REST write: this os-dev-report, sent with scripts/pm/post-stamped.mjs through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches, which executes POST /repos/objectstack-ai/objectstack/issues/22146/comments as objectstack-fleet[bot]). Also one git push, which is not REST: the empty claim-marker branch claude/issue-22146-g2-sweep at abd254508b. Reads: the card and all 14 of its comments, through REST GET. No PR, no label, no assignee write, and no MCP write tool.", "deviations": [ "The order asked for a server bound to 127.0.0.1. The CLI dev server has no host flag, and the Hono adapter listens on the wildcard IPv4 address, so the listener was not loopback-only. Every probe went to 127.0.0.1, and no other host was contacted.", "The setsid launch forked, so the first recorded PID was the parent that exited. The server's real process group was read from the process table at once and recorded, and the teardown signalled only that group, which this session had started." ], "open_questions": [], "out_of_scope_findings": [ "class: a · reach: exception: security · evidence: see private readings file · the everything-else class does not uniformly answer 401: more than one family outside D2's five classes and outside the control-plane allowlist answers an anonymous caller with 2xx · carrier: card E2 (the closed list, denial by domain), not yet cut; the seat files or folds it · dedupe words: ADR-0138 D2 everything else 401, anonymous floor gap, unclassified anonymous surface, G2 sweep finding", "carrier: card E2 · noted, not filed. The discovery documents served anonymously were read as the control-plane allowlist's discovery role and were not counted as a mismatch. The allowlist function does not name them literally, and the private file says which ones. E2's conformance-matrix row should name them.", "carrier: card E1 · noted, not filed. Behind a flow door, a guest that reaches a runAs: user data step is refused as user-less (it surfaces as 400). The guest's grants are never evaluated, as the director's pointer on this card (fact 2, the AutomationContext gap) already records. E1 will need the flow to see the guest." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
G2 measured: classes 1–5 match D2; the everything-else class does NOT. Acceptance criterion 2 is not met. Decision request below
domain:specseat 1 (#6017) ·os-tesla· sessionsession_01VZqqwTj2wsihZEbfT6yyYN· 2026-10-09T04:41Z · holder of claim6074162238, released by this act.Seat review of the measurement (
os-dev-report6074331185): ACCEPTorigin/mainabd254508bby the dev itself; 126 requests, all to127.0.0.1, one per surface; every class read in both halves (the admitted request and a negative control), with signed-in controls proving each refused surface exists. Classes 4 and 5 needed uncommitted fixture declarations (no example app declares anauthRequired: falseendpoint); they were restored toHEAD, andgit diff HEADwas empty.claude/issue-22146-g2-sweepsits at its baseabd254508bwith nothing committed.127.0.0.1.The result, per class
authRequired: falseobject_operationauthRequired: falseflowThe three families, function-level only: the
/i18ndispatcher domain (packages/runtime/src/domains/i18n.ts#handleI18nRequestmakes noshouldDenyAnonymouscall, while every other dispatcher domain does); the API-description endpoints (packages/rest/src/rest-server.ts#RestServer.registerOpenApiEndpoints); and storage download for files with no scope and no field owner (packages/services/service-storage/src/storage-routes.ts, whose own docblock calls download gating "a tracked follow-up"). Files declaredacl: 'public_read'are also served anonymously, which ADR-0104 rules as an opt-in, but D2's closed list does not name that door.决策请求(中文)
一句话问题: 实测证明五类开放入口都按 ADR-0138 的规定运作,但"其余一律拒绝匿名访问"这条不成立:未登录的访问者今天仍能读到界面文案与对象字段标签、整份 API 接口文档,并能按文件 id 下载未归属的上传文件。验收条件 2 因此未满足,ADR 不能按现状转为 Accepted。
Governing text: ADR-0138 Acceptance criteria 2("For each of D2's five classes, and for the everything-else class, a booted reference deployment's anonymous answer … matches D2's table");ADR-0138 D2("Everything else answers 401 … A new surface is therefore 401 until this list names it");ADR-0056 D2(Accepted,匿名默认拒绝);ADR-0104(Accepted,"the anonymous capability URL demoted to an opt-in
acl: 'public_read'")。本请求不改协议声明。前提(各带 re-check 命令,阳性对照在后):
git grep -c shouldDenyAnonymous origin/main -- packages/runtime/src/domains/i18n.ts→ 0 个文件;对照git grep -c handleI18nRequest origin/main -- packages/runtime/src/domains/i18n.ts→ 2。git grep -n registerOpenApiEndpoints origin/main -- packages/rest/src/rest-server.ts→ 2 处(注册与定义),定义处无匿名拒绝(读数在私有文件)。git grep -n "tracked follow-up" origin/main -- packages/services/service-storage/src/storage-routes.ts→ 1 处。public_read文件下载作为第六类写进 D2 的封闭列表(记录修订,Tier H)。三张缺陷卡按函数级描述公开立卡,修完重测"其余"一类,再走验收 PR业务含义: A = 门先关好,再宣布"其余一律拒绝"的规则生效;B = 先宣布规则,门以后再补;C = 同 A,但对外不点名哪扇门开着。
四轴论证:
me/localization读取承担(未实测,见置信缺口);头像、Logo 这类真要公开的文件已有 ADR-0104 的public_read显式声明。public_read写进列表;其余三族一律收紧,不为它们新开例外。推荐 A,回退 C。 终态:两年后平台对匿名访问只有一份封闭列表,每一类都有一个声明作为入口,其余一律 401,参照 Salesforce Experience Cloud 的 guest user profile 与 ServiceNow 的 public pages 列表。A 与 C 只差立卡是否公开:代码本身开源,三族都能从源码读出,函数级立卡带来的额外暴露有限;若你更倾向不公开,选 C。
自检:只看①选 A;②③④ 是否翻转:否(C 只改公开方式,不改方向)。
置信缺口:Console 登录页是否读取
/i18n的匿名接口未测(objectui 侧读者未数);API 文档查看器是否有匿名的真实使用者(如公开的 API 门户)未测;存储收紧对已有"未归属上传"的旧链接的影响未测。裁后执行: A ⇒ 本席当即立三张缺陷卡(交分诊定车道),起草 ADR-0138 D2 的
public_read修订(Tier H,另请你批),三卡落地后重跑"其余"一类实测,通过即开验收 PR(含验收条件 4 的两行回指)。B ⇒ 本席开验收 PR,验收条件 2 写明三族与承接卡 E2。C ⇒ 本卡只留本条,三族的细节交你走私密通道,本席等你回传修复点再重测。os-decision-facets
Prior rulings read: anonymous,openapi,i18n,public_read,capability → 67 hits; ADR-0056 D2, ADR-0090 D9, ADR-0104 (public_read opt-in), ADR-0138 D2 / Acceptance criteria 2; thread: 2 rulings (
6054113537A G2,6056614963D1 A′ D2b R), neither ruling on these three families.推荐:A(回退 C)。自检:只看①选 A;②③④ 是否翻转:否。
置信缺口:登录页文案读取路径、API 文档的匿名使用者、旧"未归属上传"链接三项未测。
你要做的: 回一行字母,例如「22146 A」。
Release:
session_01VZqqwTj2wsihZEbfT6yyYN· why: the G2 measurement is delivered and reviewed; what comes next is a maintainer decision · to:needs-user-decision, unassigned. This act moves the cardpm:dispatched→needs-user-decisionand removes the assigneeos-tesla.objectstack-fleet commentedon Oct 9, 2026
Ruling: batch #300 item 1 · letter A (G2 result: close the three doors, then accept) · maintainer 「同意」 2026-10-09T05:32Z
Director seat, summon #35,
session_01VYToj6PQehTEKNrjGM9akg(GitHubos-zhuang; written asobjectstack-fleet[bot]via the relay). Presented in batch #300 from thedomain:specseat 1's decision request (6074404262) on the G2 measurement (6074331185, ACCEPTED by the seat): on a booted reference deployment, D2's five classes answer as the ADR-0138 table says, and the everything-else class does not: three surface families answer an anonymous caller with 2xx, so acceptance criterion 2 is not met. A close the three doors, then accept; B accept now and close them under E2; C as A without public cards. The seat recommended A with C as the fallback; this seat recommended A with one added condition; the maintainer answered 「同意」. Thread-read: 6074404262. Freshness: body unchanged since the two earlierRuled:lines; no comment since the presentation; labelspriority:p1,security,needs-user-decision,domain:spec,target:v18,area:access. Premises re-read onorigin/main27a8b33dec:packages/runtime/src/domains/i18n.tscallsshouldDenyAnonymousin 0 files while the other eight dispatcher domains (actions,ai,analytics,auth,automation,meta,packages,security) do;registerOpenApiEndpointsis defined atpackages/rest/src/rest-server.ts:5095;packages/services/service-storage/src/storage-routes.ts:226calls download gating "a tracked follow-up". Added by this seat on objectuimain8f8f760fa4:apps/console/src/main.tsx:158handsloadLanguageandloadLocalesto theI18nProviderat application boot, before sign-in;loadLanguage.ts:19andloadLocales.ts:23fetch/api/v1/i18n/translations/:langand/api/v1/i18n/localeswith no Authorization header and degrade to the built-in packs on any failure. That closes the request's first confidence gap: the Console does read the/i18ndomain anonymously today.The ruling
A — close the three doors, then accept. The spec seat files three defect cards, public and at function level, for triage to route:
/i18ndispatcher domain gains the domain-level anonymous refusal every other dispatcher domain has (ADR-0056 D2; 401), with an objectui companion: the Console serves its sign-in page from the built-in packs and loads the application's translations after sign-in. The dev measures first whether the Console re-fetches translations once signed in today, and sizes the companion from that reading; the two land together so no signed-in user sees raw keys.acl: 'public_read'stays anonymous (ADR-0104).ADR-0138 D2's closed list gains a sixth class, the
public_readfile download, as a record revision (Tier H; the maintainer approves that PR). When the three cards have landed, the everything-else class is re-measured on a booted reference deployment; when it answers 401 throughout, the acceptance PR opens, with acceptance criterion 4's two back-references. ADR-0138 stays Proposed until then. ⛔ Not taken: B (an Accepted record with a known, measured gap empties the word, and sets the precedent for the next record), C (the code is open source and the three families read from it, so function-level cards add little exposure and keep the fixes in the ordinary queue). Disclosure: the route-level readings stay in the seat's container; this record and the three cards stay at function level, as the card body allows.Prior rulings read: 6054113537 (A on the eight questions and G2 as the gap) and 6056614963 (D1 A′ D2b R), both on this card, neither ruling on these families; ADR-0138 D2 and acceptance criterion 2; ADR-0056 D2 (default-deny for anonymous) and D9; ADR-0104 (the anonymous capability URL demoted to the opt-in
acl: 'public_read'); ADR-0090 D9; ADR-0049.check-prior-rulingsover 8 terms → 23 ADR hits (ADR-0056 D1 / D2 / D9, ADR-0023 §1–§5 on the OpenAPI connector, ADR-0029 D8, ADR-0036 §2 among them), none ruling the anonymous answer of these three families; thread: 2 rulings of 16 comments. 自检: 只看①选 A;②③④ 是否翻转:否(C 只改公开方式,不改方向)。置信缺口:Console 登录后是否重拉应用翻译未测(决定 objectui 配套的大小);API 文档是否有匿名的真实使用者(公开 API 门户)未测;旧「未归属上传」链接受影响的范围未测。State
needs-user-decision→pm:queue(domain:spec,priority:p1,security,target:v18,area:access) in this act; a thirdRuled:line added above the two earlier ones. The spec seat files the three defect cards and drafts the D2 revision; this card stays open for the re-measurement and the acceptance PR. The empty branchclaude/issue-22146-g2-sweepmay be deleted.Generated by Claude Code
docs(adr): ADR-0138 the guest model (Proposed) — doors, grants channe…
objectstack-fleet commentedon Oct 9, 2026
Claim: PM loop round 1 (#22146 round 4: the ADR-0138 D2 record revision under the ruling
6074960686, letter A) · 2026-10-09T06:33ZSession:
session_01VZqqwTj2wsihZEbfT6yyYNAccount:
os-tesla(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-22146-d2-public-read-classWorktree:
objectstack-issue-22146-d2Domain:
domain:specSeat:
domain:spec#1File surface:
docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.mdonly (stop on breach and explain in the report). The record stays Proposed.acl: 'public_read'file download (ADR-0104), with its admission, what it runs as and its enforcer, and every place the record counts "five" classes or names classes by number (onorigin/main27a8b33dec: the decision table:63, the ruling summary:74, D2:215and the adoption rule:244–:245, the enforcement map:487, acceptance criterion 2:578, the ADR-0090 back-pointer text:592, E2:607— re-read on the tree; this list is a lead, not a spec)./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 / 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 name them), and acceptance waits on those three cards and a re-measurement.Container & model:
S(one record),mode:subagent,model: default tier.docs/adr/**is a Tier H governed surface: the PR stays draft with the four-part final set (速读 draft by the dev, the seat's final, reviews requested fromos-zhuangandhotlong), and an at-tier contract review (CONTRACT_REVIEW_TIER, governed rule text) is owed before the maintainer's approval is asked for.Clause-②: no (a record revision; no accept set or public surface moves)
Responsibility:
n/a — not a defect cardThread-read: 6074960686
Serial constraints cleared: no open PR touches
docs/adr/0138-*.md(read 2026-10-09T06:33Z). The three defect cards it names (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, 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) are filed and unassigned; this revision does not wait on them.Ruling:
6074960686(batch #300 item 1, letter A, maintainer 「同意」): "ADR-0138 D2's closed list gains a sixth class, thepublic_readfile download, as a record revision (Tier H; the maintainer approves that PR). When the three cards have landed, the everything-else class is re-measured on a booted reference deployment; when it answers 401 throughout, the acceptance PR opens, with acceptance criterion 4's two back-references. ADR-0138 stays Proposed until then."The three defect cards filed under the ruling: #22432 (the
/i18ndispatcher domain, with its objectui companion), #22430 (the API-description endpoints), #22431 (storage download of a file with no scope and no field owner). After this round's PR, the card carriesBlocked-by:on all three for the re-measurement.This act moves the card
pm:queue→pm:dispatchedand assignsos-tesla.objectstack-fleet commentedon Oct 9, 2026
os-dev-report
{ "issue": 22146, "status": "done", "branch": "claude/issue-22146-d2-public-read-class", "pr": "https://github.com/objectstack-ai/objectstack/pull/22433", "session": "session_01VZqqwTj2wsihZEbfT6yyYN", "premise_still_valid": true, "summary": "Round 4 delivered as draft PR #22433 (Tier H, stays draft; one file, +121 / -37; Status stays Proposed): the ADR-0138 record revision under the G2 ruling 6074960686. D2's closed list gains class 6, the acl: 'public_read' file download (ADR-0104 D3 wave 2), in the existing columns: admitted by the file's own acl: 'public_read' (packages/services/service-storage/src/objects/system-file.object.ts#acl), running as a read of that one file's bytes with no principal evaluated, enforced in packages/services/service-storage/src/storage-routes.ts#registerStorageRoutes at its download gate authorizeDownload (a closure both download routes call; read on origin/main e02833c240). The adoption rule (classes 4 and 5 take the guest envelope; 1 to 3 and 6 their own declaration), every count of the door classes and the quoted ADR-0056 back-pointer count follow; the G2 result is recorded at class level under acceptance criterion 2 (classes 1-5 match; the public_read download was served as ADR-0104 rules and is now class 6; the everything-else class did not match: handleI18nRequest's /i18n domain, RestServer.registerOpenApiEndpoints, storage download of a file with no scope and no field owner) with the ruling's acceptance sentence quoted verbatim, and acceptance waits on #22432, #22430, #22431 and a re-measurement. Premises re-read on e02833c240 (5 commits past 27a8b33dec, none touching the record or the three families' files): i18n.ts still makes no shouldDenyAnonymous call. No code, no other record, no back-pointer lines; the card's assignee was not written.", "tests": "All at HEAD 024872cb90. Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (stderr: tree of objectstack-ai/objectstack at 024872cb90, --repo holds; 1 path, +121/-37 vs merge base e02833c24): 19 commands, all exit 0, each exit captured before any pipe (list under gates). check-adr-symbol-anchors: '2232 anchors across 141 records resolve'; positive control: the shared extractor reads 57 anchors in this record at base and 63 at head, the +6 (system-file.object.ts#acl, storage-routes.ts#registerStorageRoutes x2, the #authorizeDownload continuation, i18n.ts#handleI18nRequest, rest-server.ts#RestServer.registerOpenApiEndpoints) each resolving as a declaration via symbolSegmentResolution; negative control: authorizeDownloadNope reads absent. check-adr-links: '715 relative link destination(s) under docs/adr/ resolve'. check:doc-formula-expressions: first run exit 3 = PREREQUISITE NOT MET (@objectstack/formula unbuilt in the fresh worktree), not a measurement; built formula+lint under os-verify-lock (slot issue-22146-d2; 'VERDICT command-exit 0', held 104s, waited 71s; turbo 4/4), rerun exit 0. check:nul-bytes OK; separate control-byte grep on the record and the PR body: exit 1 (none). --ran reconciliation with recorded exit codes: '19 derived, 19 run, 0 NOT-MEASURED, 0 UNRUN'. check-issue-citations (dispatch-named, not in the derived set): exit 0 but '0 file(s) read' because docs/adr/** is a deferred surface, so NOT MEASURED for this diff; instead the three cited cards were read by REST GET: #22430, #22431, #22432 are open issues, not pull requests. No ablation (no code). CI: in_progress, not awaited.", "gates": [ "node scripts/check-adr-links.mjs :: 0", "node scripts/check-adr-links.mjs --self-test :: 0", "node scripts/check-adr-symbol-anchors.mjs :: 0", "node scripts/check-adr-symbol-anchors.mjs --self-test :: 0", "node scripts/check-ci-filter-parity.mjs :: 0", "node scripts/check-closing-keyword-parity.mjs :: 0", "node scripts/check-closing-keyword-parity.mjs --self-test :: 0", "node scripts/check-comment-mask-corpus.mjs :: 0", "pnpm check:adr-anchors :: 0", "pnpm check:cross-package-test-inputs :: 0", "pnpm check:doc-authoring :: 0", "pnpm check:driver-memory-census :: 0", "pnpm check:gitlink-declared :: 0", "pnpm check:nul-bytes :: 0", "pnpm check:pm-governed-merges :: 0", "pnpm check:pm-prior-rulings :: 0", "pnpm check:refd-timer-probe :: 0", "pnpm check:watch-hint-literal :: 0", "pnpm --filter @objectstack/lint run check:doc-formula-expressions :: 0", "(dispatch-named, outside the derived 19) node scripts/check-issue-citations.mjs :: 0, vacuous: 0 files read (docs/adr/** deferred)" ], "mcp_calls": "0", "api_writes": "3 REST writes, all through scripts/pm via the fleet-write relay (each a POST /repos/objectstack-ai/objectstack/dispatches, HTTP 204, run success, executed as objectstack-fleet[bot]): (1) pr_create via with-fleet.sh --via dispatch → POST /repos/objectstack-ai/objectstack/pulls (#22433, draft forced; body read back byte-identical, 12299 bytes, footer intact); (2) label-write.mjs --issue 22433 --add skip-changeset --assign os-tesla → POST /repos/objectstack-ai/objectstack/issues/22433/labels + POST /repos/objectstack-ai/objectstack/issues/22433/assignees (read-back matches: documentation, size/m, skip-changeset, assignee os-tesla; documentation and size/m added by the labelers, preserved; final re-read identical); (3) this os-dev-report via post-stamped.mjs → POST /repos/objectstack-ai/objectstack/issues/22146/comments. Plus git push x2 (not REST): the empty-branch claim marker at e02833c240 and the revision 024872cb90. No reviewer requested, no MCP call, no card-assignee write. Reads: the card and all 18 comments (one page), the three defect cards and the PR, by REST GET.", "files_changed": [ "docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md" ], "count_sites_changed": [ "TL;DR decision table D2 row :63 → :77: 'five door classes' → 'six door classes'; Ruled cell + 'class 6 added by the G2 ruling'", "D2 intro :215 → :238: 'exactly these five door classes' → 'six'; the Ruled line names 6074960686", "D2 table: row 6 added (:248)", "D2 adoption rule :244-:245 → :268-:270: 'classes 1 to 3 take their own declared grant' → 'classes 1 to 3 and 6 take their own declaration'", "D2 Enforced by :249 → :274 (NOT in the dispatch's lead list): 'Card E2 records the five classes' → 'six'", "Enforcement map :487 → :519: 'the five door classes' → 'the six door classes'", "Acceptance criterion 2 :578 → :624: 'each of D2's five classes' → 'six', plus the measured result and what remains", "Criterion 4 quoted ADR-0056 back-pointer text :592 → :665: 'closed list of five door classes' → 'six' (the count inside the quote only; the back-pointer itself not landed)", "E2 :607 → :682: 'the five classes recorded in the conformance matrix' → 'six'" ], "count_sites_left": [ ":74 → :88 the first ruling's summary, verbatim quote of 6054113537 ('the closed list of five door classes'): left, because editing it would rewrite a ruling; a new paragraph under it (:101-:106) records the G2 ruling and says why the quote still says five", "D1 point 1 '(D2, classes 4 and 5)' :207, D3 point 1 same :330, D8 'D2 class 1' :449, D2b 'D2's class 5 row' :504, E1 'denied on class 4' :681: left, class 6 is appended so classes 1-5 keep their numbers", "D2 'except at the two endpoint classes above' :254: left, still two (class 6 takes no guest envelope)", "Context table 're-measured at 7b926f7600' :122: left, a dated reading", "Alternatives G1 'every door class reads as a denial or a narrow door': left, the recorded reasoning for a letter not taken", "Criterion 1: left as the record has it (unmarked); see open_questions" ], "deviations": [ "Status line: the record's '⛔ Nothing in packages/** ... changes before acceptance' would contradict the ruling, which lands three defect cards before acceptance; reworded to 'no change this record decides lands in packages/** ... before acceptance', plus one sentence that the defect cards land first because they enforce ADR-0056 D2 and ADR-0104, both Accepted. A matching sentence was added under the Execution plan ('not execution cards of this record').", "Consistency edits beyond the dispatch's lead list, each forced by class 6 or the G2 result and named in the PR body: Decided by (+ the G2 ruling), Builds on (+ ADR-0104 D3 wave 2), Leaves unchanged (+ ADR-0104), Consumers (+ @objectstack/service-storage), Card line, the TL;DR note under the verbatim summary, the Context G2 bullet's pointer, a D2 'Measured, and not yet held' paragraph, the enforcement map's default-deny row status (names the three G2 exceptions), two Consequences bullets, Alternatives (+ G2-result B and C in the ruling's words), criterion 4's 'No line is added to' (+ ADR-0104), References (+ 6074331185, 6074404262, 6074960686 and the three cards).", "The dispatch's lead :74 (the ruling summary) was deliberately not edited: it is a verbatim quote of 6054113537; annotated instead (count_sites_left).", "check-issue-citations was named by the dispatch but is outside the derived 19; run anyway, it read 0 files (docs/adr/** is one of its deferred surfaces), so recorded as vacuous, with the three citations checked by REST instead.", "Worktree base is origin/main e02833c240, 5 commits past the dispatch's 27a8b33dec; none touch the record (byte-identical at both) or the three families' files, so the dispatch's line leads hold.", "Attribution: the commit carries the AGENTS.md model-free trailer pair (Claude-Session + Co-authored-by: Claude) and the PR body ends with the AGENTS.md session-URL footer, not the harness reminder's model-named trailer or its footer (os-dev.md: the harness reminder yields).", "Cleanup: worktree node_modules removed and git worktree remove (no --force) run after the PR and labels, before this report; the remote branch head 024872cb90 equals the local head at removal." ], "open_questions": [ { "question": "Should the post-card re-measurement also read class 6? The ruling names only the everything-else class for the re-measurement, but criterion 2 now names six classes, class 6's G2 reading is the observation that public_read files were served anonymously, and #22431 edits the very gate (authorizeDownload) that class 6 passes through.", "options": [ "A: the re-measurement reads class 6 beside the everything-else class (one more class-level line; no record change, criterion 2 already requires six)", "B: re-measure the everything-else class only, as the ruling words it, and rely on #22431's own pin that a public_read file stays anonymous" ], "recommendation": "A, because the change most likely to move class 6 is #22431 itself and a class-level line costs nothing; it is the seat's call, so the record does not add it." }, { "question": "Criterion 1 (the maintainer's approval): the record still says only 'the third is met', while the landing comment 6058198880 read criterion 1 as met by the approval on PR #22239. This revision is itself a Tier H approval.", "options": [ "A: leave the record's wording; the accepting change states criterion 1 met", "B: the seat marks criterion 1 met in this PR before asking for approval" ], "recommendation": "A: the revision did not touch criterion 1's wording, and the accepting PR is where all four are stated met together." } ], "out_of_scope_findings": [ "carrier: #22431 · noted, not filed — the acl: 'public_read' opt-in has no in-repo producer: both upload routes in registerStorageRoutes create sys_file rows as private, copy-on-claim copies the source row's acl, and FieldSchema declares no file acl, so ADR-0104's 'the field/file explicitly declares' exists only on the file-row side (the sys_file.acl select). Not a defect under ADR-0104 (an opt-in), but it bears on #22431: once an unclaimed upload such as an organization logo requires sign-in, nothing in the repository marks it public_read. Recorded in PR #22433's Acceptance notes; #22431's acceptance already asks its dev to measure the in-repo producers of unclaimed uploads." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
✅ ACCEPT — PR #22433 at
024872cb90(round 4: ADR-0138 D2 class 6). Tier H: waits on the maintainer's approvaldomain:specseat 1 (#6017) ·os-tesla· sessionsession_01VZqqwTj2wsihZEbfT6yyYN· 2026-10-09T07:07Z · holder of claim6075714215. Report:os-dev-report6076046095.Checked on GitHub and in the diff, not from the report:
main; line 1Part of #22146(the card stays open), line 2Clause-②: no; one file,docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md, +121 / −37;skip-changeset; assigneeos-tesla; the## 维护者速读(草稿)section is present.acl: 'public_read'file download, with its admission (system-file.object.ts#acl), what it runs as (that one file's bytes, no principal evaluated) and its enforcer (registerStorageRoutes'authorizeDownload); the adoption rule reads "classes 1 to 3 and 6 take their own declaration"; every door-class count reads six, and the one remaining "five" is the verbatim summary of ruling6054113537, annotated beneath it; criterion 2 records the G2 result at class level, the three cards, and the ruling's acceptance sentence verbatim; Status stays Proposed. Readings stay at function level.024872cb90: 35 runs, 24 success and 11 skipped;check-expected-skips --pr 22433: "OK — 11 skipped check-run(s), every one in the roster".check-governed-merges --pr 22433:docs/adr/**, landing tier H — an authorized APPROVED review, then the owning seat lands it; 158 changed lines.024872cb90(6076189164).The dev's open questions, decided by the seat:
acl: 'public_read'stays anonymous (ADR-0104) #22431 edits the gate class 6 passes through. No record change is needed.The
public_readproducer gap (no in-repo producer marks a filepublic_read) is carried to #22431 (6076074763).Next: the final 速读 on the PR,
needs-user-decisionon the PR, and review requests toos-zhuangandhotlong. After the approval, the seat lands the PR through the queue; the card then waits,pm:blocked, on #22432, #22430 and #22431 for the re-measurement.objectstack-fleet commentedon Oct 9, 2026
Landed: round 4, PR #22433 →
05c7c3fa3b(ADR-0138 D2 gains class 6; the G2 result recorded). The card waits on the three defect cards:pm:dispatched→pm:blockeddomain:specseat 1 (#6017) ·os-tesla· sessionsession_01VZqqwTj2wsihZEbfT6yyYN· 2026-10-09T07:40Z · holder of claim6075714215, released by this act.05c7c3fa3b(one parent,46692c118b), an ancestor oforigin/main.docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.mdis blob-equal to the approved head024872cb90.os-zhuang's APPROVED review on024872cb90(2026-10-09T07:24Z); the seat's ACCEPT6076202284; at-tier contract review PASS6076189164; the final 维护者速读6076213418.main: ADR-0138, still Proposed. D2's closed list has six classes (class 6, theacl: 'public_read'file download, ADR-0104). Criterion 2 records the G2 result at class level and what remains.What acceptance still needs (ruling
6074960686):/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 (the/i18ndispatcher domain, with its objectui companion), 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 (the API-description endpoints), 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 (storage download of a file with no scope and no field owner). They are filed for triage to route.6076202284, since 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 edits the gate class 6 passes through).Blocked-by:lines for the three cards are in the body. When the last one closes, the unblock sweep returns this card, and the spec seat re-claims it for the re-measurement.Release:
session_01VZqqwTj2wsihZEbfT6yyYN· why: round 4 is delivered and landed; what remains waits on three cards in other lanes · to:pm:blocked, unassigned. This act moves the cardpm:dispatched→pm:blockedand removes the assigneeos-tesla.objectstack-fleet commentedon Oct 9, 2026
Pointer from the
domain:cliseat: ruling6074960686item 2 has landed (#22430 → PR #22446 →166a94f75d). · seatdomain:cli#1(#6024) ·session_01BmsuLyUeuG5CNpZFMH1jzS· 2026-10-09T11:51Z. ⛔ Not a claim, ⛔ not a request for work. Function level.RestServer.registerOpenApiEndpointsmounts (the document and its viewer) refuse an anonymous caller with the shared401 UNAUTHENTICATED. The authorization matrix classifies the family (anonymous-deny-api-description), and the population baseline reads 31. The changeset declaresClause-②: no (narrowing),minor, as PR fix(service-storage)!: downloading a file with no attachments scope and no field owner requires a signed-in caller #22439 (item 3) did./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,/i18nwith the objectui companion, now landed on objectuimain) is next in this seat's lane.Generated by Claude Code
objectstack-fleet commentedon Oct 10, 2026
Pointer from the
domain:cliseat: the third door named in ruling6074960686item 1 has landed ·session_01BmsuLyUeuG5CNpZFMH1jzS· 2026-10-10T00:25Z. ⛔ Not a claim./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 → PR fix(runtime)!: the /i18n dispatcher domain refuses an anonymous caller, with the console pin moved past the sign-in companion (#22432) #22496 →b53b949a15: the/i18ndispatcher domain refuses an anonymous caller (401 UNAUTHENTICATED). With 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 (166a94f75d) and rest(public forms):POST /forms/:slug/submitanswers the anonymous submitter with the whole stored record, so any field a hook derives from existing data (a duplicate match, an owner) reaches the internet #22437 (7806a14117), all three doors of item 1 are onmain.I18nServicePlugin.registerI18nRoutes(packages/services/service-i18n/src/i18n-service-plugin.ts) mounts the same three/api/v1/i18npaths with no anonymous floor of its own. In every in-repo composition (os serve,os dev,DevPlugin) the dispatcher's gated mounts register first and answer; 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's dev measured that on a booted showcase. A host that composes the plugin on an HTTP server without the runtime dispatcher plugin was NOT measured. Source: 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's report6083216104,out_of_scope_findings[0].Generated by Claude Code
objectstack-fleet commentedon Oct 10, 2026
Unlock:
pm:blocked→pm:queue. All three anonymous-door cards are closed (#22430, #22431, #22432)Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-10T00:59Z. Unlock scan. ⛔ Not a claim, ⛔ not a dispatch. ⛔ Classes, positions and functions only.Thread-read: 6076657462
acl: 'public_read'stays anonymous (ADR-0104) #22431 closed at 2026-10-09T09:01Z, 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 at 2026-10-09T11:49Z, 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 at 2026-10-10T00:22Z. All three arecompleted./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's PR moved this repo's.objectui-shato47b1f0bb71, past its objectui companion, as ordered. Cloud's row for it rides objectstack-ai/cloud#2709.6076657462set: thedomain:specseat re-claims this card for the re-measurement of the guest model againstmainwith all three doors closed. Then the one ADR follows (batch Bump version to 0.3.4 #300 A).objectstack-fleet commentedon Oct 10, 2026
Claim: PM loop round 2 (#22146 round 5: the G2 re-measurement on
mainwith all three doors closed; then, if criterion 2 is met, the acceptance PR) · 2026-10-10T01:40ZSession:
session_01KNKBCRDJCu5tGy3TEbvtrFAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-22146-adr-0138-acceptanceWorktree:
objectstack-issue-22146Domain:
domain:specSeat:
domain:spec#3(seat post #18883)File surface:
Phase 1, the re-measurement: none. No tracked file is edited and no PR is opened. The branch is pushed at its base as the claim's marker. Classes 4 and 5 may again need uncommitted fixture declarations in the worktree (no example app declares an
authRequired: falseendpoint, re-read onfbb065fd4b); they are restored toHEADbefore teardown. The probes live outside the repository and are not published.Phase 2, only on this seat's ACCEPT of phase 1 with criterion 2 met:
docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md: the Status line, and criterion 2's record of the re-measurement at class level;docs/adr/0090-permission-model-v2-concept-convergence.mdanddocs/adr/0056-permission-model-landing-verification.md.Tier H (
docs/adr/**): the maintainer approves that PR. If criterion 2 is not met, there is no phase 2: the result comes back to this card for a decision.Container & model:
M,mode:subagent,model: default tier(dispatch-gates --tieron the phase-2 paths: no path-derived mandate; judged from the card: a measurement, then a record status change with no contract edit). Phase 2 owes a contract review atCONTRACT_REVIEW_TIERbefore the maintainer is asked.Clause-②: no (a measurement, then a record status change; no accept set or public surface changes)
Responsibility:
n/a — not a defect cardThread-read: 6091919249
Serial constraints cleared: the file lists of the 12 open PRs, read at 2026-10-10T01:40Z. None touches the three phase-2 ADRs or
examples/app-showcase/src/system/apis/**. The run builds and boots one local reference deployment on a random high port throughos-verify-lock.sh.Why the claim, with its source.
6091919249: "thedomain:specseat re-claims this card for the re-measurement of the guest model againstmainwith all three doors closed. Then the one ADR follows (batch Bump version to 0.3.4 #300 A)."6074960686, verbatim: "When the three cards have landed, the everything-else class is re-measured on a booted reference deployment; when it answers 401 throughout, the acceptance PR opens, with acceptance criterion 4's two back-references."6076202284: the re-measurement reads class 6 as well as the everything-else class. This round reads all six classes and the everything-else class on one boot of onemainSHA, so the acceptance PR cites a single reading.127.0.0.1and to that one server. No other host is probed.blockedwith the refusal as written. Nothing is worked around.This act moves the card
pm:queue→pm:dispatchedand assignszhuangjianguo.Generated by Claude Code
objectstack-fleet commentedon Oct 10, 2026
os-dev-report
{ "issue": 22146, "status": "blocked", "branch": "claude/issue-22146-adr-0138-acceptance", "pr": null, "session": "session_01KNKBCRDJCu5tGy3TEbvtrF", "premise_still_valid": true, "booted_sha": "fbb065fd4b (origin/main at worktree creation), with the uncommitted class 4/5 and negative-control fixtures; since restored to HEAD", "summary": "BLOCKED by the stop rule. The environment's safety check refused a step of the probes. While I was writing the everything-else sweep driver (the script that would send one anonymous request per registered surface), the response was stopped and withheld. The refusal, as written: \"Your response above was stopped by a safety classifier — this is not a tool or API error. The rest of it was withheld, and tool calls in it that had not finished did not run. Do not produce that content again, even reworded.\" Per the dispatch, I stopped there. I did not rephrase, re-route or retry the refused step, and the sweep driver never ran. No anonymous probe of any surface was sent, so no class was measured. What did happen: (1) Mechanism assumption 1 was confirmed by reading on fbb065fd4b. handleI18nRequest calls shouldDenyAnonymous at its first statement. RestServer.registerOpenApiEndpoints refuses an anonymous caller through enforceAuth as each handler's first act. authorizeDownload serves acl public_read and otherwise calls requireDownloadSession for a file with neither an attachments scope nor a field owner. None of these was confirmed by a booted answer. (2) Assumption 2 holds: the only example hit for an authRequired false declaration is a comment, so uncommitted fixtures were added for classes 4 and 5 and their negative controls. (3) Assumption 3 was measured: a signed-in admin can set acl public_read on the stored file record through the data API's ordinary record update of sys_file; it answered 2xx and the value read back. (4) The showcase was built, booted on a random high port in its own process group, and torn down. Only PM or the maintainer can unblock this: decide where, or by whom, the anonymous sweep may run (seat 1's earlier claim used the wording \"run, or have run in an environment that permits the probe\"). Then dispatch the measurement again.", "g2_class_results": [ "1 · the public form doors (resolve and submit) · matches D2: NOT MEASURED · no anonymous probe sent (blocked) · -", "2 · share links · matches D2: NOT MEASURED · no anonymous probe sent (blocked); subjects were created signed in, then destroyed with the ephemeral store · -", "3 · the public book and doc metadata reads · matches D2: NOT MEASURED · no anonymous probe sent (blocked) · -", "4 · authRequired: false endpoints of type object_operation · matches D2: NOT MEASURED · the fixture loaded (the boot reported 32 flows); never probed · -", "5 · authRequired: false endpoints of type flow · matches D2: NOT MEASURED · the fixture loaded; never probed · -", "6 · the acl public_read file download · matches D2: NOT MEASURED · a public_read file and a private unclaimed file were created signed in; never probed anonymously · -", "everything else · matches D2: NOT MEASURED · the booted route table was read (177 registered routes plus raw-app mounts), but the sweep driver was refused before it ran · -" ], "tests": "Measurement only, and stopped before any anonymous probe. No tracked change, so no gate family is owed: dispatch-gates --commands --repo objectstack-ai/objectstack exits 2 and prints \"this branch changes nothing against 'origin/main' (merge base fbb065fd4) - nothing to derive.\" Build: the showcase dependency closure, then the 8 workspace packages check-dev-prereqs named as unbuilt, both through os-verify-lock with slot issue-22146, OS_SKIP_DTS=1 and workspace-concurrency 2. Both printed VERDICT command-exit 0 (held 64s and 57s, waited 0s). check-dev-prereqs then exited 0 (\"67 package build artifacts present\"). os validate on the showcase with the fixtures: \"Validation passed\". Boot: the backend-only recipe from AGENTS.md (pnpm dev, --fresh, random high port), with an ephemeral tempdir and the seeded admin. The server was ready in about 12s. Requests sent: 22 in all, every one to 127.0.0.1 on that one server. They were the admin sign-in (one first attempt refused with INVALID_ORIGIN until an Origin header was added) and signed-in setup reads and writes. Zero anonymous surface probes were sent. Teardown: only the process group this session started was signalled. It then held 0 processes, the port had no listener, and the tempdir was gone. The 4 fixture files were restored with git checkout HEAD; git diff HEAD is 0 bytes and git status --porcelain is empty. The remote branch is at its base fbb065fd4b. The worktree is kept. The private scratch (setup readings, fixture patch, route-table dump, boot log) stays in this container; no g2r-readings.md verdict file was written, because no class was read.", "mcp_calls": "0", "api_writes": "1: this os-dev-report comment, sent through scripts/pm/post-stamped.mjs (the relay to POST issues/22146/comments). Also one git push, which is not REST: the marker branch at its base. Reads: the card and its 25 comments, through REST GET. No PR, no label or assignee write, and no MCP tool.", "deviations": [ "The boot carried a read-only route-table tap: a Node module load hook, preloaded only into the serve child, that wrapped the HTTP adapter's listen to record the server instance and every 5s wrote its mounted-route table to scratch. It did not change routing or answers, but the boot was not byte-for-byte the plain recipe.", "The sign-in and setup writes send an Origin header naming the server's own localhost origin, because the auth layer refuses a POST without one (INVALID_ORIGIN).", "The second build step filtered each unbuilt package together with its dependencies, so it rebuilt 58 packages instead of 8. That cost time only.", "Class 3's negative control was a fixture book with the org audience. No doc-level negative was prepared." ], "open_questions": [], "out_of_scope_findings": [ "carrier: the next G2 everything-else reading · noted, not filed (read from source and the live route table only; nothing measured). The approvals plugin mounts its actionable-link pages on the raw app (ApprovalsServicePlugin, the action-token peek and redeem handlers). This is a token-addressed door that D2's six classes do not name. Read from source, a request without a valid token is answered with an HTML result page rather than a 401. The next reading should classify this door explicitly.", "carrier: the next G2 everything-else reading · noted, not filed. The live route table also holds raw-app mounts that need an explicit classification under \"Outside the guest model by construction\" or the everything-else class: the OAuth well-known metadata documents, the runtime-config bootstrap reads (RuntimeConfigPlugin), the MCP skill read, and the dev-only metadata event stream (the metadata HMR routes). None was probed." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 10, 2026
Round 5 stopped by the stop rule: the environment's safety check refused the anonymous sweep. No class was measured. The card goes to the maintainer
domain:specseat 3 (#18883) ·zhuangjianguo· sessionsession_01KNKBCRDJCu5tGy3TEbvtrF· 2026-10-10T02:04Z · holder of claim6092292795, released by this act. Report:os-dev-report6092490239. Thread-read: 6092490239.What happened, checked by the seat:
The dev built the showcase closure, booted it from
origin/mainfbb065fd4bon a random high port in its own process group, and set up the subjects while signed in: apublic_readfile and aprivateunclaimed file, fixtures for classes 4 and 5, and their negative controls. All 22 requests went to127.0.0.1. Zero anonymous probes were sent.While the dev was writing the driver for the everything-else class, the environment's safety classifier stopped the response. Its words: "Your response above was stopped by a safety classifier — this is not a tool or API error. … Do not produce that content again, even reworded." The dev stopped there, as the claim's stop rule requires. Nothing was re-routed or reworded. ⛔ This seat does not run the sweep itself and does not hand it to another agent: a refused step stays refused.
Teardown, verified by the seat:
git status --porcelainis empty andgit diff HEADis 0 bytes;claude/issue-22146-adr-0138-acceptancesits at its basefbb065fd4bwith nothing committed.Read, but not measured on the wire: the three doors are closed in source on
fbb065fd4b:packages/runtime/src/domains/i18n.ts#handleI18nRequestcallsshouldDenyAnonymousfirst;packages/rest/src/rest-server.ts#RestServer.registerOpenApiEndpointsrefuses throughenforceAuth;packages/services/service-storage/src/storage-routes.ts#authorizeDownloadservesacl: 'public_read'and otherwise requires a session for an unclaimed file.A signed-in admin can set
acl: 'public_read'on a stored file through the data API's ordinarysys_fileupdate. That is class 6's producer path, which 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 left open.Class-level result: NOT MEASURED for all six classes and for the everything-else class. Criterion 2 is still unmet, and ADR-0138 stays Proposed.
For the next reading (function level; noted by the dev from source and the live route table, not probed):
ApprovalsServicePlugin's actionable-link pages (the action-token peek and redeem handlers) are a token-addressed door that D2's six classes do not name. Without a valid token they answer an HTML result page, not a 401. The next reading should classify this door explicitly.RuntimeConfigPlugin's bootstrap reads;Precedent: seat 1's session ran the 2026-10-09 sweep (
6074331185) in its own environment, on the maintainer's word. Where this one runs is the maintainer's to say.Maintainer-action: run, or have run in an environment that permits the probe, the ADR-0138 criterion 2 re-measurement on current
main(all six D2 door classes and the everything-else class, on one boot of one SHA, class-level publication only) — done when a comment on #22146 records a class-level result for each of the six classes and the everything-else classRelease:
session_01KNKBCRDJCu5tGy3TEbvtrF· why: the sweep is refused in this seat's environment, and running it elsewhere is the maintainer's call · to:pm:awaiting-maintainer, unassigned. When a class-level result is on record and the everything-else class answers 401 throughout, the domain:spec seat re-claims the card for the acceptance PR (Tier H, with criterion 4's two back-pointers).Generated by Claude Code
hotlong commentedon Oct 11, 2026
Maintainer-action attempted from the maintainer's own Claude session: also stopped by the environment's safety check. Not retried
The maintainer's session
64cb68a6-01c7-4dc6-acc6-ccb459c90ef2took this on the maintainer's instruction in chat, 2026-10-11 (「帮我处理 … ADR-0138 验收条件 2 的匿名探测复测」).6092490239).Reading for the queue: two Claude environments have now refused this sweep: the fleet seat's, and the maintainer's local session. The criterion-2 re-measurement therefore needs a run the maintainer performs themselves, outside a Claude session, or a different decision on how criterion 2 is evidenced. Criterion 2 stays unmet, ADR-0138 stays Proposed, and this card stays
pm:awaiting-maintainer.Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
Ruling: batch #331 duty-three item · letter A (criterion 2 is evidenced by an in-repo CI conformance test) · maintainer 「同意」 2026-10-12T22:47Z
Director seat, summon #39,
session_01TJKP6W6UDYEBHtccVdpVoz(GitHubos-steve; written asobjectstack-fleet[bot]through the relay, read back). Presented in this seat's chat with batch #331 as the one-line question on this card, with recommendation yes; the maintainer's word was 「同意」. Thread-read: 6110151111.The ruling. ADR-0138 acceptance criterion 2 is evidenced by a test in the repository, not by a one-off manual sweep: the dogfood authorization-conformance family in
packages/qa/dogfoodgains an anonymous sweep that boots the showcase, reads the registered route table, sends one unauthenticated request per route, and asserts the class-level outcome D2 states for each of the six door classes and401for everything else; it runs onmainwith the dogfood battery. The ADR's criterion-2 sentence is amended to say so (Tier H,docs/adr/**, the maintainer approves that PR), and the acceptance PR with criterion 4's two back-pointers follows once the test is green onmain. Two Claude environments refused the manual sweep (6092490239, 6110151111); a test that lives in the tree is also the shape ADR-0054 gives every other authorization proof.pm:awaiting-maintainer→pm:queue(domain:spec) in this act, unassigned; theMaintainer-action:line at 6092511536 is superseded by this ruling. The threeBlocked-by:lines named closed cards (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, closed 2026-10-09 and 2026-10-10) and leave the body in this act; no new blocker is derived.domain:spec· 2 PRs (the test, Tier S; the ADR wording with the back-pointers, Tier H) · precedent: the dogfood authz-conformance and authz-probe-blind-spot suites.blockedwith the refusal verbatim; nothing is reworded, re-routed or retried, and ⛔ never a second attempt in another agent. The card then returns topm:awaiting-maintainerwithMaintainer-action: write the anonymous-sweep conformance test by hand, or name another evidence form for criterion 2 — done when the test is on main, or a comment on #22146 names the form.objectstack-fleet commentedon Oct 13, 2026
Claim: PM loop round 1 (#22146, the test half of ruling
6134095444: the in-repo anonymous-sweep conformance test that evidences ADR-0138 acceptance criterion 2) · 2026-10-13T01:29ZSession:
session_01BRQ9Mp1yH7R1az6MaUM18HAccount:
os-justin(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-22146-anonymous-sweep-testWorktree:
objectstack-issue-22146Domain:
domain:specSeat:
domain:spec#2(seat post #18549)Ruling-ref:
6134095444(batch #331 duty-three item, letter A), read in this act; the earlier rulings6054113537,6056614963and6074960686stand.File surface (at
origin/mainc9b5b24b0or later; stop on breach and explain in the report):packages/qa/dogfood/test/(its own file; the sharedauthz-conformance.matrix.tsonly if the class table must live there, since open PR feat(plugin-security)!: no seeder or materializer writes a sys_permission_set row; the boot seeds no row for a declared set (#15204 stage 6b-2) #22900 edits that file), and the dogfood helpers it needs;.changeset/22146-*.mdonly if a published package changes (the dogfood package is private, soskip-changesetis the expected outcome);docs/adr/**: the criterion-2 wording and criterion 4's back-pointers are the second PR of this ruling, after the test is green onmain;Container & model:
M(one new dogfood suite on a booted showcase, with explicit door classification),mode:subagent,model: default tier(dispatch-gates --tier: no path-derived mandate).Clause-②: no (a test; no published accept set or public surface changes)
Responsibility: n/a — not a defect card
Thread-read: 6134095444
Serial constraints cleared (all 12 open PRs' file lists read at 2026-10-13T01:26Z): PR feat(plugin-security)!: no seeder or materializer writes a sys_permission_set row; the boot seeds no row for a declared set (#15204 stage 6b-2) #22900 (refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write under
singleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204, epic) editspackages/qa/dogfood/test/authz-conformance.matrix.ts; PR refactor(rest): move registerDataActionEndpoints into data-action-routes.ts (#23050 slice 1) #23116 editsauthz-probe-blind-spot.census.ts; PR test: the stage-8 test half — suites stop depending on the four catalog row objects (#15204 stage 8, S8-T) #22962 editspackages/qa/dogfood/vitest.config.ts. A new suite file shares none of them; if the class table must join the matrix, that region serialises behind PR feat(plugin-security)!: no seeder or materializer writes a sys_permission_set row; the boot seeds no row for a declared set (#15204 stage 6b-2) #22900. 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 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 are closed.This act moves the card
pm:queue→pm:dispatchedand assignsos-justin.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{ "issue": 22146, "status": "done", "branch": "claude/issue-22146-anonymous-sweep-test", "pr": "https://github.com/objectstack-ai/objectstack/pull/23137", "session": "session_01BRQ9Mp1yH7R1az6MaUM18H", "premise_still_valid": true, "summary": "Delivered the test half of ruling 6134095444 as draft PR #23137, which adds one dogfood suite (anonymous-door-sweep.dogfood.test.ts) and its closed classification table (anonymous-door-sweep.classification.ts). The PR changes no production code and nothing under docs/adr/**. What the suite does: (1) it boots the showcase as a deployment serves it, with MetadataPlugin over a build-shaped artifact and MCPServerPlugin, both of which serve composes; (2) it reads the registered route table off the running server from three guarded sources, the port's getMountedRoutes(), the framework's getRawApp().routes, and the declared endpoints the metadata service matches, so no route list is hardcoded; (3) it sends one unauthenticated request per route; (4) it judges each answer by class. Six door classes, the outside-by-construction families and middleware are the only rows. Everything else must answer 401 with an error envelope and nothing served, and a row that claims no route is red as stale. Each of the six classes is also asserted on a real subject, admitted and refused. Class-level result on head bf3ba9957: classes 1 to 6 match D2. The everything-else class does NOT: one family, the settings service's REST family (registerSettingsRoutes, @objectstack/service-settings), answers an anonymous caller from its own authorization logic instead of with 401. Per Zone 2 item 3 the assertion is kept as D2 states it, not weakened and not marked expected-to-fail, so the suite is red on the draft PR and the PM decides the next step. Zone 2 measurements: (1) holds; the route table is readable from inside the test, and the raw-app mounts, which the port does not record, come from the framework table. (2) holds with one refinement; classes 4 and 5 and the negative half of class 3 needed inline declarations beside the showcase (two endpoints, a no-data flow, an org-audience book and two docs), because a declared endpoint routes only through the ingested artifact. The share link and the uploaded files were created in-test while signed in, and the form and the public book are the showcase's own. No class needed a skip. (3) DISPROVEN, see above. (4) the file runs in the isolated dogfood project by name, with the dogfood battery on main; packages/qa/dogfood/vitest.config.ts is untouched. The classification names every surface the ruling listed, plus two the round-5 list did not: the discovery documents at the two spellings isAuthGateAllowlisted does not name literally, and the local storage adapter's signed byte URLs. The ADR amendment should name both.", "class_results": [ "1 · the public form doors · matches D2: yes", "2 · share links · matches D2: yes", "3 · the public book and doc metadata reads · matches D2: yes", "4 · authRequired: false object_operation endpoints · matches D2: yes (D3 empty state: the guest envelope, no grant, denied)", "5 · authRequired: false flow endpoints · matches D2: yes (admitted as the guest; the flow output only)", "6 · the acl: 'public_read' file download · matches D2: yes", "everything else · matches D2: no · one family, the settings service REST family (registerSettingsRoutes)" ], "tests": "Head bf3ba9957 (the branch merged with origin/main dfb3ba2b2, and plugin-security rebuilt after the merge). (a) pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/anonymous-door-sweep.dogfood.test.ts test/authz-conformance.test.ts, through os-verify-lock: 'Tests 1 failed | 74 passed (75)', VERDICT command-exit 1. The one failure is the everything-else case on the settings family, and every class leg is green. authz-conformance, which scans every *.dogfood.test.ts for row attribution, is green. (b) pnpm --filter @objectstack/dogfood typecheck, through os-verify-lock: tsc --noEmit, VERDICT command-exit 0, and tsc --listFilesOnly lists both new files in the program. (c) eslint --no-inline-config --format json over the 2 new files: 2 files, 0 errors, 0 warnings. The narrowing is safe because the printed config has no parserOptions.project or projectService, and eslint.config.mjs states that type-aware linting is never enabled, so the diff cannot move any untouched file's verdict. (d) Ablation A, the closed table: one outside-family row (the runtime-config bootstrap reads) was deleted through scripts/ablation-replace.mjs (anchor 1 to 0, blob eb86b66c06 to 749a3baeb8). The everything-else case went from 4 to 6 named routes, and all other cases were unchanged. Restored: blob eb86b66c06 equal to HEAD, git diff HEAD empty, git status --porcelain empty. (e) Ablation B, a product floor through dist/: the /i18n dispatcher domain's shouldDenyAnonymous guard was bypassed in packages/runtime source (blob d111be27d4 to a92151d583), and @objectstack/runtime was rebuilt. ablation-dist-preflight found the marker present in 2 built files, and the everything-else case went from 4 to 7 named routes. Restore: blob d111be27d4 equal to HEAD, runtime rebuilt, preflight --absent found the marker absent from all 6 built files with the working tree clean, and the everything-else case was back to the same 4. Both ablations moved in the predicted direction, more diagnostics. (f) Gates: see the gates field.", "gates": [ "derivation: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, at bf3ba9957 against merge base dfb3ba2b2: exit 0, 53 commands, 2 paths, 921 changed lines", "51 of the 53 exit 0 on the first run", "node scripts/check-plugin-teardown-shape.mjs --self-test: exit 3 (PREREQUISITE NOT MET, a pinned commit missing from the shallow clone). After git fetch --depth=1 origin 621a4876 it exits 0.", "pnpm check:dual-build-cjs-loads: exit 3 (PREREQUISITE NOT MET, 8 packages without dist). After a locked turbo build of those 8 (58 tasks, all cache replays) it exits 0.", "reconcile: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran (53 lines of 'command :: exit code'): exit 0, '53 derived famil(ies) accounted for — 53 run, 0 NOT-MEASURED (a DERIVED zero — all 53 recorded an exit code and none of them is 3)'", "CI on PR #23137 at bf3ba9957, read once: 12 success, 10 skipped, 20 in_progress (Build Core and Governed Surface Queue Guard in_progress). The dogfood battery is not part of PR CI; it runs on main." ], "files_changed": "2 files, +921/-0: packages/qa/dogfood/test/anonymous-door-sweep.dogfood.test.ts, packages/qa/dogfood/test/anonymous-door-sweep.classification.ts", "line_budget": "not applicable: no skills/** or governed path touched", "mcp_calls": "0", "api_writes": "3 REST writes, each one relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]). (1) pr_create through scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/pulls (draft) plus POST /repos/objectstack-ai/objectstack/issues/23137/assignees (os-justin). Read back: 9577 bytes sent, 9577 stored, identical. (2) labels_add through scripts/pm/label-write.mjs: POST /repos/objectstack-ai/objectstack/issues/23137/labels (skip-changeset); read back matching. (3) This os-dev-report through scripts/pm/post-stamped.mjs: POST /repos/objectstack-ai/objectstack/issues/22146/comments. git pushes, which are not REST writes: 4 to claude/issue-22146-anonymous-sweep-test (the empty marker, two commits, one merge of origin/main). Reads: the card and its 30 comments, the PR, and its check runs, all via REST GET.", "deviations": [ "The booted configuration is the showcase plus inline fixture declarations: a class-4 endpoint, a class-5 endpoint over a no-data flow, an org-audience book and two docs. The suite header and the PR body state this. The showcase's own two declared endpoints are authRequired: true and serve as the negative controls of classes 4 and 5.", "MetadataPlugin and MCPServerPlugin are added to bootShowcase. serve composes both and bootStack does not. Because bootStack runs the development posture, the dev-only metadata HMR routes mount, and they are classified on a measured route.", "The two server-wide 'ALL *' middleware registrations get no request, since they serve none of their own. The sweep sends the dev HMR POST, which reloads the artifact, last.", "To meet one gate's prerequisite, ran git fetch --depth=1 origin 621a487607881c66b2899b7e3477115229a156b4 into the shared object store. This adds one shallow boundary to the common .git.", "Commit trailers use the model-free pair AGENTS.md requires (Co-authored-by: Claude, Claude-Session). The model-named form in the harness reminder was not used. The PR footer is AGENTS.md's session-URL block alone, not the harness's two-line block.", "One merge of origin/main (dfb3ba2b2) into the branch before the PR, followed by a rebuild of plugin-security and a rerun of the suite." ], "open_questions": [], "out_of_scope_findings": [ "class: b · reach: public HTTP door, measured by the new suite on a booted showcase at bf3ba9957. An anonymous request to the settings service's REST family is answered by that family's own authorization logic, with a 2xx for the index and a 4xx other than 401 for the namespace reads and writes, never with the 401 floor. No settings value was served on this boot. · evidence: ADR-0056 D2 (Accepted), the platform denies anonymous callers by default with one shared '!userId && !isSystem → 401' decision; ADR-0138 D2, 'Everything else answers 401 (UNAUTHENTICATED)'. registerSettingsRoutes opens no handler with shouldDenyAnonymous or any anonymous floor. Seam: spec:ADR-0138 D2 everything-else 401 → runtime:packages/services/service-settings/src/settings-routes.ts#registerSettingsRoutes. It blocks criterion 2 the way the three doors of ruling 6074960686 did. · dedupe words: settings anonymous floor, registerSettingsRoutes shouldDenyAnonymous, ADR-0138 everything-else settings, anonymous settings index", "class: a · reach: public HTTP door, measured on the same boot. An anonymous request carrying a malformed token to the local storage adapter's signed byte routes answers 5xx INTERNAL instead of a 4xx. Read from source, not measured: LocalStorageAdapter.verifyToken throws a plain Error for a malformed, forged or expired token alike, and the two raw-byte handlers in registerStorageRoutes surface it as an internal error. · evidence: packages/services/service-storage/src/local-storage-adapter.ts#verifyToken; packages/services/service-storage/src/storage-routes.ts#registerStorageRoutes (the raw PUT and GET handlers) · dedupe words: storage token 500, verifyToken invalid token internal error, local storage signed url status, expired storage token 500", "carrier: the ADR-0138 criterion-2 wording PR (the domain:spec seat) · noted, not filed. The everything-else 401s carry two codes, UNAUTHENTICATED and AUTH_REQUIRED (the storage family). D2's text names UNAUTHENTICATED, while criterion 2 judges the status class. The suite asserts the status and the error envelope, not the code.", "carrier: the ADR-0138 criterion-2 wording PR · noted, not filed. The amendment should name the full classification the suite carries, including the two outside families the round-5 list did not have: the discovery documents (the REST root and the dispatcher's well-known document) and the local storage adapter's signed byte URLs." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
🔁 REWORK, patch round 1: PR #23137 at
bf3ba9957(Part of #22146). One item; the PR then parks in draft on #23142domain:specseat 2 (#18549) ·os-justin· sessionsession_01BRQ9Mp1yH7R1az6MaUM18H· 2026-10-13T03:12Z · holder of claim6136095148. Report:6137316611. Thread-read: the report.Read on GitHub, not from the report:
main. Its first line isPart of #22146and its second isClause-②: no. No closing keyword sits beside any card number. It carries 2 files, both underpackages/qa/dogfood/test/(+921 / −0). There is nodocs/adr/**path and no production code.@objectstack/dogfoodis"private": true, soskip-changesetis right.bf3ba9957): classes 1–6 match D2. The everything-else class does not: one family, the settings service's REST family, at function level. The dogfood battery is not in PR CI, so this red shows in no check run on the PR. It turnsmain's battery red if the PR lands first.REWORK item (sent to the same dev): a red run prints
METHOD path (source) — whyfor each route, and onmainthose lines land in public CI logs. The ruling6134095444says "no route-level readings go on GitHub". The default output must name the class, the count and the owning family or function. Route detail goes behind a local-onlyOS_TEST_*switch that is off in CI. Same assertions, same strength.Report items, one line each:
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, at function level, for triage to route (domain:services). It blocks criterion 2, as ruling6074960686's three doors did.LocalStorageAdapter.verifyTokenthrows a plain Error the two raw-byte handlers surface) #23143.UNAUTHENTICATEDandAUTH_REQUIRED): Acceptance notes. The carrier is the ADR-0138 criterion-2 wording PR.git fetch --depth=1 origin 621a4876into the shared object store: accepted. It adds one shallow boundary and changes no ref.Next:
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 merged).pm:dispatched→pm:blocked(Blocked-by: #23142,Unlock-action: re-check PR #23137). The assignee stays: this seat follows the PR to MERGED.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 lands, the suite is re-run green on a merged head. Then the PR goes ready and into the queue (no governed path; no contract surface).Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{ "issue": 22146, "round": "patch round 1 on PR #23137 (claim 6136095148): item 1, red output without route-level readings", "status": "done", "branch": "claude/issue-22146-anonymous-sweep-test", "pr": "https://github.com/objectstack-ai/objectstack/pull/23137", "head": "4bdd92c65 (git rev-parse --short HEAD); merges origin/main 287fd6e5d once, at acd55cd37", "session": "session_01BRQ9Mp1yH7R1az6MaUM18H", "premise_still_valid": true, "summary": "Item 1 is done in one commit (4bdd92c65), on anonymous-door-sweep.dogfood.test.ts only. By default a red sweep case now prints one line per class and mount family: the class (door class N, outside family ID or everything-else), the mount family the route table names (the pattern's leading segments past the API base, since the table records patterns, not registrars; a declared endpoint's family is its kind), the source, and the count of red routes. It never pairs a route with its answer and never prints a body. Route-level detail prints only when OS_TEST_ANON_SWEEP_ROUTE_DETAIL=1 is set, which follows Prime Directive #9's OS_TEST_* form. The switch is off by default and ignored whenever CI is set, and turbo's strict env mode never forwards it through turbo run test, because @objectstack/dogfood#test declares only OS_TEST_TIERS and OS_TEST_SHARD. The suite header gives the local command. Two smaller leaks are closed in the same spirit: a request that does not answer within 15s throws a path-free message unless the switch is on, and in the sweep it is recorded as status 0, so it is counted red instead of thrown with its route. The 'exactly one answer' case still prints route keys, with no status. Judgement is unchanged: the same routes are judged by the same predicates, and a case is red exactly when one of them misses its class, because the printed list is non-empty iff a route is red. On the new head the result is as before: classes 1 to 6 green, and the everything-else case red on the settings family (#23142), now printed as 'everything-else · /api/settings (port): 4 route(s) red'. The PR body was not edited.", "tests": "Head 4bdd92c65, one os-verify-lock run (VERDICT command-exit 0, held 199s, waited 505s). (a) pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/anonymous-door-sweep.dogfood.test.ts test/authz-conformance.test.ts: 'Tests 1 failed | 74 passed (75)', VITEST_EXIT=1. The one red case is everything-else, and its whole printed body is 'everything-else · /api/settings (port): 4 route(s) red' plus the local-switch hint; the log contains 0 lines with 'answered'. (b) pnpm --filter @objectstack/dogfood typecheck (tsc --noEmit): TYPECHECK_EXIT=0. (c) eslint --no-inline-config --format json over the 2 suite files: 2 files, 0 errors, 0 warnings, with the same invariance as before (no type-aware linting in eslint.config.mjs). (d) Ablation A rerun through scripts/ablation-replace.mjs: the console-bootstrap outside-family row was deleted (anchor 1 to 0, blob eb86b66c06 to 749a3baeb8). The default run went red with exactly three lines: 'everything-else · /api/settings (port): 4 route(s) red', 'everything-else · /api/v1/runtime (raw): 1 route(s) red', 'everything-else · /api/v1/studio (raw): 1 route(s) red'. That is the same 6 red routes as round 1, now counted by family, with 0 'answered' lines in the log. A second mutated run with OS_TEST_ANON_SWEEP_ROUTE_DETAIL=1, kept local and not quoted here, printed 6 route-level lines, which shows the switch works. Restore: blob eb86b66c06 equal to HEAD, git diff HEAD empty (0 bytes), git status --porcelain empty.", "gates": [ "derivation: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, at 4bdd92c65 against merge base 287fd6e5d: exit 0, 53 commands (the same 53 as round 1), 2 paths, 997 changed lines", "52 of the 53 exit 0 on the run at 4bdd92c65", "pnpm check:dual-build-cjs-loads: exit 3 (PREREQUISITE NOT MET, 8 packages without dist in the recreated worktree). After the locked turbo build of those 8 (EIGHT_BUILD_EXIT=0) it exits 0.", "reconcile: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran (53 lines of 'command :: exit code'): exit 0, '53 derived famil(ies) accounted for — 53 run, 0 NOT-MEASURED (a DERIVED zero — all 53 recorded an exit code and none of them is 3)'", "The gate run finished before the ablation began, so no gate read the mutated classification file. dual-build-cjs-loads was re-run after the restore; it reads dist only.", "CI: per the PM's 04:19Z reading, finished at 4bdd92c65 with no failures. It was not re-read here." ], "files_changed": "This round: 1 file, packages/qa/dogfood/test/anonymous-door-sweep.dogfood.test.ts (+89/-13 in 4bdd92c65), plus one merge of origin/main. The PR total against merge base 287fd6e5d is 2 files, +997/-0.", "line_budget": "not applicable: no skills/** or governed path touched", "mcp_calls": "0", "api_writes": "1 REST write: this os-dev-report, through scripts/pm/post-stamped.mjs (relay dispatch POST /repos/objectstack-ai/objectstack/dispatches executing POST /repos/objectstack-ai/objectstack/issues/22146/comments). There is no PR-body write and no label write this round. git push, which is not a REST write: 1 push of acd55cd37 (the merge) and 4bdd92c65 to claude/issue-22146-anonymous-sweep-test.", "deviations": [ "The local switch is named OS_TEST_ANON_SWEEP_ROUTE_DETAIL (OS_TEST_* per Prime Directive #9), with the value 1 to turn it on. It is a test-only switch, so it takes no _ENABLED suffix.", "The 'owning family' is the mount family the route table can name, the pattern's leading segments past the API base. The framework table records patterns, not registering functions or plugins, so no registrar name is printed. A declared endpoint's family is its kind (type and authRequired).", "Lock contention: this round queued about 60 minutes in total before the one locked run that did everything (suite, typecheck, the 8-package build, ablation A). The holders, all alive and making progress, were pid 9779 (a spec and lint chain), pid 13241 (a lint and objectql build) and pid 18977 (issue-23100's gate battery). The 53 gates, which take no lock, ran in the gap.", "Stray file left for a person to remove: /build-r2.pid (5 bytes, a background job's pid) was written at the filesystem root by a mis-scoped shell variable in this round. The removal was refused by the environment's built-in safety check, and it was not worked around.", "The worktree was recreated on the existing branch for this round, then removed again after the push (node_modules removed, git worktree remove exit 0, no --force)." ], "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
✅ Patch round 1 accepted: PR #23137 at
4bdd92c65. The card movespm:dispatched→pm:blockedon #23142; the PR parks in draftdomain:specseat 2 (#18549) ·os-justin· sessionsession_01BRQ9Mp1yH7R1az6MaUM18H· 2026-10-13T04:49Z · holder of claim6136095148. Report:6138455961. Thread-read: the report.Read on GitHub:
4bdd92c65changes one file, the suite (+89 / −13), plus one merge ofmain.OS_TEST_ANON_SWEEP_ROUTE_DETAIL === '1' && !process.env.CI. The default red line prints the class, the mount family, the source and a count, and pairs no route with an answer.everything-else · /api/settings (port): 4 route(s) red(the dev's local run).answeredline. The restore was proven by blob hash.4bdd92c65: 34 success, 7 skipped, 0 failures.Lint & Repo GatesandTypeScript Type Checkboth pass on this head.dual-build-cjs-loadsran again after its prerequisite build.State:
Blocked-by: #23142withUnlock-action: re-check PR #23137(both are body lines now). The assignee stays: this seat follows PR test(dogfood): the anonymous door sweep — ADR-0138 criterion 2 as an in-repo conformance test #23137 to MERGED.## Parked in draftsection: the expected red, its whole list (that one case and that one family), and the unlock condition.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 merged. Then mergemain, re-run the suite throughos-verify-lockwith every case green, then ready and the queue.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 is unrouted at this read. If it is still unclaimed once triage gives it a lane, this seat claims it as the unowned blocker (pm-dispatch's cross-lane channel for an unowned blocker).Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Pointer from
domain:servicesseat 2 ·session_01FzQy26KznSa5j2gnf3AzsE· 2026-10-13T06:08Z. Not a claim; this card stays its holder's.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 closedcompletedby PR fix(service-settings)!: the settings REST family refuses an anonymous caller with the shared 401 floor, mounted once (#23142) #23171, merged as20949e7a7conorigin/main. The settings REST family now answers an anonymous caller401 UNAUTHENTICATEDon all four of its routes, before any settings logic. That was the fourth door this card'spm:blockedwaited on, so PR test(dogfood): the anonymous door sweep — ADR-0138 criterion 2 as an in-repo conformance test #23137's sweep can re-run againstmain.authz-conformance.matrix.tsdraws its population from the REST and runtime route ledgers only. It does not read service-owned ledgers such assettings-route-ledger.ts, which has four rows. Today the settings floor is ratcheted by its package-local census pin and by PR test(dogfood): the anonymous door sweep — ADR-0138 criterion 2 as an in-repo conformance test #23137's live sweep, not by the matrix. The contract review of PR fix(service-settings)!: the settings REST family refuses an anonymous caller with the shared 401 floor, mounted once (#23142) #23171 (6139053386, ③ out-of-scope 2) recommends that E2's matrix population take service-owned route ledgers, this family first.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Unlock:
pm:blocked→pm:dispatched. #23142 landed;mainis merged into PR #23137, and the suite re-run is nextdomain:specseat 2 (#18549) ·os-justin· sessionsession_01BRQ9Mp1yH7R1az6MaUM18H· 2026-10-13T06:21Z · holder of claim6136095148. Thread-read:6139415669.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 is closedcompletedby PR fix(service-settings)!: the settings REST family refuses an anonymous caller with the shared 401 floor, mounted once (#23142) #23171, landed onmainas20949e7a7("the settings REST family refuses an anonymous caller with the shared 401 floor, mounted once"). No other blocker is known. The suite is the measurement that says whether one exists.4bdd92c65→87a0f748athrough the relay's base sync (pr_update_branch, a platform merge ofmain; no file of this PR conflicts). TheBlocked-by:andUnlock-action:body lines leave, and the card moves back topm:dispatchedwith its assignee.anonymous-door-sweep.dogfood.test.tson87a0f748athroughos-verify-lock, as soon as a batch slot frees.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 was. The PR stays draft.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{ "issue": 22146, "round": "resume after the unlock 6139589944: the suite re-run on PR #23137 with #23142 on main", "status": "done", "branch": "claude/issue-22146-anonymous-sweep-test", "pr": "https://github.com/objectstack-ai/objectstack/pull/23137", "head": "87a0f748a (git rev-parse --short HEAD; equal to git ls-remote; contains 20949e7a7, the settings fix, checked with merge-base --is-ancestor exit 0). No merge, no commit and no push this round.", "session": "session_01BRQ9Mp1yH7R1az6MaUM18H", "premise_still_valid": true, "summary": "Every case is green on 87a0f748a: 75 of 75 across the anonymous sweep and authz-conformance, and the dogfood typecheck exits 0. Class-level result: classes 1 to 6 match D2, and the everything-else class now matches D2 too (it answers 401 throughout, nothing served), so ADR-0138 criterion 2's test is green on a head that holds main's settings-family fix. Nothing was changed. The everything-else case asserts its own floor (more than 100 routes judged) and it held. The exact count of routes judged is NOT MEASURED this round. A green run prints no count, and reading it needed a temporary, restored log tap. The first tap was a no-op: its replacement re-contained its anchor, so ablation-replace refused it and restored the file, blob equal to HEAD. The second tap, with a fixed anchor, queued twice and never acquired the lock, held by pid 12445 (issue-23100's gate battery). No code change was needed for the suite to pass.", "class_results": [ "1 · the public form doors · matches D2: yes", "2 · share links · matches D2: yes", "3 · the public book and doc metadata reads · matches D2: yes", "4 · authRequired: false object_operation endpoints · matches D2: yes (D3 empty state)", "5 · authRequired: false flow endpoints · matches D2: yes", "6 · the acl: 'public_read' file download · matches D2: yes", "everything else · matches D2: yes · routes judged: the suite's floor (more than 100) held; exact count NOT MEASURED (see summary)" ], "tests": "One os-verify-lock run at 87a0f748a (VERDICT command-exit 0, held 740s, waited 447s, a shared box). Build: pnpm turbo run build --filter='@objectstack/dogfood^...' --concurrency=2, BUILD_EXIT=0, 64 of 64 tasks successful. Suite: pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/anonymous-door-sweep.dogfood.test.ts test/authz-conformance.test.ts, VITEST_EXIT=0, 'Test Files 2 passed (2)', 'Tests 75 passed (75)'. Typecheck: pnpm --filter @objectstack/dogfood typecheck, tsc --noEmit, TYPECHECK_EXIT=0. Working tree after the run: git status --porcelain empty.", "gates": [ "pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/anonymous-door-sweep.dogfood.test.ts test/authz-conformance.test.ts :: exit 0 (75 passed)", "pnpm --filter @objectstack/dogfood typecheck :: exit 0", "dispatch-gates: not re-derived this round, as ordered (no change; the head carries the PM's base sync only)" ], "files_changed": "none", "line_budget": "not applicable: no change", "mcp_calls": "0", "api_writes": "1 REST write: this os-dev-report, through scripts/pm/post-stamped.mjs (relay dispatch POST /repos/objectstack-ai/objectstack/dispatches executing POST /repos/objectstack-ai/objectstack/issues/22146/comments). No git push, no PR-body write and no label write.", "deviations": [ "The everything-else count of routes judged was not obtained: the read-only count tap's first attempt was a no-op, refused and restored, and its second never acquired the lock (holder pid 12445, issue-23100's gate battery). It is reported as NOT MEASURED, not estimated.", "The worktree was recreated with git worktree add -B at origin/claude/issue-22146-anonymous-sweep-test (87a0f748a). The local branch, at 4bdd92c65, is an ancestor of it, so nothing was dropped. After the run, node_modules was removed and the worktree removed (git worktree remove exit 0, no --force).", "Still present, for a person to remove: /build-r2.pid, the stray 5-byte file at the filesystem root from patch round 1. The environment's safety check refused its removal." ], "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
✅ Re-run accepted; PR #23137 at
87a0f748ais ready with auto-merge armed (Part of #22146)domain:specseat 2 (#18549) ·os-justin· sessionsession_01BRQ9Mp1yH7R1az6MaUM18H· 2026-10-13T07:22Z · holder of claim6136095148. Report:6140429847. Thread-read: the report.Tests 75 passed (75)across the anonymous sweep andauthz-conformance, and the dogfood typecheck exits 0. This is on87a0f748a, which carries20949e7a7(the settings fix). Classes 1 to 6 match D2, and the everything-else class answers 401 throughout. ADR-0138 criterion 2's test is green. The exact count of routes judged is NOT MEASURED (the lock was taken), but the suite's own floor (more than 100) held.87a0f748a: 34 success and 7 skipped, every skip on theEXPECTED_SKIPSroster (Check Changesetbecause the PR carriesskip-changeset). 0 failures.mergeable: clean.check-governed-merges --pr 23137: not governed, 997 changed lines.pr_ready+automerge_enable(run38362114527). The PR body's parked section now records the unlock and the green re-run.pm:dispatchedwith its assignee.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Landed: the test half, PR #23137 →
8323cad97(Part of #22146). The card goes back topm:queuefor the ADR-0138 wording-and-acceptance PRdomain:specseat 2 (#18549) ·os-justin· sessionsession_01BRQ9Mp1yH7R1az6MaUM18H· 2026-10-13T07:37Z · holder of claim6136095148, released by this act. Thread-read:6140475138.8323cad97, an ancestor oforigin/main. It addspackages/qa/dogfood/test/anonymous-door-sweep.dogfood.test.ts(+658) andanonymous-door-sweep.classification.ts(+339), and nothing else.Part of #22146and carries no closing keyword. No issue closed in the merge window. This card stays open.6136095148;6137316611, REWORK6137371330, patch report6138455961;6138474810on 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, unlocked6139589944once20949e7a7landed;6140429847(75/75), queued6140475138.packages/qa/dogfood. It runs withmain's dogfood battery (Dogfood Regression Gate, which skips on PR heads).6134095444): one Tier H PR ondocs/adr/0138-…. It amends criterion 2's sentence to name the test, names the suite's full classification (the six door classes; the outside-by-construction families, including the two the round-5 list lacked: the discovery documents and the local storage adapter's signed byte URLs), notes the two refusal codes (UNAUTHENTICATEDandAUTH_REQUIRED), and carries criterion 4's two back-pointers. That accepting PR lands on the maintainer's approval.main". The next claimer first readsDogfood Regression Gateon amainrun that contains8323cad97, and starts only when it concludessuccess.Release:
session_01BRQ9Mp1yH7R1az6MaUM18H· why: a partial landing (Part of #22146) · to:pm:queue, unassigned, for the ADR-0138 wording-and-acceptance PR above. Resume-from: none (the next stage starts frommain). This act moves the cardpm:dispatched→pm:queueand removes the assigneeos-justin.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Claim: PM loop round 1 (#22146, the ADR half of ruling
6134095444: the ADR-0138 wording-and-acceptance PR, criterion 2 named to the in-repo sweep and criterion 4's two back-pointers) · 2026-10-13T08:57ZSession:
session_01BRQ9Mp1yH7R1az6MaUM18HAccount:
os-justin(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-22146-adr-0138-acceptanceWorktree:
objectstack-issue-22146-adrDomain:
domain:specSeat:
domain:spec#2(seat post #18549)Ruling-ref:
6134095444(letter A), read in this act; the earlier rulings6054113537,6056614963and6074960686stand.Precondition, read in this act:
Dogfood Regression Gateconcludedsuccess(all 3 shards and the aggregate) onmainCI run 38363741526, whose head is8323cad97, the landing of PR #23137.File surface (at
origin/main150da61aaor later; stop on breach and explain in the report):docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md: the status and acceptance text, criterion 2 named to the sweep and its full classification, the two refusal codes noted;docs/adr/0090-permission-model-v2-concept-convergence.mdanddocs/adr/0056-permission-model-landing-verification.md: one status-line continuation each, criterion 4's form;docs/adr/only if it records ADR-0138's status;packages/**,content/docs/**orskills/**(the record's own rule before acceptance), and no execution card filed;Container & model:
M(one Tier H decision-record PR: status, acceptance criteria and two back-pointers),mode:subagent,model: default tier(dispatch-gates --tier: no path-derived mandate).Clause-②: no (decision-record text; no published accept set or public surface changes)
Responsibility: n/a — not a defect card
Thread-read: 6140706427
Serial constraints cleared (all 13 open PRs' file lists read in this act): none touches
docs/adr/0138-…,0090-…,0056-…or the ADR index. The test half (PR test(dogfood): the anonymous door sweep — ADR-0138 criterion 2 as an in-repo conformance test #23137) is onmainas8323cad97.This act moves the card
pm:queue→pm:dispatchedand assignsos-justin. Tier H: the PR lands on an authorized approval only.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{ "issue": 22146, "status": "done", "branch": "claude/issue-22146-adr-0138-acceptance", "pr": "https://github.com/objectstack-ai/objectstack/pull/23219", "head": "1b9e22281 (git rev-parse --short HEAD; equals git ls-remote of the branch; one commit on base 150da61aa)", "session": "session_01BRQ9Mp1yH7R1az6MaUM18H", "premise_still_valid": true, "summary": "Opened one Tier H draft PR, #23219 (first line Fixes #22146, Clause-② line copied from the claim, assignee os-justin, label skip-changeset). It accepts ADR-0138 and names criterion 2's in-repo evidence. ADR-0138 reads Accepted (2026-10-13), accepted by the approval of that PR. Criterion 2 now names the dogfood anonymous sweep and its full classification at function level: the six door classes; the outside-by-construction families, each with a reason and a symbol anchor (D2's three, the five ruling 6134095444 listed, and the two the round-5 list lacked); the middleware exclusion; the everything-else rule; and a two-codes note. Criterion 2 keeps the 2026-10-09 measurement as history and records the result on main at class level (four cards closed; Dogfood Regression Gate success on run 38363741526 at 8323cad97, and again on 38372006085 at 150da61aa). ADR-0090 and ADR-0056 each gain criterion 4's line verbatim, with DATE filled, as a one-line status continuation. No decision D1-D9 or D2b changes in substance.", "files_changed": "3 files, +159 / -51 (210 changed lines, under the 3000 human-merge threshold): docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md, docs/adr/0090-permission-model-v2-concept-convergence.md (+1), docs/adr/0056-permission-model-landing-verification.md (+1). Nothing in packages/**, content/docs/** or skills/**. No ADR index records a status: docs/adr has no README, and PRIORITIZATION.md is retired. So no index edit.", "tests": "Docs-only change: no package build or test is owed, and the derived set holds none. Gates on head 1b9e22281: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 19 commands from merge base 150da61aa, the same 19 as the PM lead list. Each was run with its exit code captured before any pipe: node scripts/check-adr-links.mjs :: exit 0 | node scripts/check-adr-links.mjs --self-test :: exit 0 | node scripts/check-adr-symbol-anchors.mjs :: exit 0 | node scripts/check-adr-symbol-anchors.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-corpus.mjs :: exit 0 | pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 | pnpm check:adr-anchors :: exit 0 | pnpm check:cross-package-test-inputs :: exit 0 | pnpm check:doc-authoring :: exit 0 | pnpm check:driver-memory-census :: exit 0 | pnpm check:gitlink-declared :: exit 0 | pnpm check:nul-bytes :: exit 0 | pnpm check:pm-governed-merges :: exit 0 | pnpm check:pm-prior-rulings :: exit 0 | pnpm check:refd-timer-probe :: exit 0 | pnpm check:watch-hint-literal :: exit 0. check:doc-formula-expressions first exited 3 (PREREQUISITE NOT MET: @objectstack/formula and @objectstack/lint unbuilt). Both were built through os-verify-lock (VERDICT command-exit 0, held 180s, waited 503s), and the re-run exits 0. Reconcile: '✓ dispatch-gates --ran: 19 derived famil(ies) accounted for — 19 run, 0 NOT-MEASURED (a DERIVED zero — all 19 recorded an exit code and none of them is 3).' Anchor proof (one-off, no file kept): node scripts/ablation-replace.mjs mutated ADR-0138's ApprovalService.peekActionToken anchor to peekAblatedToken. Anchor count 1 -> 0; blob f799acb3ed4d -> 2995ea9555e4. check-adr-symbol-anchors went red ('[unresolved-symbol] ...0138-...md:669 ... peekAblatedToken has no declaration site'). The tool restored the file (blob == HEAD f799acb3ed4d; git diff HEAD empty; git status clean). A first attempt was a refused no-op: its replacement contained the anchor text ('anchor count moved 1 -> 1'), so the gate never ran. The back-pointers were compared by script with criterion 4's block, whitespace-normalised: both identical. Control-byte self-scan of the 3 files and the PR body: no match. The PR body was read back after the write: 10089 chars sent and stored, identical. check-closing-keyword-parity --body: only 'Fixes #22146' binds. PR CI, read once, not polled: Governed Surface Queue Guard, Check Changeset, PR Size and the claim guards are success; Lint, Type Check and Test Core shards are in_progress.", "gates_ci": "in_progress (read once after the PR opened; not awaited)", "mcp_calls": "0", "api_writes": "3 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]), carrying 4 REST writes: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/23219/assignees [os-justin], run 38377176070 success; (2) label-write labels_add = POST /repos/objectstack-ai/objectstack/issues/23219/labels [skip-changeset], run 38377277580 success, read back as documentation, size/m, skip-changeset; (3) this os-dev-report comment = POST /repos/objectstack-ai/objectstack/issues/22146/comments. git push (2 pushes of the branch) is not a REST write.", "deviations": [ "Assumption 1's list of what moves was short by three spots, each measured and changed. (a) D2's 'Outside the guest model by construction' list gains ONE pointer bullet to criterion 2. Without it, the accepted record's D2 would list three outside families while the classification on main holds ten. The bullet restates nothing. (b) The Decision lead said a contract with no enforcer 'is Proposed, exactly as the record is'; that clause is gone. (c) The Enforcement-map lead got the same edit.", "The two-codes note sits in criterion 2, not in D2. D2 is ruled text, and it decides the status. AUTH_REQUIRED is registered for @objectstack/service-storage in the ADR-0112 ledger, and writing it into D2 would read as ruling two codes for one refusal.", "Criterion 4's template block in ADR-0138 keeps the literal DATE. Only the landed lines in ADR-0090 and ADR-0056 carry 2026-10-13. So the date appears 3 times: the ADR-0138 status and the two continuations.", "ADR-0138's status line carries no PR number. Adding it after pr_create would have moved the head the PR body cites, and the body is written once. The seat may add the link later, as the change after ADR-0139's acceptance did.", "Not measured: whether the sweep file appears in the dogfood shard test logs of run 38363741526. The job-log host (blob storage) is refused by the container's egress proxy. Read instead: the gate's conclusion, and the 'Test completeness guard' step, success on all three shards.", "Commit trailers are the model-free pair AGENTS.md prescribes (Claude-Session + Co-authored-by: Claude). The harness reminder's model-named Co-Authored-By was not used; this reports it and does not count it as a deviation." ], "cleanup": "node_modules removed before this post. git worktree remove ../objectstack-issue-22146-adr (no --force) is the step after it. No server, monitor or background process remains: the gate loop and the lint build both exited and were reaped.", "open_questions": [], "out_of_scope_findings": [ "carrier: card E2 (cut by the seat from the accepted record) · noted, not filed · The Enforcement-map row 'the list itself' still names only AUTHZ_CONFORMANCE (card E2). The in-repo sweep now holds the list on main as a proof. Whether it is also the map's enforcer is E2's to measure; the PR's Acceptance notes say so.", "carrier: 承接者:无 · noted, not filed · One refusal class answers with two codes on the wire (UNAUTHENTICATED, and AUTH_REQUIRED from the storage download and upload gates). AUTH_REQUIRED is a registered storage code that objectui consumes, and the synonym detector does not flag it. It is recorded in criterion 2. No contract text this round read says one code per refusal, so it is not class (b)." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
✅ ACCEPT: PR #23219 at
1b9e22281(Fixes #22146). Tier H (docs/adr/**): it waits on the maintainer's approval, which is ADR-0138's criterion 1domain:specseat 2 (#18549) ·os-justin· sessionsession_01BRQ9Mp1yH7R1az6MaUM18H· 2026-10-13T09:39Z · holder of claim6142011298. Report:6142672477. Thread-read: the report.Read on GitHub:
Shape. The PR is a draft against
main. Line 1 isFixes #22146, the only closing keyword. It changes 3 files underdocs/adr/**(+159 / −51 = 210 lines) and nothing inpackages/**,content/docs/**orskills/**. It carriesskip-changeset, its assignee isos-justin, and it files no execution card.check-governed-merges --pr 23219: landing tier H.The back-pointers. ADR-0090 and ADR-0056 each gain one status-line continuation. Each is criterion 4's text verbatim, with DATE filled, in ADR-0042's form (the continuation line right under
**Status**).ADR-0138, sentence by sentence against the diff:
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, 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), and the suite at8323cad97green onmainruns 38363741526 and 38372006085.Criterion 2's classification, checked against
anonymous-door-sweep.classification.tsas PR test(dogfood): the anonymous door sweep — ADR-0138 criterion 2 as an in-repo conformance test #23137 landed it:6134095444ordered classified explicitly, each with its reason and a symbol anchor;UNAUTHENTICATED, andAUTH_REQUIREDon storage).Every family is named at function or mount level, and there is no route-level reading anywhere.
Evidence accepted:
--ranreconcile has 0 NOT-MEASURED.check-adr-symbol-anchorsred, and the file was restored by blob hash.CI at this read: 30 success and 11 skipped (all on the
EXPECTED_SKIPSroster;Check Changesetskips forskip-changeset), withTest Core (1/6)in progress. 0 failures.Deviations, one line each:
2026-10-13, and the status line carries no PR number: accepted. If the merge falls on another day, the seat raises that one-word fix in the quick-read.One addition this seat names for the maintainer (in the quick-read): criterion 2 now says "A route moves into a door class or an outside family only by an amendment of this record, and the classification follows that amendment." That follows from D2 being a closed list. It also means a future outside-family row in the dogfood classification goes through a Tier H ADR edit.
Report items, one line each:
AUTHZ_CONFORMANCE: Acceptance notes. Whether the sweep is also its enforcer is card E2's to measure when the seat cuts it.Next:
needs-user-decisionon the PR, the final quick-read, and review requests toos-zhuangandhotlong. On an authorized approval the seat lands it (Tier H). At MERGED it cuts execution cards E1–E4 from the accepted record. The card stayspm:dispatchedwith its assignee.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
PR #23219 approved and armed by the maintainer:
os-zhuangAPPROVED at1b9e22281, then readied it, enabled auto-merge and queued itdomain:specseat 2 (#18549) ·os-justin· sessionsession_01BRQ9Mp1yH7R1az6MaUM18H· 2026-10-13T09:50Z · holder of claim6142011298. Thread-read:6142722183.Read on GitHub:
5501651056,os-zhuang,APPROVED, on1b9e22281, the PR's head. The account is inGOVERNED_APPROVERS(scripts/pm/check-governed-queue-guard.mjs). This is ADR-0138's acceptance criterion 1.ready_for_reviewandauto_merge_enabledbyos-zhuangat 2026-10-13T09:43Z, and the merge-queue entry at 2026-10-13T09:45Z. These are the maintainer's own landing acts on the Tier H PR. This seat did not flip, arm or queue it.This act:
needs-user-decisionleaves the PR, because the approval it waited on is in.CI on
1b9e22281: every suite completed, with 0 failures.Next:
The card stays
pm:dispatchedwith its assignee until MERGED.Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Landed: PR #23219 →
38a2d3c76(Fixes #22146). ADR-0138 is Accepted, and execution cards E1–E4 are cutdomain:specseat 2 (#18549) ·os-justin· sessionsession_01BRQ9Mp1yH7R1az6MaUM18H· 2026-10-13T10:16Z · holder of claim6142011298, closed by this landing. Thread-read:6142901763.38a2d3c76, an ancestor oforigin/main.os-zhuangAPPROVED on1b9e22281(review5501651056). That account is inGOVERNED_APPROVERS, and the approval is the record's acceptance criterion 1.Fixes #22146.Fixesline: 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 by docs(adr): ADR-0138 reads Accepted — criterion 2 names the in-repo anonymous sweep; criterion 4's back-pointers land in ADR-0090 and ADR-0056 #23219, ci(gates): every Lint and Type Check gate step runs after a red and each lane ends in one verdict step; Test Core continues past a failed package (#23177 ruling B, item A1) #23191 by ci(gates): every Lint and Type Check gate runs after a red and each lane ends in one verdict step; Test Core continues past a red package #23212, spec(page): no stated rule decides between two isDefault record pages for one object, so the renderer cannot honour isDefault without inventing precedence (the spec half of objectui#12195) #23015 by feat(spec,objectql,metadata-protocol)!: one default record page per object per deployment, a second refused at os validate, registration and save (#23015) #23206, [finding] IdentityObjectsPlugin registers sys_organization but never establishes the single posture's Default Organization (ADR-0131 D3), so a kernel booted without AuthPlugin refuses every system seed: 'the install holds no organization at all' #23189 by fix(plugin-auth)!: identity objects keep the single posture's Default Organization (ADR-0131 D3) #23203. Nothing closed without its own PR.completed. This act removespm:dispatched, anddomain:spec,area:access,priority:p1,securityandtarget:v18stay.6134095444;6136095148→ PR test(dogfood): the anonymous door sweep — ADR-0138 criterion 2 as an in-repo conformance test #23137 →8323cad97, landing6140706427;6142011298, report6142672477, ACCEPT6142722183, quick-read6142739511on the PR, then approved and armed6142901763.Execution cards, cut from the accepted record (its Execution plan: "The seat cuts these cards from the accepted record"). Each carries its table row verbatim, the decisions it enforces, and their Enforced by lines. Triage grades and routes them:
guestanchor's bindings (D3, D4) #23224. The record orders it first.pm:blockedwithBlocked-by: #23224. The record orders it after E1.sys_record_share'sguestrecipient (D8): ADR-0138 E4: retiresys_record_share'sguestrecipient (D8) #23228. Independent.The record's follow-ups F1–F3 are "named, not filed" by its own text. Each is filed when its stated trigger arrives.
Carried from the ACCEPT, one line each:
AUTHZ_CONFORMANCE: E2 measures whether the sweep is also its enforcer.Generated by Claude Code