Skip to content

feat(storage)!: retire the storage scope public from StorageScopeSchema and refuse it at the upload doors (#22443) - #22469

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-22443-retire-public-storage-scope
Oct 9, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-22443-retire-public-storage-scope

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #22443
Clause-②: no

Retires the storage scope public, which never made a file publicly readable, and keeps acl: 'public_read' (ADR-0104) the one opt-in for anonymous download: the triage answer on the card ("trim"). This PR lands the half that every remaining option shares. The other half, the sys_file object's public scope option and rows already stored with it, is a decision rather than an edit, and stays on the card (see "Left for a decision"). #22443 remains open.

What lands

  • StorageScopeSchema (@objectstack/spec, system/object-storage.zod.ts). public leaves the enum through enumWithRetiredValues. tsc refuses it, and a parse, alone or as ObjectStorageConfig.scope, answers with a prescription that names acl: 'public_read' instead of zod's generic enum message.
  • The two upload-starting handlers in registerStorageRoutes (presigned and chunked, @objectstack/service-storage). A request naming scope public is answered 400 INVALID_REQUEST before any file row, session row, upload URL or backend multipart exists. INVALID_REQUEST is an existing code, registered under this package in the ADR-0112 ledger and already used by these handlers for a request they cannot take. The message names the remedy and where it lives: on the stored file record, because the upload request carries no acl and every upload is stored acl: 'private'. Every other scope, and an omitted one, is taken exactly as before.
  • The upload request contract (api/storage.zod.ts). The scope description on GetPresignedUrlRequestSchema and InitiateChunkedUploadRequestSchema no longer offers public as an example, and says what the scope is and is not. The shape is unchanged (z.string()).
  • D3 entry storage-scope-public-retired (step 18), and one changeset: minor on both packages, a declared narrowing, disposition registered storage-scope-public-retired. No D2 conversion (H2 below). spec-changes.json and the upgrade guide do not move, because step 18 is not projected yet.
  • The two reference pages, regenerated.

The download handlers are not touched, so their verdict from PR #22439 stands. A pin here asserts it on an already-stored public-scoped row with the default acl: anonymous 401 AUTH_REQUIRED, signed-in 200.

Measurements: the four dispatch hypotheses

All readings at origin/main da159f7 unless stated.

  • H1, where the vocabulary is declared or checked. Holds. Five sites, and the spec enum is not the upload vocabulary:
    1. StorageScopeSchema. Consumed only by ObjectStorageConfigSchema.scope, and ObjectStorageConfigSchema has no runtime consumer: no parse of it outside packages/spec.
    2. GetPresignedUrlRequestSchema.scope and InitiateChunkedUploadRequestSchema.scope. Open z.string(), and the first one's description taught public. No server handler parses a request with them.
    3. The sys_file object's scope select (user, tenant, public, private, temp, attachments). This is the stored vocabulary and, before this PR, the only enforcement: the engine refuses an insert outside it (invalid_option).
    4. The two upload-starting handlers. No check of their own before this PR; they pass the caller's value to site 3.
    5. sys_upload_session.scope. A free Field.text, copied from the request; no vocabulary.
  • H2, an authored spelling and stored rows. No authored spelling, so no conversion. scope: 'public' has one hit in the tree, a spec request-schema test fixture (re-pointed to tenant here), against 29 hits for scope: 'attachments', the control. At the .objectui-sha pin f0268ad784 and at objectui main 2063f7a: zero hits; the console passes no scope and the record attachments panel passes attachments (the control). No metadata type carries either schema, and stored sys_file rows are data, which the ADR-0087 chain does not reach. Measured on a real ObjectQL over sqlite, with the real SystemFile minus its public option and a legacy public row written through the driver:
    • findOne and find pass the row through, and an update that does not name scope succeeds. Reading is unaffected.
    • The field-reference copy path (copyOwnedFile in file-reference-lifecycle.ts) re-inserts the source row's scope, and that insert is refused VALIDATION_FAILED, invalid_option on scope. An update that writes scope: 'public' is refused the same way.
    • So retiring the option without touching stored rows would break writing an already-owned legacy public file id into a second record field. Per the dispatch, that is a fork for the maintainer, not a choice for this PR.
  • H3, the error code. Holds. INVALID_REQUEST is reused; no new code, so Clause-② stays no.
  • H4, storage-route-ledger.ts. Holds. Its 'public' is a route disposition (an unauthenticated browser route), not a storage scope. Untouched.

Left for a decision

The sys_file scope select keeps its public option in this PR. The options, as measured above:

  • A. Keep it as a stored-only value. After this PR no upload can write it; legacy rows, and copies of them, keep it. No data migration. What remains: the stored vocabulary keeps a member labelled "Public" that grants nothing.
  • B. Retire the option and rewrite stored public rows to user, once. A storage-service backfill, the shape the sys_file organization backfill already takes. Declared and enforced agree everywhere afterwards. It costs a data rewrite on every deployment, a no-op where there are no such rows. Keys keep their public/ prefix and no bytes move.
  • C. Retire the option with no rewrite. Measured to break the copy path above. Not viable.
  • D. Retire the option and stop the copy path inheriting the source scope. No data rewrite, but it changes the copy rule for every copy, attachments-scope sources included, which is a design question of its own.

The recommendation, reasoned on the four axes, is in the report on the card.

Tests

All at 149062a9db (this branch merged with origin/main 3ca71b6):

  • @objectstack/service-storage: vitest run, 47 files and 789 tests passed. typecheck exit 0.
  • @objectstack/spec: vitest run --project local, 629 files and 18783 tests passed (1 todo). typecheck exit 0, which includes the test layer, so the @ts-expect-error on the retired member compiles as a real check. Repo-project files that read these surfaces (retired-key-migrate-sentence, step18-rationale-merge, conversions-major18-merge, error-catalog-docs): 4 files and 41 tests passed. The rest of --project repo is declared to CI.
  • Gates: dispatch-gates --commands derived 119 families on this head, plus check:generated from the dispatch list. All 119 ran with exit 0, and dispatch-gates --ran reports 119 run, 0 not measured, 0 unrun.
  • Lint, narrowed: eslint on the 8 touched .ts files, 8 files in the JSON output, 0 errors, 0 warnings. The population is eslint.config.mjs's own TS glob, and eslint.config.mjs never enables type-aware linting, so this diff cannot move a verdict on an untouched file. The repo-wide pnpm lint is CI's.
  • Reverse verification, each a committed fix mutated through scripts/ablation-replace.mjs and restored to git diff HEAD empty:
    • Door gate disabled: the refusal pin goes red (1 failed, 3 passed); the control, body-acl and legacy-download pins stay green.
    • public re-admitted to the enum: both prescription pins go red (2 failed, 64 passed), and the unknown-value control stays green. A first run of this ablation used the dummy retired key publik, which is the control's own value, so it reddened the control as well. It was rerun with an unrelated key; the reading above is the rerun.

Acceptance notes

  • Reported to the seat to file (reproducible): an upload naming any scope outside the sys_file select, a free key prefix for instance, is answered 500 INTERNAL by the upload-starting handlers. The engine's invalid_option is relayed as an internal error whose message tells an operator to restore the data engine. Measured with the presigned handler over a real engine. objectui's upload adapter documents scope as a free logical key prefix, with examples that are not select options. Not fixed here: a different defect, with its own shape to decide.
  • ObjectStorageConfigSchema, and with it the rest of StorageScopeSchema, has no runtime consumer. Noted, not filed (an unconsumed declaration with no pull).
  • acl: 'public_read' 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 this PR names can be applied only by a write to the stored sys_file row. Noted for the guest-model family (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).
  • No liveness ledger row moves: the ledger walks metadata types, and none carries StorageScope.

Generated by Claude Code

@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/service-storage, @objectstack/spec, touching 19 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/spec/spec-changes.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

17 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 4e9fe9ff6afbc1957039b351f8d4ca88948865cf.

⛔ 5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/spec-changes.json) — pages documenting those are invisible to this run
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 141 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 4e9fe9ff6afbc1957039b351f8d4ca88948865cf → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 053c265ec3c4268fe75ff93ead4dc37ccc371244 — the merge of head cb35f90d4da4c0b96a2b07bbfcd29461d726d98b into base 4e9fe9ff6afbc1957039b351f8d4ca88948865cf, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 053c265ec3c4268fe75ff93ead4dc37ccc371244 && git checkout 053c265ec3c4268fe75ff93ead4dc37ccc371244
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4e9fe9ff6afbc1957039b351f8d4ca88948865cf cb35f90d4da4c0b96a2b07bbfcd29461d726d98b && git checkout -B drift-repro 4e9fe9ff6afbc1957039b351f8d4ca88948865cf && git merge --no-ff cb35f90d4da4c0b96a2b07bbfcd29461d726d98b

