Skip to content

docs(adr): ADR-0138 D2 gains class 6, the acl public_read file download; the G2 result recorded (still Proposed) - #22433

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-22146-d2-public-read-class
Oct 9, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-22146-d2-public-read-class

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #22146
Clause-②: no

What this PR is

Round 4 of #22146: a record revision of docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md under the G2 ruling 6074960686 (batch #300 item 1, letter A, maintainer 「同意」). Status stays Proposed.

What the revision records

D2 gains class 6, the acl: 'public_read' file download (ADR-0104 D3 wave 2). The row uses the table's existing columns:

  • What admits the request: the file's own acl: 'public_read' (packages/services/service-storage/src/objects/system-file.object.ts#acl). It is an explicit opt-in, because the upload routes create every file as private.
  • What it runs as: a read of that one file's bytes, nothing else. No principal is evaluated.
  • Enforced by: packages/services/service-storage/src/storage-routes.ts#registerStorageRoutes, at the download gate authorizeDownload, which both download routes call.
    • I read this on origin/main e02833c240: a file marked public_read skips the parent-record authorizer and gets the stable URL.
    • authorizeDownload is a closure inside registerStorageRoutes. So the row anchors the function and names the gate as a same-line continuation anchor, and both resolve as declarations.

The adoption rule now reads: classes 4 and 5 take the guest envelope; classes 1 to 3 and 6 take their own declaration; nothing else serves a guest.

The G2 result and the ruling, at class level only. The full record is under acceptance criterion 2, with short pointers in the Status line, D2 and the enforcement map.

Consistency edits that the ruling forced:

  • Status line. It now says the G2 sweep was measured and not met. "⛔ Nothing in packages/** … changes before acceptance" is now "no change this record decides …", followed by one sentence explaining why the three defect cards land first: they enforce ADR-0056 D2 and ADR-0104, which are both Accepted.
  • Front matter:
    • Decided by gains the G2 ruling.
    • Builds on gains ADR-0104 D3 wave 2.
    • Leaves unchanged gains ADR-0104.
    • Consumers gains @objectstack/service-storage.
    • Card now mentions the G2 sweep and this revision.
  • TL;DR: a note under the verbatim ruling summary.
  • Context: the G2 bullet points to criterion 2.
  • D2: a short "Measured, and not yet held" paragraph.
  • Enforcement map: the anonymous default-deny row's status names the three G2 exceptions.
  • Consequences:
    • a new bullet: class 6 changes nothing that serves today;
    • the "Not measured" bullet now counts G2 as measured.
  • Alternatives considered: gains G2-result B and C, in the ruling's words.
  • Criterion 4: "No line is added to …" gains ADR-0104.
  • Execution plan: a sentence saying the three defect cards are not execution cards.
  • References: gain the G2 report, its review and the ruling, and the three cards.

Every count and class-number site

Line numbers are base e02833c240 (identical to 27a8b33dec for this file) → head 024872cb90.

Site Before After
TL;DR decision table, D2 row (:63 → :77) five door classes six door classes; the Ruled cell adds "class 6 added by the G2 ruling"
D2 intro (:215 → :238) exactly these five door classes six; the Ruled line names the G2 ruling
D2 table five rows 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
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) a closed list of five door classes six. This changes only the count inside the quoted text; the back-pointer itself is not landed
E2 (:607 → :682) the five classes recorded in the conformance matrix six

Sites deliberately left as they are:

  • The first ruling's summary (:74 → :88). It says "the closed list of five door classes", and it is a verbatim quote of 6054113537, so 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.
  • Sites whose numbers did not move. Class 6 is appended, so classes 1 to 5 keep their numbers:
    • D1 point 1 "(D2, classes 4 and 5)" (:207);
    • D3 point 1, the same phrase (:330);
    • D8 "D2 class 1" (:449);
    • D2b "D2's class 5 row" (:504);
    • E1 "denied on class 4" (:681).
  • "except at the two endpoint classes above" (:254). It is still two: class 6 takes no guest envelope.
  • The Context table "re-measured at 7b926f7600" (:122) is a dated reading, so it stays as measured.
  • Alternatives, G1 ("every door class reads as a denial or a narrow door") is the reasoning recorded for a letter that was not taken.
  • Criterion 1 is left as the record has it. The landing comment 6058198880 read it as met by the approval on PR docs(adr): ADR-0138 the guest model (Proposed) — doors, grants channel, organization, ownership, and every guest key's fate #22239. This revision is itself a Tier H approval, so whether to mark it met is the seat's call.

