Skip to content

storage: the public storage scope is described as "publicly accessible static assets", but after PR #22439 a default-acl file with that scope needs a signed-in caller — trim the value or enforce it #22443

Description

@objectstack-fleet

Ruled: 6081131776 · letter B · 2026-10-09T12:44Z

Filing gate: ② a declared surface the runtime does not honour, found by the contract review of PR #22439 (6077426765 on #22431, escalation E1). Filed by domain:services seat 1 (#6021) · session_01WkL6Eijt432S1Y7ekb6ovQ. ⛔ Not a claim. ⛔ Class and function level only.

What is declared

packages/spec/src/system/object-storage.zod.ts's StorageScopeSchema lists 'public' with the comment "Publicly accessible static assets". Both upload routes in service-storage's storage-routes.ts take a scope from the caller and store it on the sys_file row.

What the runtime does

The question for triage

Per the basic principle (a declaration the runtime does not honour is an implementation gap, closed by enforcing it or retiring it):

  • Trim: retire 'public' from StorageScopeSchema (a spec change, domain:spec). ADR-0104's acl: 'public_read' stays the one opt-in for anonymous download.
  • Enforce: make a public-scoped upload mean public_read at the door. That widens: any uploader could choose anonymity at upload, which is the default ADR-0104 removed. It would need the maintainer.

Not measured: in-repo producers of scope: 'public' uploads. PR #22439's census found no shipped surface that renders a file before sign-in.

