You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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
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
Blocked-by: #22432
Blocked-by: #22430
Blocked-by: #22431
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:
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
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: false endpoints of type flow, object_operation reads, anything else; every other surface answers 401.
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 EXTERNAL posture 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), the guest value of sys_audience_binding_suggestion, the two named context entries, the fallback permission set's guest reading.
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/spec before 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.
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
Blocked-by: #22432
Blocked-by: #22430
Blocked-by: #22431
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 anonymousobject_operationposture). The maintainer asked 「guest 是不是应该完整的重新设计并开adr」, then stated the demand 「最为一个元数据开发平台,guest 是常见的需求吧?」, and answered the seat's proposal (adomain:specdesign 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:specseat, 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:
origin/mainpackages/core/src/security/anonymous-deny.ts(#2567)!userId && !isSystem → 401decisionguestbuiltin position, held implicitly and exclusively by unauthenticated principals; theeveryone/guestaudience anchors packages suggest and never own; the human / agent / guest principal taxonomyguestanchor resolves nothing (#21158, closednot_plannedon 2026-10-04 for want of demand; its question is folded into this card)f586f1a89)assembleExecutionContext(fail-closed, 401) andassembleExecutionContextOrGuest(a first-class guest envelope), the latter adopted only by surfaces that genuinely serve anonymous principalspackages/core/src/security/assemble-execution-context.tsnear:30and:386, in no ADRauthRequired: falseendpoint must carry an armedrateLimit; webhook signature keys named as a future vocabulary, not promisedpackages/metadata-core/src/anonymous-form-intake.ts(#21967, #21980, #21331)object_operationpathundefined(packages/runtime/src/endpoint-executor.ts:95)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
guestuser 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 (theanonrole 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)
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.authRequired: falseendpoints of typeflow,object_operationreads, anything else; every other surface answers 401.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).EXTERNALposture stand unchanged.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
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.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.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