Verification at 024872cb90

I derived the gate list with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack from the worktree. The change set is 1 path, +121 / −37 against merge base e02833c24, and the tool gave 19 commands. I ran every one at 024872cb90 and captured each exit code before any pipe:

Command Exit
node scripts/check-adr-links.mjs (and --self-test) 0, 0
node scripts/check-adr-symbol-anchors.mjs (and --self-test) 0, 0
node scripts/check-ci-filter-parity.mjs 0
node scripts/check-closing-keyword-parity.mjs (and --self-test) 0, 0
node scripts/check-comment-mask-corpus.mjs 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions 0 (see below)
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

Changeset

None. docs/adr/** ships in no package's files[], and this PR adds or changes no .changeset/*.md. The label is skip-changeset, the opt-out that the Check Changeset job in pr-automation.yml reads.

Acceptance notes

维护者速读(草稿)


Generated by Claude Code

…ad; the G2 result recorded (still Proposed)

Record revision under the G2 ruling (letter A): D2's closed list names the
ADR-0104 opt-in file download as a sixth door class, with its admission,
what it runs as and its enforcer. Every count of the door classes, the
adoption rule, the enforcement map, acceptance criterion 2, the quoted
ADR-0056 back-pointer text and E2 follow. The G2 sweep's class-level
result is recorded: classes 1 to 5 match, the everything-else class does
not (three families, named at function level), and acceptance waits on
their three defect cards and a re-measurement. Status stays Proposed.

Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 024872cb90d3443ba803d939e084ee022d1fddb7
Local-runs: none

PR #22433 (draft, base main, merge base e02833c240), card #22146, under the G2 ruling 6074960686 (batch #300 item 1, letter A, 「同意」). Inputs: the card body and all 19 comments, the PR body and file list, the net diff origin/main...024872cb90 (one file, docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md, +121 / −37), and the check-runs on the head, read at 2026-10-09T06:59Z. Every path#Symbol below was read with git show origin/main:PATH; nothing was built, run or re-run. The dev report 6076046095 was read as claims.

① Derived judgments

No accept set, public surface, export or stored shape moves: the diff is one docs/adr/** record, Status stays Proposed, and no packages/**, content/docs/** or skills/** path is touched. The judgments are on the record's decisions as rule text.

  • Class 6 — right. The ruling: "ADR-0138 D2's closed list gains a sixth class, the public_read file download". The row's three cells hold on origin/main. packages/services/service-storage/src/objects/system-file.object.ts#acl is the Field.select with private / public_read (:78). Both upload creation sites in storage-routes.ts write acl: 'private' (:696, :824), so "an explicit opt-in" is right. authorizeDownload is a closure declared inside export function registerStorageRoutes (:308, :358); it returns the presigned TTL on file.acl === 'public_read' before opts.authorizeFileRead is consulted, so "no principal is evaluated" is right; both download routes, GET /files/:fileId/url (:1215) and GET /files/:fileId (:1262), call it. The gate's own header names ADR-0104 D3 wave 2, and ADR-0104's ### D3 wave 2 reads "the anonymous capability URL demoted to an opt-in acl: 'public_read'", so the citation and the Builds-on link both resolve. The continuation spelling (path#registerStorageRoutes, then backticked #authorizeDownload) is a form check-adr-symbol-anchors.mjs declares in its own self-test.
  • The adoption rule — right. "classes 4 and 5 take the guest envelope; classes 1 to 3 and 6 take their own declaration" closes D9's rule the way the code reads: class 6 evaluates no principal and takes no guest envelope. "except at the two endpoint classes above" (:254) is still two. Not a new decision.
  • The G2 result and what remains — right, at class level. Criterion 2's sub-bullets match the report 6074331185 and the seat's table 6074404262 (classes 1 to 5 match; class 4 denied with no grant bound as D3's empty state; class 5 admission and narrowness, D2b unbuilt). The three families are the ruling's three, in its words, at function level, and the three cards map right: runtime: the /i18n dispatcher domain answers an anonymous caller — handleI18nRequest makes no shouldDenyAnonymous call, unlike every other dispatcher domain (ADR-0056 D2); with an objectui companion for the Console sign-in page #22432 the /i18n domain with the 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, while acl: 'public_read' stays anonymous (ADR-0104) #22431 storage download of a file with no scope and no field owner with public_read staying anonymous — each an open issue whose title says the same. The ruling's acceptance sentence is quoted verbatim, checked word for word against 6074960686; so are the B and C sentences under Alternatives. The anchors resolve: packages/runtime/src/domains/i18n.ts#handleI18nRequest (export async function, :35; shouldDenyAnonymous occurs 0 times in that file) and packages/rest/src/rest-server.ts#RestServer.registerOpenApiEndpoints (a private method of export class RestServer, :5095).
  • The Status line — right. The ruling orders the three cards first ("When the three cards have landed … the acceptance PR opens … ADR-0138 stays Proposed until then"), so the old "⛔ Nothing in packages/** … before acceptance" could not stand. "No change this record decides lands … before acceptance", plus the sentence that the cards enforce ADR-0056 D2 and ADR-0104, is the ruling's order and nothing more; both records read Accepted on origin/main. The matching sentence under the Execution plan says the same.
  • Every count and class-number site — consistent. Swept the head: every door-class count reads six (TL;DR D2 row :77, D2 intro :238, the Enforced-by paragraph :274, the enforcement map :519, criterion 2 :624, the quoted ADR-0056 back-pointer :665, E2 :682). The one remaining "five" (:88) is the verbatim summary of ruling 6054113537, which does say "the closed list of five door classes"; it is annotated at :101 to :106, and editing a quoted ruling would have been the wrong move. Classes 1 to 5 keep their numbers (:207, :330, :449, :504, :681). The record is byte-identical at the dispatch's 27a8b33dec and the merge base e02833c240, and none of the five commits between them touches the record or the three families' files.
  • Front matter and references — right. Decided-by names the G2 ruling with its batch, letter and 「同意」. Leaves-unchanged gains ADR-0104, and criterion 4's "No line is added to" gains it too (consistent: D2 names the door, ADR-0104 is not amended). Consumers gains @objectstack/service-storage, the package's name on origin/main. The enforcement map's default-deny row now states its measured exception, which ADR-0049 asks of a declared row. References gain the report, the review and the ruling, and the three cards. The pre-existing D2 anchors (isPublicAudienceRead, resolveExecutionContext, AUTHZ_CONFORMANCE) still declare.
  • Readings kept private — right. The record names domains, functions and classes; no route beyond the ruling's own /i18n domain name, no size, no payload, no request shape. The card body's "classes, positions and functions only" holds.
  • One observation, no action in this PR. "unlike every other dispatcher domain" (criterion 2) is the ruling's premise and runtime: the /i18n dispatcher domain answers an anonymous caller — handleI18nRequest makes no shouldDenyAnonymous call, unlike every other dispatcher domain (ADR-0056 D2); with an objectui companion for the Console sign-in page #22432's title, carried verbatim; the ruling itself enumerated the eight domains that call shouldDenyAnonymous (actions, ai, analytics, auth, automation, meta, packages, security). A literal file census on origin/main finds further domain files without a direct call (data, keys, mcp, notifications, share-links, ui, unavailable, activation-gate), some denied through another seam (the REST enforceAuth seam; share-links is class 2 itself). The sentence binds nothing: E2's domain-level deny and the conformance matrix are the rule, and E2 is where the per-domain count gets proven. Not a widening.

Check-runs on the head at the read time above: 35 runs, none failed. Lint & Repo Gates (the job that carries check-adr-symbol-anchors and check-adr-links) and the Test Core aggregate were in_progress; Check Changeset, Governed Surface Queue Guard, Check Documentation Links, Check PR Size, the four Type Check jobs and the consumer and source gates, Test Core shards 1 to 6, and Dogfood Regression Gate were success; the rest skipped by the path filter. Not awaited. The dev's "2232 anchors across 141 records resolve" is a claim; the six new anchors were read here directly.

② Semver level

Clause-②: no, no arm, and the skip-changeset label — right. The one changed path is under docs/adr/**, which ships in no package's files[]; nothing an author can write, import or call moves; the record stays Proposed, so not even ADR-0138's own decisions take effect. Check Changeset on the head: success. A record revision that publishes nothing takes no changeset.

③ Boundary flags

Dev flags (deviations in 6076046095):

  1. The Status-line rewording — answered, right: forced by the ruling's order (①).
  2. Consistency edits beyond the dispatch's lead list — answered, right: each was read in the diff (Decided by, Builds on, Leaves unchanged, Consumers, Card, the TL;DR note, the Context pointer, the D2 "Measured, and not yet held" paragraph, the enforcement-map status, two Consequences bullets, Alternatives B and C, criterion 4's list, the Execution-plan sentence, References); every one is a count, pointer or reference consequence of class 6 or the G2 result, and none adds a decision.
  3. The :74 ruling summary left at "five" — answered, right: a verbatim quotation of 6054113537 is kept as ruled; the annotation under it is the remedy.
  4. check-issue-citations vacuous on docs/adr/** — answered: a process note; the three citations were read here by REST (open issues, not PRs, titles matching the record's mapping).
  5. Worktree base e02833c240 against the dispatch's 27a8b33dec — answered: verified identical for the record; no intervening commit touches the three families' files.
  6. Attribution — answered, right: the commit carries the model-free trailer pair AGENTS.md names, and the PR body ends with the session-URL footer.
  7. Cleanup — answered: the remote branch head equals the PR head.

open_questions:

  1. Re-measure class 6 beside the everything-else class? — answered, A, and the record is right to change nothing: criterion 2 as revised already requires each of six classes to match, so a re-measurement reads class 6 by the criterion's own text, while the ruling's sentence names the everything-else class as the condition for opening the acceptance PR. The seat carries class 6 into the re-measurement dispatch (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, while acl: 'public_read' stays anonymous (ADR-0104) #22431 edits authorizeDownload, the gate class 6 passes through). No ruling needed.
  2. Criterion 1's wording — answered, A: leave it. The landing comment 6058198880 read criterion 1 as met by the approval on docs(adr): ADR-0138 the guest model (Proposed) — doors, grants channel, organization, ownership, and every guest key's fate #22239; this revision is a further Tier H approval; the accepting PR states all four met together. Marking it here is outside the round's file surface as claimed in 6075714215 (the D2 revision only). No ruling needed.

out_of_scope_findings (the public_read opt-in has no in-repo producer): a note that names its evidence and its carrier (#22431, whose acceptance already measures the producers of unclaimed uploads) — a Prime Directive #10 note, not a defect of this PR. Nothing escalated.

Landing: Tier H. This record is the at-tier contract review the claim 6075714215 says is owed before the maintainer's approval is asked for; it does not lift the landing — docs/adr/** lands only on an authorized APPROVED review (Prime Directive #14). The PR stays draft; no seat merges, queues or arms auto-merge.

Implemented-by: claude/issue-22146-d2-public-read-class
Reviewed-by: session_01VZqqwTj2wsihZEbfT6yyYN

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

domain:spec seat 1 (#6017) · os-tesla · session session_01VZqqwTj2wsihZEbfT6yyYN · 2026-10-09T07:08Z。待批的是 head 024872cb90。席位复核 ACCEPT 记录是 #22146 的 6076202284,at-tier 合约复审在本 head 上 PASS(6076189164),CI 35 项:24 通过,11 项按预期跳过(已核对跳过名单)。


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants