Repository navigation
docs(adr): ADR-0138 the guest model (Proposed) — doors, grants channel, organization, ownership, and every guest key's fate - #22239
Conversation
…al, a closed list of doors, one grants channel, one organization rule, a fate per guest key Transcribes the maintainer's ruling (A on all eight questions, G2 on the measurement gap, with the Q4 and Q2 clarifications) into nine decisions, each with its contract and enforcement point, plus an enforcement map, acceptance criteria and the post-acceptance execution plan. D2b, the anonymous door x elevated flow combination, is presented as the record's own open decision on the four axes and is not decided here. Records the 2026-08-08 Option A ruling (f586f1a) as D9. Declares no metadata type (D5 is shape only) and changes no code: every package change is cut as a card after acceptance. Claude-Session: https://claude.ai/code/session_01LAi5BVvQNiYzepSAcsoFLK Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAi5BVvQNiYzepSAcsoFLK Co-authored-by: Claude <noreply@anthropic.com>
维护者速读
|
|
Pointer from the director seat (summon #35, The two letters the 速读 (6055701071) asked for are ruled, in chat, verbatim 「D1 A′ D2b R」, recorded as the ruling supplement 6056614963 on #22146:
One revision round on this PR before approval: D1, D5, D2b, the E2/E3 rows, the Consequences paragraph on owner-assigning hooks (withdrawn), and the "What the ruling did not settle" list (D2b closed). Then the seat re-posts the 速读 and the maintainer approves. Thread-read: 6055701071. Generated by Claude Code |
Claude-Session: https://claude.ai/code/session_01LAi5BVvQNiYzepSAcsoFLK Co-authored-by: Claude <noreply@anthropic.com>
…D2b R (publish refusal, both directions) Applies the ruling supplement 6056614963 (maintainer 「D1 A′ D2b R」): D1 keeps the invariant (the guest is a principal and never an owner, a forged owner is refused, attribution is not ownership) and drops the organization-level default owner; ownership of a guest-written row is the business scenario's own metadata and the empty state is the owner left unset, with no publish refusal. D5's shape loses its default-owner element. D2b is closed as R, a publish refusal with a prescription in both directions; the four-axis table stays as its reasoning, and M and the first-drafted default owner move to Alternatives considered. E2 carries R with its registered ADR-0087 semantic entry; E3 shrinks to the stamp's guest branch with no new key; acceptance criterion 3 is met. Claude-Session: https://claude.ai/code/session_01LAi5BVvQNiYzepSAcsoFLK Co-authored-by: Claude <noreply@anthropic.com>
维护者速读(修订版)
|
Part of #22146
Clause-②: no
What this PR is
Round 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 ruling6054113537(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 supplement6056614963(maintainer 「D1 A′ D2b R」): D1 is revised to A′, and D2b is closed as R.packages/**,content/docs/**orskills/**. The ruling places every code change after acceptance, as execution cards E1 to E4 (the record's Execution plan).docs/adr/**, so this PR stays draft and lands only by the maintainer's hand.Part of.What the record says, one line per decision
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).D2, the closed list of doors. Exactly five door classes serve an unauthenticated request:
authRequired: falseendpoints of typeobject_operation;authRequired: falseendpoints of typeflow.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.D2b, anonymous door × elevated flow (R). Publish refuses an
authRequired: falseflow endpoint whose target declaresrunAs: 'system', in both directions, with a prescription: an authenticated endpoint, the signed inbound-hook channel, a public form, andrunAs: '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.D3, the grants channel (ADR-0090 D9, enforced). The
guestanchor'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, noteveryone, 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.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.
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.
D6 and D7. ADR-0106 D7, explain's
EXTERNALfloor 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.D8, guest keys. Every declared guest key gets a fate and an ADR-0087 disposition.
sys_record_share'sguestrecipient is to be removed on its own card, E4. The basis is ADR-0090 D11 and the already-registeredsharing-rule-recipient-reconcileentry.guest_portalset name was not on the card's list. It is recorded too, with the fate keep.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 ofassemble-execution-context.ts.What the revision changed (supplement
6056614963)What stays open
How the number was chosen: 0138
origin/mainat73a0a6bf1d, after this round's merge, tops out at 0137, and 0136 is absent. Still no open PR adds 0136 or 0138; docs(adr): ADR-0048 §3.4 narrowed — positions, permission sets and capabilities hold one name per deployment #22198, the one ADR PR at the first count, has landed on ADR-0048.--write: it applies the edits it can prove, you apply the rest #22142 (104 files) and chore: version packages #21988 (229 files). Only docs(adr): ADR-0048 §3.4 narrowed — positions, permission sets and capabilities hold one name per deployment #22198 touchesdocs/adr/, and it touches0048-cross-package-metadata-collision.md. No open PR adds 0136 or 0138.0136-declared-journeys-as-priority-anchor.md, and spec: hold a predicate to what the engine can run; declare its fault semantics (ADR-0136) #18985 renumbered its own record from 0136 to 0137 because of it.scripts/check-adr-anchors.mjscomputes the next free number as the highest number plus one, which gives 0138.Back-pointers: none in this PR, by house practice
Verification at
2d69b2b714(the revision, on a merge of main73a0a6bf1d)I ran every command in the claim-time gate list at the revision head. Each exit code was captured before any pipe.
node scripts/check-adr-links.mjs(and--self-test)node scripts/check-adr-symbol-anchors.mjs(and--self-test)node scripts/check-ci-filter-parity.mjsnode scripts/check-closing-keyword-parity.mjs(and--self-test)node scripts/check-comment-mask-corpus.mjspnpm --filter @objectstack/lint run check:doc-formula-expressionspnpm check:adr-anchorspnpm check:cross-package-test-inputspnpm check:doc-authoringpnpm check:driver-memory-censuspnpm check:gitlink-declaredpnpm check:nul-bytespnpm check:pm-governed-mergespnpm check:pm-prior-rulingspnpm check:refd-timer-probepnpm check:watch-hint-literalcheck-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.check:pm-prior-rulings. The self-test passes 155 cases. The tool's--card 22146read returns 16 ADR decision hits and 1 ruling on the thread (6054113537).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/formulaand@objectstack/lintclosure under the verify lock after this round's merge (VERDICT command-exit 0), and the gate exited 0.dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat2d69b2b714gives the same 19 families. The--ranreconciliation 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 base73a0a6bf1.Not measured, and named
Changeset
I read the Check Changeset job in
pr-automation.yml. It has two exemptions: theskip-changesetlabel (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/*.mdand changes none, anddocs/adr/**ships in no package'sfiles[]. So the label isskip-changeset.Acceptance notes
RestServer.registerFormEndpointsinpackages/rest/src/rest-server.tsstill says that security is delegated to aguest_portalset carried on the context, and that the middleware falls open when none is registered. What admits a form submission today is thepublicFormGrantbranch inSecurityPlugin.维护者速读(草稿)
runAs的说明写出"匿名入口 + 系统身份流程"时,发布就会被拦下。Generated by Claude Code