Skip to content

fix(service-storage): sys_file's file-reference columns are lifecycle-owned - #28454

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-6wjq4y
Oct 31, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-6wjq4y

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

Fixes #27767
Fixes #27778

Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none — no skill or docs page teaches writing these columns; they are the storage service's own bookkeeping

Ruling-ref: 6545931776 (letter A)

维护者速读

What changes

Tests

New pins in sys-file-storage-owned-columns.integration.test.ts (the #27765 rig: real ObjectQL over sqlite, StorageServicePlugin on the local adapter, data writes as a signed-in caller with no system context):

  1. declares exactly the five file-reference columns, each readonly;
  2. an update by id cannot change any of the five (one case per column), and the rest of the write lands;
  3. a predicate update cannot change any of them;
  4. a create cannot set any of them, and the row is written;
  5. a batch create cannot set them on any row;
  6. the lifecycle's claim, copy and release still write them;
  7. a system create and a system update keep the values they write;
  8. service-storage: after #27399, sys_file.key/status are readonly and so no longer engine-required on insert; state or restore the invariant, and trim an unused export #27778: every storage writer sets key and status (presign, complete, field copy).
Pins on origin/main 5517005 (premise): 9 red / 15 green

Measured with only the list exported (no readonly, no guard change): pins 1–5 red (9 cases), pins 6–8 and every pre-existing case green. Pins 1–5 green after the change. The mechanism of the reading is kept off GitHub under the security dispatch rule.

Ablations (one per pin, via scripts/ablation-replace.mjs; each restored to blob == HEAD, git diff HEAD empty)

Acceptance notes

  • content/docs/permissions/system-context.mdx row 47g describes the guard's system early return in terms of the five storage-owned columns. It stays true (a system insert still keeps them) but does not name the reference columns; left untouched, outside this card's file surface.
  • storage-service-plugin.ts's wiring comment names the guard by the storage-owned columns only; no wiring change, left as it is.
  • The pending service-storage: sys_file lifecycle columns (status, size, mime_type) are writable through the data door, so a never-uploaded file can be marked committed and then claimed by a file field #27399 changeset says every other sys_file column ("name, acl, scope, ownership") is written through the data door as before. If "ownership" is read as the reference columns, this PR's changeset, in the same release, supersedes it. It is left unedited: check-empty-changeset treats a rewrite of another PR's pending note as a release decision for a person, so the seat decides whether that one word changes.

Verification

Head 7fc7033d (2d5586fc plus the revert of a changeset edit; no source change between them).

  • Targeted pins: pnpm --filter @objectstack/service-storage exec vitest run --maxWorkers=2 src/sys-file-storage-owned-columns.integration.test.ts ⇒ 24/24.
  • Package: pnpm --filter @objectstack/service-storage exec vitest run --maxWorkers=2 ⇒ Test Files 71/71, Tests 1326/1326; pnpm --filter @objectstack/service-storage typecheck ⇒ exit 0 (dts build of the closure).
  • Lint: pnpm exec eslint --no-inline-config on the three touched source files ⇒ exit 0.
  • Dogfood (owed by the narrowing arm), on 7fc7033d: pnpm --filter @objectstack/dogfood test under the verify lock ⇒ VERDICT command-exit 0, Test Files 361 passed / 3 skipped (364), Tests 2917 passed / 28 skipped (2945).
Derived gates: 62 derived, 62 run, 0 NOT-MEASURED (dispatch-gates --ran reconciled, exit 0)

Every dispatch-gates --commands line run with stdin closed, exit codes recorded before any pipe. 58 exit 0 on the first pass. Four were re-run after their prerequisite and then exit 0: check-plugin-teardown-shape --self-test (exit 3 until the pinned control commit was fetched), check:dual-build-cjs-loads and check:i18n (exit 3 until a full dts build), and check-empty-changeset (exit 1 on an edit of the pending #27399 note, which was reverted; see Acceptance notes). The derived list is identical for both heads.


Generated by Claude Code

…-owned

The five columns that record which record and field hold a file, and which
record last let it go (ref_object, ref_id, ref_field, released_ref_object,
released_ref_id), take the form the storage-owned columns already have:
readonly on sys_file, so the engine's static strip takes a non-system
caller's value off an update, and the same create-side guard drops them
from a non-system create. The lifecycle's claim, release and copy still
write them under the system context.

Also states in system-file.object.ts that the presence of key/status is
the storage writers' invariant, not the engine's, and trims the guard's
unused helper export.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibwQbgGE61XZrK6xRjM
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-storage, touching 11 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/webhooks.mdx (via ref_id (literal, a string literal in SYS_FILE_REFERENCE_COLUMNS))
  • content/docs/permissions/system-context.mdx (via installSysFileStorageOwnedColumnsGuard (symbol, a top-level function))
  • content/docs/protocol/objectql/types.mdx (via ref_id (literal, a string literal in SYS_FILE_REFERENCE_COLUMNS), ref_object (literal, a string literal in SYS_FILE_REFERENCE_COLUMNS))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v18/18-2.mdx (via released_ref_id (literal, a string literal in SYS_FILE_REFERENCE_COLUMNS), released_ref_object (literal, a string literal in SYS_FILE_REFERENCE_COLUMNS))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 60 of 212 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 — 10 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 776c1d875b2d140b4c2419103a7ad8681c1d2610 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 50d3373db0ad62dc1c0955ba81b17b346570079c — the merge of head 7fc7033de3b8c45e516ba45ede12ee9928ebac7d into base 776c1d875b2d140b4c2419103a7ad8681c1d2610, 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 50d3373db0ad62dc1c0955ba81b17b346570079c && git checkout 50d3373db0ad62dc1c0955ba81b17b346570079c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 776c1d875b2d140b4c2419103a7ad8681c1d2610 7fc7033de3b8c45e516ba45ede12ee9928ebac7d && git checkout -B drift-repro 776c1d875b2d140b4c2419103a7ad8681c1d2610 && git merge --no-ff 7fc7033de3b8c45e516ba45ede12ee9928ebac7d

node scripts/docs-audit/affected-docs.mjs --json 776c1d875b2d140b4c2419103a7ad8681c1d2610

⚠️ 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 776c1d875b2d140b4c2419103a7ad8681c1d2610 → 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

triage#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head 7fc7033de3b8c45e516ba45ede12ee9928ebac7d · 2026-10-31T17:47Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 7fc7033de3b8c45e516ba45ede12ee9928ebac7d
Local-runs: none
Scope: packages/services/service-storage: system-file.object.ts (new SYS_FILE_REFERENCE_COLUMNS beside SYS_FILE_STORAGE_OWNED_COLUMNS; readonly: true on ref_object, ref_id, ref_field, released_ref_object, released_ref_id; the #27778 header statement on key / status), sys-file-storage-owned-columns.ts (LIFECYCLE_OWNED_COLUMNS = both lists; dropStorageOwnedColumns un-exported and renamed dropLifecycleOwnedColumns; one warn line), 8 new pins in sys-file-storage-owned-columns.integration.test.ts, the changeset (@objectstack/service-storage patch, ADR-0087 not-required). 4 files, +211/−9. Fixes #27767 + #27778 (ruling A 6545931776; triage 6510433159 step 2 / 6511387772; claim 6546009563; dev report 6546642607). Not governed.
Inputs: both cards' bodies, triage, seat-4 release 6539653511, ruling, claim and dev report, the PR body, the whole PR diff, git grep on origin/main (dropStorageOwnedColumns is referenced only inside its own file, so dropping the export breaks no importer; the five storage columns already carry readonly: true at the merge-base), check-runs (read 2026-10-31T17:53Z: all 48 latest-per-name check-runs completed, 33 success + 15 skipped, none failed, none running) — 48 checks. Nothing built or run here; the seat's numbers (24/24 targeted, 71 files / 1326 package, five ablations, 62 derived gates, dogfood 361 files) are the dev report's. The security-dispatch rule holds: this record stays at rule level, as the PR does.

① Derived judgments

② Rulings cited

③ Narrowing / widening

  • Narrowing: a non-system write (create, batch create, update by id, update by predicate) to any of the five reference columns is dropped; the rest of the write lands and nothing is refused. A caller passing strictReadonlyWrites on an update gets ERR_READONLY_FIELD_REJECTED, as for every readonly column today. Attaching a file to a record stays the file-field write.

④ Behaviour / flags

  • Seat lands (Tier S path): draft → ready → queue after this record; the changeset ships @objectstack/service-storage patch.
  • The contract-review-tier review the ruling required before the queue is this record.

Implemented-by: claude/project-thread-6wjq4y
Reviewed-by: session_01L9qibw58ehQ2gRHP2h2Puz
VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-tesla · session_01L9qibz3ZUbZ8Qcn1rDw9yc

维护者速读

席位意见:契约复审 PASS,按裁定 A 原样实现。可以直接落地。

席位核对

ACCEPT · seat domain:server#1 · head 7fc7033d · 2026-10-31T17:55Z

  1. Shape: Fixes #27767 and Fixes #27778. Draft, base main, 4 files, +211/−9, with a patch changeset for @objectstack/service-storage (ADR-0087 not required). No packages/spec path.
  2. Ruling: A 6545931776. The five reference columns take the fix(service-storage): sys_file's storage-owned columns (key, mime_type, size, status, etag) are not writable outside the storage service #27765 form: readonly on the object, plus the same create-side guard. The PR adds no new refusal, no spec key and no wiring change. The lifecycle writers keep running in the system context.
  3. Contract review: lane PASS 6547053036 at this head. It declares Clause-②: no (narrowing) and Metadata-protocol: none.
  4. Gates / check-runs:
    • Dev report 6546642607: 24/24 targeted tests, the package suite at 71 files / 1326 tests, five ablations red, 62 derived gates exit 0, and the dogfood battery green.
    • On head 7fc7033d: 48 check-runs completed, 33 success, 15 skipped, 0 failed.
  5. Accepted deviations: the pending service-storage: sys_file lifecycle columns (status, size, mime_type) are writable through the data door, so a never-uploaded file can be marked committed and then claimed by a file field #27399 changeset wording and docs row 47g are left as they are; the PR names both. This record stays at rule level, per the security-disclosure rule.
  6. Landing: check-governed-merges --pr 28454 reports NOT governed. The seat readies the PR and arms auto-merge under the maintainer's 「按 pm-dispatch 审过的pr你可以自主落地」.

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 tests tooling

Projects

None yet

1 participant