Skip to content

docs(qa-checklist): attachments-storage maintenance — totalSize step fix, ADR-0104 D3 receipt clause, and the scratch authz fixture landed permanently - #7716

Merged
huangyiirene merged 2 commits into
mainfrom
claude/issue-7671-7669-7670-attachments-checklist
Aug 11, 2026
Merged

docs(qa-checklist): attachments-storage maintenance — totalSize step fix, ADR-0104 D3 receipt clause, and the scratch authz fixture landed permanently#7716
huangyiirene merged 2 commits into
mainfrom
claude/issue-7671-7669-7670-attachments-checklist

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes#7671
Fixes#7669
Fixes#7670

Three packed checklist-maintenance cards from the attachments-storage QA run (#7635). All three land in one file — docs/qa/platform-checklist/areas/attachments-storage.json — which is why they were packed into one branch and one PR. Docs-only: no product code, no showcase seed code, no coverage.json.

Per-card ledger

CardLanding siteBeforeAfter
#7671upload-session-abort · step 1POST /upload/chunked { filename, mimeType, size, chunkSize }…{ filename, mimeType, totalSize, chunkSize } + the 5 MiB chunkSize floor spelled out, and storage-routes.ts:274-281 added to source
#7669inline-grid-receipt-cells · acceptance clause 3 (0-indexed, the batch-payload clause)"receipt as a resolved stored-file object (absolute url) … receipt.name … an http(s) url"the ADR-0104 D3 stored form: the bare opaque sys_file id, asserted together with the field-ownership stamp (ref_object/ref_id/ref_field/acl) that proves the reference is managed. Title, step text, a new negative, and source (ADR + storage-routes.ts) follow
#7670new area-levelfixtures block + fixtures.provisioning on 3 itemsthe qa_vault/qa_shared/qa_nofiles + two-persona provisioning existed only as prose in run #7635; three items carried standing "not demonstrable on stock seeds" gapsone replayable qa-scratch-authz recipe (6 ordered calls, each grounded at file:line), referenced by the three consuming items; their knownGaps restated as closed-by-recipe with the dogfood pin named as fallback

diff --stat: 1 file changed, 109 insertions(+), 21 deletions(-) — one file, 1:1 with the scope above.

#7671 — the step text was unrunnable, not merely imprecise

The chunked-init route destructures totalSize and answers 400 INVALID_REQUEST without it, so a runner following the old step could not open a session at all:

274: const { filename, mimeType, totalSize, chunkSize: reqChunkSize, scope, bucket, metadata } = req.body ?? {};
275: if (!filename || !mimeType || !totalSize) {
276: sendError(res, 400, 'INVALID_REQUEST', 'filename, mimeType, and totalSize are required');
280: const chunkSize = Math.max(reqChunkSize ?? 5242880, 5242880);

The 5 MiB floor is recorded in the same breath because it silently rewrites totalChunks when a caller asks for less — a runner computing chunk counts client-side would otherwise disagree with the server for reasons the checklist never mentioned.

⛔ Clause 4's failed/expired enforce-or-remove finding from the same run is not touched here — it belongs to #7667, which is not in this pack. Likewise #7668's stale-@objectstack/core pin problem on attach-requires-parent-edit clause 3.

#7669 — the checklist was wrong, the product is right

Run #7635 scored this clause partial against correct behaviour. Under ADR-0104 D3 (docs/adr/0104-field-runtime-value-shape-contract.md:211-224) the stored form of a file field is an opaque fileId string — "the inline {url, name, size} blob is retired from the write path" — while the resolved { id, name, size, mimeType, url } shape is the expanded form produced at read time, whose url is "derived, never stored". storage-routes.ts:250-252 documents the same id as "the value a file field stores as a reference (ADR-0104 D3)".

So the clause was asserting a contract the ADR had already retired. It now asserts what the run actually proved and what D3 actually requires. A new negative pins the drift itself, so a future run does not re-score the shipped shape as a defect:

⛔ NOT a failure: a batch carrying the bare id instead of {name, url}. That is the shipped contract (ADR-0104 D3) …

D3's parent-derived read gating is cross-referenced to download-authz-both-sides (which already carries the non-entitled personas) rather than duplicated here — the same "cross-reference, do not duplicate" discipline this item already applies to the ADR-0059 form-side guard.

#7670 — landed checklist-side, as the card scoped it

The card's ⛔ clause said to stop and fork-report if the right landing turned out to be showcase seed code. It did not: the run's own provisioning was pure runtime HTTP against a live app, so it lands as a replayable recipe, and no repo code is touched. (A seeded private files-enabled showcase object would be the better long-term fix and is recorded as such in read-inherits-parent-rls' knownGaps — it is a separate, product-side change, not this card.)

The recipe is written once at area level and referenced by items via fixtures.provisioning.use, because three (soon four) items need the identical twenty-call sequence and inlining it per item is how ledger entries drift apart. Every call carries its own source citation:

Two gaps are recorded honestly rather than papered over: the SDK's meta.saveItem does not send ?package= (packages/client/src/index.ts:701-707), so steps 2-4 must be raw HTTP or the objects land outside the package; and the two personas' exact permission-set payloads were never captured by run #7635, so step 6 is specified by outcome (read-not-edit for A, baseline-only for B) with an instruction to verify the split independently before scoring any storage clause.

Gates

GateResult
pnpm check:platform-checklist1 problem — the known pre-existing coverage.json · qa: UNCLASSIFIED (#7347). Identical on origin/main before any edit; recorded, not chased. No new problem introduced: revision/history pairing, id shape and clause/oracle structure all validate across the 5 revised items.
JSON validityJSON.parse green; 8 items, all provisioning.use values resolve to the qa-scratch-authz key
revision / history5 items bumped 1 → 2, each with a dated history entry per README.md lifecycle
pnpm check:nul-bytesgreenOK (scanned 7104 text files … no raw ASCII control bytes); plus a targeted control-byte self-scan of the edited file (clean)

Docs-only, releases nothing → no changeset; skip-changeset applies.

Note for the PM (not blocking)

The area-level fixtures block is a new shape for this ledger: README.md's "Item anatomy" documents fixtures only per-item, and the validator is presence-level so it neither blesses nor rejects the area-level form. It is self-documented by a $comment inside the block, but the convention arguably belongs in README.md alongside the item anatomy. That file was outside this pack's declared surface, so it was deliberately left alone rather than edited on the side.


Generated by Claude Code

…ceipt clause against ADR-0104 D3
Two checklist-maintenance edits falling out of the attachments-storage QA run
(#7635), both in docs/qa/platform-checklist/areas/attachments-storage.json.
upload-session-abort (#7671) — the chunked-init step told the runner to POST
`size`. The route destructures `totalSize` and answers 400 INVALID_REQUEST
without it (storage-routes.ts:274-276), so a runner following the old text
could not open a session at all. The step now names `totalSize` and the 5 MiB
`chunkSize` floor (Math.max(reqChunkSize ?? 5242880, 5242880), line 280),
which silently rewrites totalChunks when a caller asks for less. Clause 4's
failed/expired enforce-or-remove finding from the same run is NOT touched
here — it is owned by #7667.
inline-grid-receipt-cells (#7669) — the run scored clause 3 `partial` against
a product that is correct. The clause demanded a resolved stored-file object
with receipt.name and an absolute http(s) URL; under ADR-0104 D3 the STORED
form is the bare opaque sys_file id and the resolved {id, name, size,
mimeType, url} shape is the read/expand form whose url is "derived, never
stored". The clause now asserts the opaque id plus the field-ownership stamp
(ref_object/ref_id/ref_field/acl) that proves the reference is managed rather
than a placeholder; title, step text and source follow, and a negative pins
the drift so a future run does not re-score the shipped shape as a defect.
Both items bump revision with a history entry per README.md lifecycle.
check:platform-checklist carries only the pre-existing `qa: UNCLASSIFIED`
red (#7347), unchanged by these edits.
…s a permanent, replayable recipe
Fixes the gap that would force blocked(fixture) on this area's entire authz
core every run: stock showcase has no private files-enabled parent — its only
`enable.files` object is showcase_project, which is public_read_write AND
readable through the showcase `everyone` baseline. Run #7635 closed that at
runtime, touching no repo file, and scored the items instead of blocking them.
This lands that provisioning permanently, checklist-side.
New AREA-LEVEL `fixtures` block on the area document, carrying one recipe
(`qa-scratch-authz`) as an ordered call sequence a runner can replay literally:
1. POST /api/v1/packages — scratch package
2. PUT /meta/objects/qa_vault?package=… — private + enable.files
3. PUT /meta/objects/qa_shared?package=… — public_read + enable.files
4. PUT /meta/objects/qa_nofiles?package=… — no files (FILES_DISABLED probe)
5. two fresh sign-ups, sys_user ids resolved system-side
6. sys_user_permission_set bindings: A read-not-edit, B baseline-only
Every call is grounded at file:line rather than transcribed from the run
narrative: the install body shape is pinned in client.test.ts:2144-2166;
`sharingModel`'s enum and `enable.files`' default-false live at
object.zod.ts:1827 and :281; `?package=` is read as query.package and threaded
to saveMetaItem at runtime/src/domains/meta.ts:262,319; the PUT is
manage_metadata-gated per ADR-0066 D1. Two honest knownGaps ride along — the
SDK's meta.saveItem does not send `?package=` (so steps 2-4 must be raw HTTP),
and the personas' exact permission-set bodies were never captured by the run,
so step 6 is specified by outcome and must be verified rather than trusted.
The recipe is written once at area level and referenced by the three consuming
items through `fixtures.provisioning.use`, so four items cannot drift into four
copies of the same twenty-call sequence. download-authz-both-sides,
read-inherits-parent-rls and attach-requires-parent-edit each opt in, restate
their stale "not demonstrable on stock seeds" knownGaps as closed-by-recipe
with the dogfood pin named as the fallback, and bump revision with history.
attach-requires-parent-edit additionally names qa_nofiles as the FILES_DISABLED
probe target instead of showcase_account, which is files-disabled only
incidentally and would go green for the wrong reason if that ever changed.
Checklist-side only, per the card's scope note: no showcase seed code, no
coverage.json. check:platform-checklist carries only the pre-existing
`qa: UNCLASSIFIED` red (#7347); check:nul-bytes green.
@vercel

vercelBot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 11, 2026 11:53am

Request Review

@huangyiirenehuangyiirene added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/m labels Aug 11, 2026 — with Claude
@huangyiirene
huangyiirene marked this pull request as ready for review August 11, 2026 12:10
@huangyiirene
huangyiirene added this pull request to the merge queueAug 11, 2026
Merged via the queue into main with commit a7ef0a7Aug 11, 2026
27 checks passed
@huangyiirene
huangyiirene deleted the claude/issue-7671-7669-7670-attachments-checklist branch August 11, 2026 12:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@huangyiirene@claude