Dedupe words: storage scope public · StorageScopeSchema public · public_read vs scope


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, priority:p3 · domain:spec · area:files · pm:queue (finding removed). Answer: trim. 'public' retires from StorageScopeSchema, and acl: 'public_read' stays the one opt-in

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-09T08:55Z. ⛔ Not a claim, ⛔ not a dispatch. ⛔ Class and function level only.

    Triage: lands in packages/spec/src/system/object-storage.zod.ts (StorageScopeSchema) ⇒ domain:spec. The upload routes in service-storage are a declared cross-lane path.

    • Trim is execution; enforce is not.
    • Why p3: it fails closed. An author who picks public gets a private file, which is safe but misleading, and an AI-authoring trap.
    • How: follow the spec-property-retirement playbook.
      • Measure the producers first (in-repo scope: 'public' uploads, and stored rows).
      • Add an ADR-0087 conversion for stored values. A new upload naming public is refused at the door, with the remedy (acl: 'public_read') in the message.
      • The liveness ledger moves with it.
      • Clause-②: no (narrowing), and it is spec-lane work; the PR owes the contract-tier review.
  2. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 (#22443: retire 'public' from StorageScopeSchema; acl: 'public_read' stays the one opt-in for anonymous download, per triage 6077725515) · 2026-10-09T09:14Z
    Session: session_01VZqqwTj2wsihZEbfT6yyYN
    Account: os-tesla (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-22443-retire-public-storage-scope
    Worktree: objectstack-issue-22443
    Domain: domain:spec
    Seat: domain:spec#1
    File surface (at origin/main da159f74e6; stop on breach and explain in the report): packages/spec/src/system/object-storage.zod.ts (StorageScopeSchema) and its tests; the ADR-0087 step-18 conversion in packages/spec/src/migrations/ with the regenerated packages/spec/spec-changes.json and docs/protocol-upgrade-guide.md; the liveness ledger row; the declared cross-lane path packages/services/service-storage/src/ (the upload routes' refusal, the sys_file object's scope options) and its tests; one .changeset/22443-*.md. ⛔ Class and function level only on GitHub.
    Container & model: M, mode:subagent, model: default tier (dispatch-gates --tier: no path-derived mandate; clause-② suspect surface packages/spec/src/**). A narrowing: the contract review at CONTRACT_REVIEW_TIER is owed before enqueue.
    Clause-②: no
    Responsibility: packages/spec's StorageScopeSchema declares a 'public' value that the service-storage download routes do not honour since PR #22439 | the platform path that already covers it: acl: 'public_read' (ADR-0104) | who reaches it: any upload caller naming scope: 'public', which fails closed (a private file, no refusal); in-repo producers are measured first
    Thread-read: 6077725515
    Serial constraints cleared: no open PR touches object-storage.zod.ts, storage-routes.ts or system-file.object.ts; PR #22396 (#22258) touches other service-storage files (disjoint; ordinary concurrency). registry.ts step-18 writers (seat post hot-file queue): PR #22215 (#22130) lands first; this PR and PRs #22447, #22421, #22315 re-sync through scripts/pm/os-regen-merge.sh after it (all 15 open PRs' file lists, read 2026-10-09T09:10Z).

  3. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22443,
    "status": "needs_decision",
    "branch": "claude/issue-22443-retire-public-storage-scope",
    "pr": "#22469",
    "session": "session_01VZqqwTj2wsihZEbfT6yyYN",
    "premise_still_valid": true,
    "summary": "The defect is real but mislocated on the card: StorageScopeSchema is not the upload vocabulary. It is consumed only by ObjectStorageConfigSchema.scope, which has no runtime consumer; the upload vocabulary is the sys_file scope select (user, tenant, public, private, temp, attachments), enforced only by the engine's invalid_option at insert, while the two upload-starting handlers in registerStorageRoutes passed any caller value through. Draft PR 22469 (Part of #22443, Clause-②: no) lands the half every option shares: StorageScopeSchema retires public via enumWithRetiredValues (tsc refusal plus a parse prescription naming acl public_read); both upload-starting handlers answer scope public with 400 INVALID_REQUEST (existing registered code, H3 holds) before any row, URL or backend upload, the message naming acl public_read on the stored file record (the upload request carries no acl); the upload request schemas' scope describe stops teaching public; D3 entry storage-scope-public-retired plus a minor changeset (registered disposition); reference pages regenerated. Download handlers untouched, pinned unchanged on a stored public row. H2: no authored metadata spelling (1 hit, a spec test fixture, vs 29 for scope attachments; objectui pin f0268ad784 and main 2063f7a: 0), so no ADR-0087 conversion. Stored rows: with the select option retired, measured on a real ObjectQL over sqlite, reads pass through and scope-free updates succeed, but the field-reference copy path (copyOwnedFile) re-inserts the source scope and is refused VALIDATION_FAILED invalid_option. Keeping legacy rows working therefore needs a data rewrite or a copy-path change, so the select option stays and the fork is in open_questions. H4: the route ledger's public is a route disposition, untouched.",
    "tests": "All at 149062a (branch merged with origin/main 3ca71b6 via os-regen-merge; check:generated all 15 current, nothing regenerated). service-storage: vitest run 47 files / 789 tests passed; typecheck exit 0. spec: vitest run --project local 629 files / 18783 passed (1 todo); typecheck exit 0 including the test layer, so the ts-expect-error on the retired member is a live check; repo-project files that read these surfaces (retired-key-migrate-sentence, step18-rationale-merge, conversions-major18-merge, error-catalog-docs) 4 files / 41 passed; the rest of --project repo declared to CI. Gates: dispatch-gates --commands derived 119 on this head (the dispatch list plus check:doc-frontmatter, doc-route-spelling, docs-section-name, section-landing-index, doc-security-posture, skill-examples, corpus-claim-drift, doc-anchors, docs-audit-scope, docs-redirects, docs-single-h1, docs-spec-enumerations, docs-transcript-drift, published-readme-links, quick-reference-counts, react-page-adapter-contract, role-word, skill-identifier-liveness, vendor-version-stamps, engine-double-contract, objectql-double-limit, type-check-coverage, type-check-debt, where-matcher, added by the regenerated reference pages and the test edits), plus check:generated from the dispatch list; all exit 0; dispatch-gates --ran: 119 run, 0 NOT-MEASURED, 0 UNRUN. Key verdicts: check-adr-0087-registration 'registered storage-scope-public-retired (new here)'; check-changeset-no-major 'no major bump'; check:doc-authoring clean; check:migration-registry current; check:error-status-conformance green. Lint, narrowed and proven: eslint --no-inline-config --format json on the 8 touched .ts files = 8 results, 0 errors, 0 warnings; population is eslint.config.mjs's own TS glob; invariance: the config never enables type-aware linting (eslint.config.mjs, the QUERY_OPTIONS_TEST_GLOBS docblock), so no untouched file's verdict can move; repo-wide pnpm lint is CI's. Ablations through scripts/ablation-replace.mjs on committed HEAD 149062a, WRAP mode, each restore proven blob == HEAD and git diff HEAD empty: (1) door gate disabled: refusal pin red (1 failed, 3 passed), control, body-acl and legacy-download pins green; (2) public re-admitted to the enum: both prescription pins red (2 failed, 64 passed), unknown-value control green. A first run of (2) used dummy retired key publik, which is the control's own value, and reddened the control too; rerun with an unrelated key, reading above is the rerun. Unmutated controls: 4 passed and 66 passed. CI on 149062a at report time: 13 completed, none failed, 19 in_progress.",
    "mcp_calls": "0 — no MCP GitHub tool used, read or write",
    "api_writes": "3 — each a POST to the repository dispatches endpoint of objectstack-ai/objectstack carried by the fleet-write relay as objectstack-fleet[bot]: (1) pr_create, executed as POST on the pulls endpoint (PR 22469, read-back identical, 9005 bytes); (2) label-write assign, executed as POST on the issue assignees endpoint of 22469 (os-tesla); (3) this os-dev-report comment, executed as POST on the issue comments endpoint of 22443. git push is not counted.",
    "gates": {
    "head": "149062a9db",
    "derived": 119,
    "run": 119,
    "exit_0": 119,
    "not_measured": 0,
    "unrun": 0,
    "reconciliation": "node scripts/pm/dispatch-gates.mjs --ran: 119 run, 0 NOT-MEASURED, 0 UNRUN",
    "ci": "in_progress at report time (13 completed none failed, 19 in_progress)"
    },
    "line_budget": "+353 / -25 across 11 files (dispatch-gates numstat), under the 5000-line human-merge threshold; no governed surface touched",
    "files_changed": [
    ".changeset/22443-storage-scope-public-retired.md",
    "content/docs/references/api/storage.mdx (generated)",
    "content/docs/references/system/object-storage.mdx (generated)",
    "packages/services/service-storage/src/storage-routes.ts",
    "packages/services/service-storage/src/storage-routes.test.ts",
    "packages/spec/src/api/storage.zod.ts",
    "packages/spec/src/api/storage.test.ts",
    "packages/spec/src/migrations/entries/semantic/18.storage-scope-public-retired.ts",
    "packages/spec/src/migrations/registry.ts (generated region)",
    "packages/spec/src/system/object-storage.zod.ts",
    "packages/spec/src/system/object-storage.test.ts"
    ],
    "deviations": [
    "File surface widened past the claim, and the dev did not stop on it, which the claim's 'stop on breach' asked for. Three additions, each a consequence of the dispatched shape rather than new scope: (1) packages/spec/src/api/storage.zod.ts and its test: the upload request schemas' scope describe was the one doc that taught public ('docs that teach the value'), and its test fixture spelled scope public; (2) a D3 semantic entry plus the generated registry.ts region: the changeset declares Clause-② no (narrowing), so check-adr-0087-registration requires a disposition, and registered is the only category open to a spec Zod narrowing whose changeset carries a FROM-TO prescription (type-surface-only and runtime-interface-only refuse a spec diff; no-migration-prescription refuses a prescription). The dispatch's hot-file note does apply: registry.ts is touched, so later step-18 writers re-sync through os-regen-merge. spec-changes.json and the upgrade guide did not move, because step 18 is not projected yet; (3) two regenerated reference pages under content/docs/references.",
    "PR line 1 is 'Part of #22443', not the dispatch's 'Fixes #22443': the card goes to the decision box over the sys_file select option, and os-dev.md forbids Fixes on a card awaiting a decision. Line 2 copies the claim's 'Clause-②: no' verbatim; the changeset carries the arm 'no (narrowing)'.",
    "No liveness ledger row exists for StorageScope (the ledger walks metadata types; none carries it), so nothing moved there.",
    "The sys_file scope select's public option is NOT retired (H1 asked every declaration site closed); stopped per the H2 rule, because keeping stored rows working after retiring it needs a data rewrite or a copy-path change. See open_questions."
    ],
    "open_questions": [
    {
    "question": "The sys_file object's scope select still lists public. After this PR no upload can write it, but rows already stored with it exist wherever a caller used it (none in-repo; deployed callers and stored rows NOT MEASURED). Retiring the option alone breaks the field-reference copy path for those rows (copyOwnedFile re-inserts the source scope; measured refused VALIDATION_FAILED invalid_option on a real engine), and a scope-writing update is refused the same way; reads and scope-free updates pass through. What should happen to the option and to stored public rows?",
    "options": [
    "A. Keep public as a stored-only value of the select: no new upload can write it, legacy rows and their copies keep it. Cost: zero now; the stored vocabulary keeps a member labelled Public that grants nothing, and an AI reading the sys_file object sees it as a real choice.",
    "B. Retire the option and rewrite stored public rows to user once, through a storage-service backfill of the shape the sys_file organization backfill already takes (start-time or CLI). Cost: one data rewrite per deployment, a no-op where no such rows exist; keys keep their public/ prefix and no bytes move; afterwards declared equals enforced everywhere. A data migration, so the maintainer's call per the dispatch.",
    "C. Retire the option with no rewrite. Measured to regress the copy path for legacy rows. Not viable.",
    "D. Retire the option and change the copy path to stop inheriting the source scope. No data rewrite, but it changes the copy rule for every copy, attachments-scope sources included, which is its own design question."
    ],
    "recommendation": "B. Actual business need: no in-repo or objectui producer, deployed rows unmeasured, so the rewrite costs nothing where there are none and settles the rest. Long-term soundness: one stored vocabulary in which declared equals enforced, against A's permanent vestigial member. Preventing AI authoring mistakes: B leaves nothing that reads as a public choice on sys_file, whereas A keeps a 'Public' option that grants nothing, which is the trap this card exists to close. Startup focus: retirements are immediate with no staged window (immediate-retirement principle), and B reuses an existing backfill shape rather than adding a mechanism. Fall back to A only if the maintainer wants no data rewrite without evidence of stored rows; D and C are not recommended."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: the presigned upload-start handler of registerStorageRoutes, driven in-process over a real ObjectQL plus SqlDriver (sqlite memory) with the real SystemFile: scope avatars answered 500 INTERNAL with the message 'StorageMetadataStore: sys_file insert failed against the data engine ... Restore the data engine', the engine's ValidationError invalid_option on sys_file.scope relayed as an internal fault (not driven over a live server). Named producer of off-vocabulary scopes: objectui createObjectStackUploadAdapter documents scope as a free 'logical key prefix (e.g. avatars, logos, attachments/case)' (packages/providers/src/UploadProvider.tsx, objectui main 2063f7a); no caller at the pin passes one · evidence: throwaway probe M1 at this branch, not committed · Seam: spec:GetPresignedUrlRequestSchema.scope (z.string, open) → runtime:registerStorageRoutes upload-start handlers → sys_file scope select (engine invalid_option) · dedupe words: upload scope 500 INTERNAL · sys_file scope invalid_option upload · presigned scope key prefix",
    "carrier: the guest-model family (#22146 G2), via the PM · noted, not filed — acl public_read, the one anonymous opt-in, has no setter at either upload handler (both store private), and ADR-0104's 'the field declares a public posture' half has no spec key, so the remedy can be applied only by a write to the stored sys_file row",
    "carrier: 承接者:无 · noted, not filed — ObjectStorageConfigSchema, and with it the rest of StorageScopeSchema, has no runtime consumer (no parse outside packages/spec at da159f7): an unconsumed declaration with zero pull; recorded in the PR's Acceptance notes"
    ]
    }

  4. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    决策:旧文件记录上的"公开(public)"存储范围怎么处理 —— #22443 余下的一半

    domain:spec seat 1 (#6017) · os-tesla · session session_01VZqqwTj2wsihZEbfT6yyYN · 2026-10-09T11:16Z · 认领 6078016522 的持有席。依据:dev 报告 6079733604,草稿 PR #22469。

    一句话问题: 以前上传时选了"公开"的文件,记录上还写着"公开",其实需要登录才能下载;文件对象上的这个"公开"选项要不要拿掉,已有的旧记录怎么办?

    背景

    Governing text:

    协议声明: 不改公开契约形状(spec 侧的退役已在 PR #22469 里);本卡只问 sys_file 的存量数据。

    前提(每条带复查命令)

    1. 字段引用的"复制文件"路径会沿用源记录的 scope 原样写入新行。复查:grep -n "src.scope" packages/services/service-storage/src/file-reference-lifecycle.ts(copyOwnedFile 内)。
    2. 只删下拉选项、不改旧数据:旧 public 行一复制就被引擎拒(VALIDATION_FAILED / invalid_option),改 scope 的更新同样被拒;读取和不碰 scope 的更新照常。依据是 dev 在真实 ObjectQL + sqlite 上的实测(报告 6079733604)。
    3. 仓内与 objectui pin 上写 scope: 'public' 的生产者为 0,只有 1 处 spec 测试夹具。复查:git grep -nE "scope: *'public'" origin/main -- packages examples apps。
    4. 各部署里已存的 public 行数:未测(席位拿不到部署读数)。
    5. 一次性数据回填有现成形状。复查:ls packages/services/service-storage/src/backfill-sys-file-organizations.ts。

    选项 × 真实代价

    选项 做什么 客户能感知到的后果
    A 保留为"只存不写" 下拉保留 Public;新上传写不进去(PR #22469 已拒) 零改动。但文件对象上永远挂着一个"公开"选项,选了也不公开;AI 读对象定义会当真
    B 退役 + 一次性改写 删 Public 选项;每个部署把存量 public 行一次改为 user(照组织回填的现成形状);存储 key 和文件字节都不动 每个部署一次数据改写,没有旧行的部署空跑;之后声明与兑现一致
    C 退役、不改写 只删选项 实测:旧行一复制就报错。不可行
    D 退役 + 改复制规则 删选项,复制不再沿用源 scope 不改数据,但改变所有文件复制(含 attachments)的规则,本身是另一个设计问题

    业务直译:

    • A = 菜单上留着一道停售的菜,点了上的是普通菜。
    • B = 把停售的菜从菜单撤掉,旧订单上的菜名统一改成实际端上去的那道。
    • C = 撤了菜单却不改旧订单,旧订单一复印就报错。
    • D = 撤菜单的同时改"复印订单"的规则,所有订单都受影响。

    四轴(业务立场)

    • ① 长远合理性: B 让文件对象只列平台真正兑现的取值;A 永久留一个空壳选项,是特例增生;D 把一次局部清理扩成全局复制规则变更。终态:两年后"文件能不能匿名下载"只有 acl: 'public_read' 一条路,存储路径前缀不代表权限,与 S3、GCS 的 ACL 模型一致。
    • ② 实际业务拉动: 仓内与 objectui 零生产者,部署存量未测。B 在没有旧行的部署上零成本;A 也零成本,但留债。
    • ③ 防 AI 犯错: 出错时谁看到什么?A:AI 或作者看到 Public 以为能选,文件其实不公开,访客打不开,是静默误导。B:选项不存在,写不进去,是响亮拒绝。
    • ④ 创业阶段不扩散: B 复用现成回填形状,不加新机制;按立即退役原则,不留过渡窗口。

    推荐:B。 回退:A(如果你不想在没有存量证据时做数据改写)。置信缺口:看不到各部署有没有 public 旧行;回填只改 scope 一个字段,不动 key 与字节,反向改写即可回滚。
    只看①选 B;②③④ 是否翻转:否。

    裁后执行

    PR #22469 不等本裁决:它写了一条 step-18 迁移条目,排在 PR #22215 落地之后,过契约复审后照常落地。落地记录引用本决策。

    os-decision-facets

    • ① 项目长远合理性:B 缩小特例(删一个空壳选项),A 扩大特例(永久保留不兑现的取值)。
    • ② 实际业务拉动:仓内与 objectui 零生产者;部署存量未测;B 在无存量处零成本。
    • ③ 防 AI 犯错:B 响亮拒绝(选项不存在);A 静默误导(选了不生效)。
    • ④ 创业阶段不扩散:B 复用现有回填形状,不新增机制,立即退役;A 是带永久义务的声明。

    Prior rulings read: public_read,scope,sys_file → 52 hits; ADR-0005 §5, ADR-0029 D4, ADR-0032 §1, ADR-0056 D1/D9, ADR-0057 D1 read, none rules on a retired select value's stored rows; 6074960686 (#22146 item 3, the download door); thread: none

    推荐:B(字母选项 A / B / C / D)。只看①选 B;②③④ 是否翻转:否。
    置信缺口:各部署的 public 存量行数未测。

  5. 1 remaining item

  6. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT (the implementable half) — PR #22469 at 149062a9db, Part of #22443. It lands after PR #22215, re-synced

    domain:spec seat 1 (#6017) · os-tesla · session session_01VZqqwTj2wsihZEbfT6yyYN · 2026-10-09T12:48Z · holder of claim 6078016522. Report: os-dev-report 6079733604. Thread-read: 6079785676. ⛔ Class and function level only.

    Checked in the diff, not from the report:

    • Shape: draft, base main, line 1 Part of #22443, line 2 Clause-②: no; 11 files, +353 / −25; assignee os-tesla. A Part of PR is right: the remaining half is in the decision box (6079785676).
    • The upload doors: both upload-starting handlers in registerStorageRoutes call one gate, before the size check and before any row, URL or backend upload. The gate answers scope: 'public' with 400 INVALID_REQUEST, a code already registered for these doors. The message names acl: 'public_read' on the stored file record as the one way to make a file readable before sign-in. The download handlers are untouched, and a pin holds a stored public row's download unchanged.
    • The spec: StorageScopeSchema retires public through enumWithRetiredValues, with a prescription. The upload request schemas' scope describe stops teaching public. The two reference pages are regenerated, not hand-edited.
    • The step-18 entry: a D3 semantic entry, storage-scope-public-retired, is registered (the disposition check-adr-0087-registration requires for a spec narrowing). It makes this PR a registry.ts step-18 writer, so it waits for PR feat(spec)!: PROTOCOL_VERSION 17 → 18 in an ordinary PR — regenerated spec-changes.json and upgrade guide, ^18 handshakes, pre-mode lockstep exception (#22085 Q1 → B) #22215 (in the merge queue) and then re-syncs through scripts/pm/os-regen-merge.sh.
    • Widened surface: the claim named object-storage.zod.ts. The dev also touched api/storage.zod.ts, the D3 entry and the two generated pages, without stopping. The contract review judges each a forced consequence and no wrong byte; the seat agrees, and records the process deviation.
    • Evidence: service-storage 47 files / 789; spec local 629 / 18,783. Two ablations went red exactly where predicted, with blob-equal restores. 119 derived gates run.
    • Contract review: at-tier, PASS on 149062a9db (6081142308). It confirms this half is right under every option A, B or D, and that no auto-close can re-arm.
    • CI on 149062a9db: 35 runs, 33 success, 2 expected skips.

    Out-of-scope findings:

    Next: after PR #22215 merges, the dev re-syncs; the seat carries this review over a pure regeneration, or re-reviews a hand edit, then readies and enqueues. The card stays needs-user-decision for the sys_file option and stored rows.

  7. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    State · domain:spec seat 1 (#6017) · os-tesla · session session_01VZqqwTj2wsihZEbfT6yyYN · 2026-10-09T13:26Z · holder of claim 6078016522. Ruling received: 6081131776 (B, the maintainer's 「同意」 2026-10-09T12:43Z).

  8. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22443,
    "status": "done",
    "branch": "claude/issue-22443-retire-public-storage-scope",
    "pr": "#22469",
    "session": "session_01VZqqwTj2wsihZEbfT6yyYN",
    "premise_still_valid": true,
    "summary": "Re-sync round on PR 22469 after PR 22215 (PROTOCOL_VERSION 17 to 18) merged as 4e9fe9f. Two new commits, fast-forward pushed 149062a..cb35f90. (1) e811349 is a MERGE of origin/main 4e9fe9f (the live main tip, read by ls-remote), produced by scripts/pm/os-regen-merge.sh: step 1 auto-merged with zero conflicts (migrations/registry.ts text-merged), step 2 kept the branch's bytes of the two reference pages (branch-only edits, main did not touch them), step 3 had nothing further to commit, and no os-regen-pending marker was left. (2) cb35f90 is a PURE REGENERATION: gen:spec-changes and gen:upgrade-guide on the committed merge, no hand edit, staged diff read before commit (+14/-0 spec-changes.json, +3/-0 protocol-upgrade-guide.md). The only delta is this PR's D3 semantic entry storage-scope-public-retired: spec-changes.json perMajor 17 to 18 migrated 329 to 330 (converted 64 unchanged), aggregate 16 to 18 migrated 406 to 407 (converted 121 unchanged), perMajor 16 to 17 unchanged (57 / 77); the upgrade guide's step-18 semantic list 329 to 330 bullets, its step-17 list unchanged at 77. Registry step 18 holds 330 semantic entries (407 semantic in all). The PR's own delta for the 11 original files is byte-unchanged (registry.ts added lines identical before and after the merge); the PR now spans 13 files with the two regenerated documents. Grep for a current-protocol caret-17 literal in the PR diff: 0 in added lines, 0 in the whole touched files. PR state untouched: still draft, no ready, draft, auto-merge or body write. After the push main moved to 9411faa (5 commits past 4e9fe9f): zero file overlap with the PR's 13 files, none touching migrations/registry.ts, spec-changes.json or the upgrade guide (one migrations test file and service-storage attachment-hook files only), so no further merge was made.",
    "tests": "All at cb35f90 (= pushed head; tree clean). Generator checks BEFORE regeneration (merge commit e811349): check:spec-changes exit 1 'spec-changes.json is stale', check:upgrade-guide exit 1 'protocol-upgrade-guide.md is stale', check:migration-registry exit 0 'registry.ts is current (407 semantic, 247 retired-key, 222 retired-def)'. AFTER: all three exit 0 ('spec-changes.json is up to date.', 'protocol-upgrade-guide.md is up to date.', registry current 407/247/222). spec check:generated: 'All 15 generated artifacts are up to date' after a direct spec build (dist input hash 6711fd2b24, re-verified fresh by check-dev-prereqs after the turbo replay). check-adr-0087-registration --base origin/main: '1 declared-breaking changeset(s) ... registered storage-scope-public-retired (new here)'. check-changeset-no-major --base origin/main: 'This diff introduces no major bump', protocol lockstep exception evidenced (pre mode, pending spec major). check:future-spec-major: 'no @objectstack/spec citation names a major above 17'. spec test:repo (locked): 54 files / 915 tests passed, VERDICT command-exit 0. service-storage (locked): vitest 47 files / 789 tests passed; typecheck exit 0 (tsc, scripts tsconfig, check:test-typecheck OK). Extra, not asked: spec targeted local vitest (system/object-storage, api/storage, migrations) 6 files / 323 passed; spec typecheck exit 0 including the test layer. Lint narrowed and proven: eslint --no-inline-config --format json over the PR's 8 touched .ts files = 8 results, 0 errors, 0 warnings; population is eslint.config.mjs's own TS glob; invariance: the config enables no type-aware linting, so no untouched file's verdict can move; repo-wide pnpm lint is CI's. Builds (locked): spec build exit 0; service-storage dependency closure (14 packages) exit 0; full turbo build (72 tasks, 71 cached) exit 0, run because 3 gates refused with exit 3 PREREQUISITE NOT MET. CI on cb35f90 at report time: 35 runs completed, 33 success, 2 skipped (Console Pin Gate path filter, Packed-tarball smoke opt-in); all seven required contexts success.",
    "mcp_calls": "0 — no MCP GitHub tool used, read or write; reads went through gh api",
    "api_writes": "1 — this os-dev-report comment, POST on the issue comments endpoint of 22443, sent through scripts/pm/post-stamped.mjs on the fleet-write relay as objectstack-fleet[bot]. git push is not counted.",
    "gates": {
    "head": "cb35f90d4d",
    "derived": 119,
    "run": 119,
    "exit_0": 119,
    "not_measured": 0,
    "unrun": 0,
    "reconciliation": "node scripts/pm/dispatch-gates.mjs --ran: 119 derived famil(ies) accounted for, 119 run, 0 NOT-MEASURED (a derived zero: all 119 recorded an exit code and none is 3), 0 UNRUN",
    "first_pass_refusals": "check:skill-examples, check:dual-build-cjs-loads, check:i18n exited 3 (PREREQUISITE NOT MET, unbuilt dists) on the first sweep; re-run after the full turbo build, each exit 0 ('262 prose examples type-check across 3 surface(s)'; dual-build floors held; 'check-i18n-bundles: OK (9 package(s))'). The reconciled list carries the re-run codes.",
    "ci": "35 completed on cb35f90: 33 success, 2 skipped, 0 failed, 0 in_progress; seven required contexts success"
    },
    "line_budget": "+370 / -25 across 13 files vs merge base 4e9fe9f (dispatch-gates numstat, 395 changed lines, under the human-merge threshold); the 11 original files stay +353 / -25, the two regenerated documents add +17 / -0; no governed surface touched",
    "files_changed": [
    "docs/protocol-upgrade-guide.md (generated, this round)",
    "packages/spec/spec-changes.json (generated, this round)",
    "packages/spec/src/migrations/registry.ts (merge only: main's step-18 entries joined; this PR's generated region byte-unchanged)",
    ".changeset/22443-storage-scope-public-retired.md (unchanged this round)",
    "content/docs/references/api/storage.mdx (generated, unchanged this round)",
    "content/docs/references/system/object-storage.mdx (generated, unchanged this round)",
    "packages/services/service-storage/src/storage-routes.ts (unchanged this round)",
    "packages/services/service-storage/src/storage-routes.test.ts (unchanged this round)",
    "packages/spec/src/api/storage.zod.ts (unchanged this round)",
    "packages/spec/src/api/storage.test.ts (unchanged this round)",
    "packages/spec/src/migrations/entries/semantic/18.storage-scope-public-retired.ts (unchanged this round)",
    "packages/spec/src/system/object-storage.zod.ts (unchanged this round)",
    "packages/spec/src/system/object-storage.test.ts (unchanged this round)"
    ],
    "commits": [
    {
    "sha": "e811349997",
    "kind": "merge",
    "note": "os-regen-merge.sh step 1, origin/main 4e9fe9f, zero conflicts, steps 2-3 changed nothing"
    },
    {
    "sha": "cb35f90d4d",
    "kind": "pure regeneration",
    "note": "gen:spec-changes + gen:upgrade-guide only; no hand edit"
    }
    ],
    "deviations": [
    "Push order: the two commits were pushed (fast-forward) right after the regeneration commit and BEFORE the gate sweep, per AGENTS.md's push-before-every-multi-minute-step rule; the dispatch listed the push last. No commit followed, so the pushed head is the head every gate and test above measured.",
    "Worktree: the old registration was pruned (git worktree prune) and the new worktree checked out the existing local branch, which already sat at origin's 149062a, instead of creating it with -b.",
    "Shared history deepened by 400 commits (git fetch --deepen) because the shallow checkout failed an ancestry control leg; see open_questions.",
    "Commit trailers: the AGENTS.md model-free pair (Claude-Session plus Co-authored-by Claude), not the harness-suggested model-named Co-Authored-By line, which the pre-push trailer gate refuses."
    ],
    "open_questions": [
    {
    "question": "This PR's retired-value prescription on StorageScopeSchema names '@objectstack/spec 17.8.0' as the removing release. main has been in Changesets pre mode (tag next) with a pending spec major since a87d8be, which was already an ancestor of the earlier merge base 3ca71b6 (measured after deepening: exit 0, control leg 3ca71b6 in origin/main also exit 0; before deepening both legs read exit 1, so that negative was void). check-changeset-no-major prints the same pre-mode exception, and the PROTOCOL_VERSION changeset says the line ships as 18.0.0-next.N. So a 17.8.0 spec release is not on the current line, but check:future-spec-major refuses any spec version above the package.json major (17), so 17.8.0 is the spelling the gates admit. The contract review read the version as a prediction that goes stale only if a release is cut first; this reading says the pre-mode line already makes it stale.",
    "options": [
    "A. Leave it. The gates require a 17.x spelling while package.json reads 17.7.0; the question covers every prescription written in the pre-mode window and belongs to the release lane (check:future-spec-major and the ADR-0087 level rule disagree only in pre mode). Cost: the shipped refusal may name a release that is never cut.",
    "B. Re-spell it as 18.0.0 in a hand edit now. check:future-spec-major refuses it, and the hand edit ends the carry of the PASS record.",
    "C. Drop the version from the prescription. That breaks the retired-value helper's own contract (name what replaced it, the removing version, the one-line fix)."
    ],
    "recommendation": "A for this PR. Real business need: measured, the gate admits only 17.x, so B cannot land and C breaks a helper contract that every retirement follows. Long-term soundness: one rule for every pre-mode tombstone beats a one-off spelling on one PR. Preventing AI authoring mistakes: a single rule, settled in the gate, keeps tombstones consistent, while per-PR guesses do not. Startup focus: no new gate; the seat routes the question to the release lane only if it wants it settled. Not filed; the family size is NOT MEASURED."
    }
    ],
    "out_of_scope_findings": []
    }

  9. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22469 → ee8751d41e (the first half: public retired from StorageScopeSchema and refused at the upload doors). Part of #22443; the card goes back to pm:queue for the ruled follow-up

    domain:spec seat 1 (#6017) · os-tesla · session session_01VZqqwTj2wsihZEbfT6yyYN · 2026-10-09T19:10Z · holder of claim 6078016522, released by this act. ⛔ Class and function level only.

    • Landed: merged through the merge queue as ee8751d41e (2026-10-09T18:44Z). It has one parent, 9411faa1ba, and is an ancestor of origin/main.
    • Content check: all 13 PR paths on ee8751d41e are blob-equal to the reviewed head cb35f90d4d. The review chain: ACCEPT 6081213304; at-tier contract review PASS 6081142308 at 149062a9db, carried to cb35f90d4d over a pure regeneration by Regen-provenance: (6086677604).
    • What now holds:
      • StorageScopeSchema refuses public with a prescription naming acl: 'public_read'.
      • Both upload-starting handlers answer scope: 'public' with 400 INVALID_REQUEST before any row, URL or backend upload.
      • The download handlers are unchanged.
      • The step-18 D3 entry storage-scope-public-retired is registered and projected (step 18: 330 semantic entries).
    • What remains (ruling B, 6081131776):
      • Remove the public option from sys_file.scope.
      • Add a one-time backfill in the shape of backfill-sys-file-organizations.ts that rewrites stored public rows to user. It counts before it writes, does nothing at zero, and names its inverse.
      • Pins: a copy of a rewritten row succeeds; a deployment with no such rows runs clean.
      • The changeset states the data rewrite. Clause-②: no (narrowing), with a contract review before the queue. That PR carries Fixes #22443.
    • Filed from this half: finding(service-storage): an upload start naming a scope outside the sys_file vocabulary answers 500 INTERNAL (an engine invalid_option relayed as an internal fault) instead of a 400 naming the allowed scopes #22470 (an off-vocabulary upload scope answered 500).

    Release: session_01VZqqwTj2wsihZEbfT6yyYN · why: a partial landing (Part of #22443) · to: pm:queue, unassigned, for the ruled follow-up above. This seat re-claims it when a dispatch slot frees. This act moves the card pm:dispatched → pm:queue and removes the assignee os-tesla.

  10. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 4 (#22443 follow-up, ruling B 6081131776: retire the public option from sys_file.scope and add a one-time backfill that rewrites stored public rows to user) · 2026-10-09T21:02Z
    Session: session_01VZqqwTj2wsihZEbfT6yyYN
    Account: os-tesla (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-22443-sys-file-public-scope-backfill
    Worktree: objectstack-issue-22443-b
    Domain: domain:spec
    Seat: domain:spec#1
    File surface (at origin/main faf6348508; stop on breach and explain in the report): packages/services/service-storage/src/objects/system-file.object.ts (the scope select); one new backfill module beside packages/services/service-storage/src/backfill-sys-file-organizations.ts and its test, plus the package export if the precedent has one; the copy-path pin near file-reference-lifecycle.ts copyOwnedFile (test only); one .changeset/22443-*.md. The ruling names the spec seat to dispatch this half, so the card stays in this lane although the files are service-storage's.
    Container & model: M, mode:subagent, model: default tier (dispatch-gates --tier: no path-derived mandate). The contract review at CONTRACT_REVIEW_TIER is owed before enqueue (the ruling: "contract review before the queue").
    Clause-②: no
    Responsibility: n/a — not a defect card (maintainer-ruled, #22443 ruling B 6081131776)
    Thread-read: 6087530099
    Serial constraints cleared: PR #22469 (the first half) landed as ee8751d41e. No open PR touches packages/services/service-storage/ (all 14 open PRs' file lists, read 2026-10-09T21:02Z), and no pm:dispatched card's claimed file surface names it.

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions