Skip to content

Commit 05c7c3f

Browse files
docs(adr): ADR-0138 D2 gains class 6, the acl public_read file download; the G2 result recorded (still Proposed) (#22433)
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`](#22146 (comment)) (batch #300 item 1, letter A, maintainer 「同意」). **Status stays Proposed.** - **One file, no code.** Nothing changes in `packages/**`, `content/docs/**`, `skills/**` or any other record. No back-pointer line is written into ADR-0090 or ADR-0056. - **Tier H.** The diff touches `docs/adr/**`, so this PR stays draft and lands only on the maintainer's approval. - **The card stays open**, so line 1 is `Part of`. What remains on #22146: the three defect cards, the re-measurement of the everything-else class, and the acceptance PR. ## 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. - **Classes 1 to 5 match** D2's table. - **The `public_read` download was served anonymously**, as ADR-0104 rules, while D2 did not yet name it. The ruling adds it as class 6. - **The everything-else class did not match.** Three families, named at function level as the three defect cards name them: - the `/i18n` dispatcher domain (`packages/runtime/src/domains/i18n.ts#handleI18nRequest` makes no `shouldDenyAnonymous` call); - the API-description endpoints (`packages/rest/src/rest-server.ts#RestServer.registerOpenApiEndpoints`); - storage download of a file with no scope and no field owner. - **Acceptance waits** on #22432, #22430 and #22431 and on a re-measurement. The record quotes the ruling's sentence on that verbatim. - No route, size, payload or request shape appears. The readings stay in the seat's container. **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 #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 | - **`check-adr-symbol-anchors`** reports "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 are `system-file.object.ts#acl`, `storage-routes.ts#registerStorageRoutes` twice, the `#authorizeDownload` continuation, `i18n.ts#handleI18nRequest` and `rest-server.ts#RestServer.registerOpenApiEndpoints`. All six resolve as declarations. - Negative control: a made-up `authorizeDownloadNope` reads as absent. - **`check-adr-links`** reports "715 relative link destination(s) under docs/adr/ resolve". - **`check:doc-formula-expressions`.** The first run exited 3 (PREREQUISITE NOT MET: `@objectstack/formula` not built in the new worktree), which measured nothing. I built the formula and lint closure under `os-verify-lock.sh` (`VERDICT command-exit 0`), and the rerun exited 0. - **`check:nul-bytes`** is OK. A separate control-byte grep on the record exited 1, meaning no hits. - **Reconciliation.** `dispatch-gates.mjs --ran` with the recorded exit codes reports "19 derived, 19 run, 0 NOT-MEASURED, 0 UNRUN". - **`check-issue-citations`** was named by the dispatch but is not in the derived set. I ran it anyway and it exited 0, but it read 0 files, because `docs/adr/**` is one of its deferred surfaces. So it measured nothing for this diff. Instead I checked by REST that #22430, #22431 and #22432 are open issues and not pull requests. - **CI:** in progress, not awaited. ## 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 - **The `public_read` opt-in has no in-repo producer.** - The upload routes in `registerStorageRoutes` create every `sys_file` row as `private`, and copy-on-claim copies the source row's `acl`. `FieldSchema` declares no file `acl`, so ADR-0104's "the field/file explicitly declares" exists today only on the file-row side, as the `sys_file.acl` select. - This is not a defect under ADR-0104, which rules an opt-in. But it bears on #22431: once an unclaimed upload, such as an organization logo, requires a signed-in caller, nothing in the repository marks that file `public_read`. - Carrier: #22431, whose acceptance already asks the dev to measure the in-repo producers of unclaimed uploads. Noted, not filed. ## 维护者速读(草稿) - **改了什么:** 按你对 G2 实测结果的裁决(「同意」,A),修订 ADR-0138 这份文档。ADR 仍然是 Proposed(提议中),本 PR 不改任何代码,也不改其他 ADR。 - D2 的"匿名可访问入口清单"从五类加到六类。第六类是文件被显式标成 `public_read` 后的匿名下载(头像、Logo 这类),这是 ADR-0104 早已裁定的做法,这里只是把它写进清单。 - 把 G2 实测结论按"类"写进文档:前五类都和文档一致;"其余一律拒绝匿名"这条不成立,有三处还对未登录的人开着。这三处只写到函数级:界面文案接口、API 文档接口、没有归属的上传文件下载。 - 写明 ADR 转为 Accepted 的前提:三张缺陷卡(#22432、#22430、#22431)落地后,重测"其余"一类,全部返回 401,再开验收 PR。 - **为什么改:** 实测证明"其余一律 401"今天还不是事实。你选择先关门、再验收,所以文档必须把 `public_read` 这扇合法的门写进清单,把还开着的三扇门和验收条件写清楚,不然下一次重测会把合法的门误判为漏洞,或者把漏洞当成已经关好。 - **风险与代价(含回滚):** - 合并后运行时行为不变,因为只改了一个文档文件。回滚就是还原这次修订。 - 第六类不改变任何现有行为:`public_read` 文件今天本来就能匿名下载,#22431 修复后仍然可以。 - 仓内目前没有任何代码会把文件标成 `public_read`(上传一律是 `private`)。#22431 收紧"无归属上传"之后,像组织 Logo 这类文件,如果没人手动标成 `public_read`,未登录时就看不到了。这一点交给 #22431 实测。 - **席位意见:** - **你要做的:** 席位复核之后,批准这份修订(Tier H,点 Approve 或亲手合并)。注意:批准的是这次修订,不是接受 ADR,ADR 仍保持 Proposed。 --- _Generated by [Claude Code](https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 46692c1 commit 05c7c3f

1 file changed

Lines changed: 121 additions & 37 deletions

File tree

0 commit comments

Comments
 (0)