node scripts/docs-audit/affected-docs.mjs --json 4e9fe9ff6afbc1957039b351f8d4ca88948865cf

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 4e9fe9ff6afbc1957039b351f8d4ca88948865cf → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 149062a9db0504b10d5ab22c2403752ac26d3fb9
Local-runs: none

Reviewing seat session_01VZqqwTj2wsihZEbfT6yyYN (the adopting seat, domain:spec#1) · 2026-10-09T12:44Z. Inputs: card #22443 (body and all four comments: triage 6077725515, claim 6078016522, dev report 6079733604, decision 6079785676), PR #22469 (body, the 11-file list, the net diff against merge-base 3ca71b6e05), and the check-runs on this head. Nothing built, run or re-run. ⛔ Class and function level only (guest-model family).

Check-runs on the head: 35 runs, every one completed: 33 success, 2 skipped (Console Pin Gate, path-filtered; Packed-tarball smoke, opt-in), 0 in progress, 0 failed. All seven required contexts are success: Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard. Also success: Check Changeset, Spec property liveness, Part-of PR must not also close its card, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Check PR Size. The file list touches no governed surface; +353 / -25, size/m; head repo is the base repo (not a fork).

① Derived judgments

Every accept-set and public-surface change the net diff implies, each judged:

  1. StorageScopeSchema (@objectstack/spec, system/object-storage.zod.ts): public leaves the enum through enumWithRetiredValues; the nine other members stay. RIGHT. This is the triage answer ("trim", 6077725515) under ADR-0049 enforce-or-remove, with ADR-0104's acl: 'public_read' kept as the one opt-in. The helper removes the member from the type (so tsc refuses it) and answers a parse of it with the prescription, while an unknown value keeps zod's own message (pinned as the control). The prescription names what replaced the member, the removing version and the one-line fix, which is the helper's own contract. The version named, 17.8.0, is the next lockstep minor after the released 17.7.0, the same convention the 17.7.0-naming prescriptions on main follow. The only consumer is ObjectStorageConfigSchema.scope (default global), which no runtime parses, so the default the prescription names is the right one for its surface.
  2. ObjectStorageConfigSchema with scope: 'public' now refuses, with the prescription at the scope path. RIGHT, and pinned. No runtime and no authored metadata carries this schema (its consumers at main are spec-internal), so nothing outside spec moves.
  3. The two upload-starting handlers in registerStorageRoutes (presigned and chunked, @objectstack/service-storage) answer scope: 'public' with 400 INVALID_REQUEST through requireAcceptedUploadScope, placed after requireUploadSession and the required-field check and before the size gate, the file row, the session row, the upload URL and the backend multipart. RIGHT. INVALID_REQUEST is pre-existing and registered (H3 holds: no new code, so Clause-②: no stays true). The refusal sits behind the session gate, so an anonymous caller still meets the session refusal first and learns nothing new. The pin asserts createFile, createSession, getPresignedUpload and initiateChunkedUpload are never reached; the control pins every other sys_file select member and an omitted scope as taken before, with acl still private. Call-site completeness (Prime Directive 10): store.createFile has exactly these two upload call sites in the runtime tree; copyOwnedFile in file-reference-lifecycle.ts inherits a stored row's scope and takes no caller value, so it is not a door.
  4. Download handlers untouched. RIGHT per ruling 6074960686 item 3 (a file is judged by acl, the attachments scope and field ownership). Pinned on an already-stored public row with the default acl: anonymous 401 AUTH_REQUIRED, signed-in 200.
  5. GetPresignedUrlRequestSchema.scope and InitiateChunkedUploadRequestSchema.scope: description only; the shape stays an open z.string(). RIGHT. The old description taught public as an example; left alone it would advertise a value the door now refuses (Prime Directive 10). No accept-set change on the spec side; the regenerated reference page under content/docs/references/api/ carries the new text (AUTO-GEN; check:docs is inside the green required contexts).
  6. D3 semantic entry storage-scope-public-retired at step 18, plus the generated region of migrations/registry.ts. RIGHT. No metadata type carries either surface, so there is no authored source for a D2 conversion to rewrite; a D3 entry is the class that carries the judgement only a caller can make (whether a given file must render before sign-in). The entry file and the registry region are byte-equal; check:migration-registry and check:adr-0087-registration are inside the green required contexts. spec-changes.json and the upgrade guide carry no step-18 id on main (none of the sibling 18.* entries either), so their not moving is right.
  7. No ADR-0087 D2 conversion and no liveness-ledger row. RIGHT. The ledger under packages/spec/liveness/ is keyed by metadata type and has no StorageScope row; Spec property liveness is green.
  8. The sys_file object's scope select keeps public. RIGHT for this PR: retiring it alone is measured to break copyOwnedFile for legacy rows, which is the fork the seat put to the maintainer (6079785676), see ③.
  9. Sibling checkout (Post-Task Checklist step 4). Console Pin Gate was skipped by its path filter, so the check-runs do not answer this. The dev's H2 reading is zero hits for StorageScope, ObjectStorageConfig and a public scope spelling at pin f0268ad784 and at objectui main 2063f7a; a read-only grep of the sibling checkout at 2063f7a agrees. The removal breaks no pinned import.
  10. The @ts-expect-error pin on the retired member is a live check. tsconfig.test.json includes the test layer and typecheck runs check:test-typecheck over it; the file's debt rows are TS6133 only, so an unfired expect-error (TS2578) has no row to hide under.

Nothing in the diff is judged wrong. One observation, not a finding: the prescription's version string is a prediction; it goes stale only if a release is cut before this lands, which every retirement prescription here accepts.

② Semver level

Changeset .changeset/22443-storage-scope-public-retired.md: @objectstack/spec: minor, @objectstack/service-storage: minor. MATCHES what the diff publishes. Both packages' published source moves (a spec enum narrowing; a new refusal on two service handlers), and both moves are accept-set narrowings, i.e. BREAKING. Under the launch-window convention (check-changeset-no-major, in force until GA) a breaking change ships as minor and its carriers are the BREAKING banner plus the ADR-0087 disposition; the changeset carries the bold BREAKING banner, the adr-0087 marker line reading registered storage-scope-public-retired, a FROM → TO table and the one-line fix (Post-Task Checklist step 3), and the PR title carries the !. patch would be wrong (public surface moves) and major is refused. No skip-changeset label; Check Changeset is green.

Clause-②: line. PR body line 2 reads Clause-②: no; the changeset reads Clause-②: no (narrowing). CONSISTENT, and both legal spellings: the value answers whether the diff widens, and no is true (no new accepted key or value, no new error code); an absent arm in the body declares no direction, and the arm is read from the changeset by check-adr-0087-registration (clause2-line.mjs is the one reader). no (narrowing) is the spelling the arm exists for.

③ Boundary flags

Dev report 6079733604: every deviations entry and the open_questions entry.

  • D1 File surface widened past the claim, without stopping. Each added path judged: (a) packages/spec/src/api/storage.zod.ts and its test: RIGHT. The request schemas' scope describe was the one doc teaching public; a description advertising a refused value is the Prime Directive 10 shape, and the fixture had to stop spelling it. (b) the D3 entry and the registry.ts region: inside the claimed packages/spec/src/migrations/ surface; registered is the only ADR-0087 category open to a spec Zod narrowing that carries a prescription. (c) the two reference pages under content/docs/references/: AUTO-GEN, regenerated by the spec build; leaving them reds check:docs. All three are consequences of the dispatched shape; none is new scope. Not stopping on breach is a process deviation, reported in the report as the claim asked; it leaves no wrong byte in the diff. ANSWERED.
  • D2 Part of #22443, not Fixes. RIGHT: the card is needs-user-decision, and a Fixes would close a half-delivered card on merge. Re-arm audit, all three carriers: body — the two mentions of #22443 are Part of #22443 and #22443 remains open., no closing keyword adjacent to either (negations and quoted text included), and the Part-of guard is green on this body; commits — the four messages (wip, feat, docs, merge) name no issue number and carry no closing keyword; changeset — the summary line ends (#22443) preceded by the word "download", the bookkeeping lines (Clause-②, the adr-0087 marker) carry no keyword, and the phrase "The one-line fix:" names no number; title — (#22443) preceded by "doors". Nothing GitHub composes from these can re-arm an auto-close. ANSWERED.
  • D3 No liveness ledger row. RIGHT, see ①.7. ANSWERED.
  • D4 The sys_file select's public option left in place (H1 asked every declaration site closed). The dev stopped per the H2 rule because the measured cost is a data rewrite or a copy-rule change. ESCALATED by the seat as decision 6079785676 (options A / B / C / D, recommendation B); the card carries needs-user-decision.
  • Open question — is this PR's half independent of that decision? YES: right under A, B and D. A (keep public as a stored-only value): the door refusal in this PR is exactly what makes it write-proof. B (retire the option and backfill stored public rows to user once): the door refusal is still required, since nothing may re-create a public row after the rewrite, and the spec retirement is untouched; the backfill is its own changeset. D (retire the option and stop copyOwnedFile inheriting the source scope): the same. The PR's legacy-download pin seeds its row through the engine-absent StorageMetadataStore(null) stand-in, so it does not depend on the select option; under B or D the follow-up may retire that pin as a shape that can no longer exist, which is the follow-up's edit, not a defect here. The D3 prescription (another scope, then acl: 'public_read' on the stored record) holds under all three, and the changeset's "files already stored with scope public are not touched" is true of this PR under all three.
  • Out-of-scope findings in the report. (i) An off-vocabulary scope answered 500 INTERNAL by the upload-starting handlers (the engine's invalid_option relayed as an internal fault): reproducible and a different defect, TO FILE by the seat per Prime Directive 10, not this PR's. (ii) acl: 'public_read' has no setter at either upload door, so the remedy this PR names is applied by a write to the stored sys_file row: true, and the remedy exists (the acl select carries public_read); noted for the guest-model family (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). (iii) ObjectStorageConfigSchema has no runtime consumer: noted, no pull. None blocks.

Implemented-by: claude/issue-22443-retire-public-storage-scope
Reviewed-by: session_01VZqqwTj2wsihZEbfT6yyYN

VERDICT: PASS

Landing note for the owning seat: no governed path, so no tier lifts; the card stays open on its decision; registry.ts step-18 writers re-sync through os-regen-merge.sh after PR #22215 lands, per the claim's hot-file note.

This was referenced Oct 9, 2026
claude added 2 commits October 9, 2026 17:10
…the protocol-18 merge

Pure regeneration (gen:spec-changes, gen:upgrade-guide) on top of the merge
of origin/main 4e9fe9f, which projects step 18 into both documents. The
only delta is this branch's D3 semantic entry storage-scope-public-retired:
step 17 to 18 migrated 329 to 330, aggregate 16 to 18 migrated 406 to 407,
the guide's step-18 semantic list 329 to 330. No hand edit.

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 carried over a pure regeneration — 149062a9db → cb35f90d4d

domain:spec seat 1 (#6017) · session session_01VZqqwTj2wsihZEbfT6yyYN · 2026-10-09T18:15Z · holder of claim 6078016522. Report: os-dev-report 6086647975 (the re-sync after PR #22215).

Regen-provenance: 6081142308 · 149062a9db0504b10d5ab22c2403752ac26d3fb9 → cb35f90d4d · pnpm --filter @objectstack/spec gen:spec-changes && pnpm --filter @objectstack/spec gen:upgrade-guide → (empty)

What the hop is, read off the committed trees (not the report):

  • e811349997 merges main at 4e9fe9ff6a (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, protocol 18) into 149062a9db through os-regen-merge.sh. git diff-tree --cc prints no content of its own.
  • cb35f90d4d regenerates the two ADR-0087 documents in a separate commit: packages/spec/spec-changes.json +14 and docs/protocol-upgrade-guide.md +3. Both route merge=os-regen. The one added id is this PR's storage-scope-public-retired: step 18 has 330 semantic entries, up from 329.
  • The 11 hand-written PR paths carry byte-identical +/- lines in 3ca71b6e05..149062a9db and in 4e9fe9ff6a..cb35f90d4d. The path set grows to 13 by the two generated documents alone.
  • The record carried is the at-tier contract review 6081142308 (PASS at 149062a9db). The line above is a pointer; a reader re-runs the comparison on the committed trees.

The dev's open question, answered by the seat: A. The retired-value prescription names @objectstack/spec 17.8.0, and the spec line is in pre mode toward 18. check:future-spec-major admits only a 17.x spelling while packages/spec/package.json reads 17.7.0, so 18.0.0 cannot land. Dropping the version breaks the retired-value helper's contract. The spelling is a release-lane question for every pre-mode tombstone, not this PR's; it stays as written.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 9, 2026 18:16
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 9, 2026 18:16
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit ee8751d Oct 9, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-22443-retire-public-storage-scope branch October 9, 2026 18:44
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 protocol:system size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants