Repository navigation
feat(spec,service-storage,client)!: one upload-scope vocabulary for the upload requests, the sys_file select, the upload doors and the SDK (#22470) - #22647
Conversation
…ad requests, the sys_file select and the upload doors Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
… the UploadScope export reaches; add the changeset Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
…in the upload-scope pins Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
…load-scope-vocabulary
📓 Docs Drift CheckThis PR changes 3 package(s): 17 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 144 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 96915b3c49584baef1b66aa3e2a5508532114771 && git checkout 96915b3c49584baef1b66aa3e2a5508532114771
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5fb1746611439610b81d4a44846c22de15f90d6f 1b715cc92b90733e70a9215e27fc35aae66200f2 && git checkout -B drift-repro 5fb1746611439610b81d4a44846c22de15f90d6f && git merge --no-ff 1b715cc92b90733e70a9215e27fc35aae66200f2
node scripts/docs-audit/affected-docs.mjs --json 5fb1746611439610b81d4a44846c22de15f90d6f
|
Contract reviewServed-tier: Inputs: card #22470 (body; comments ① Derived judgments
② Semver level
③ Boundary flags
Nothing is escalated. Implemented-by: VERDICT: PASS Read and polled at 2026-10-10T08:55Z. |
…load-scope-vocabulary Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
The os-regen driver kept the branch's side of content/docs/references/index.mdx; main's side is restored and the page regenerated, so it counts both main's new data schema and this branch's UploadScope (1516 schemas). Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
Fixes #22470
Clause-②: yes (narrowing)
One upload-scope vocabulary, declared once in
@objectstack/specand read by the two upload requests, thesys_filescope select, the two upload-start doors and the SDK'sstorage.upload. A scope outside it is now a caller error, answered400 INVALID_REQUESTnaming the allowed values, instead of the data engine'sinvalid_optionrelayed as500 INTERNALwith a message telling the operator to restore the data engine.Direction: triage
6080435724as amended by6081565553(the vocabulary is thesys_fileselect, notStorageScopeSchema), standing per6092602285; the client half ruled option A in seat answer6095219171.What changes
@objectstack/spec: newUploadScopeSchemaand typeUploadScopebeside the upload request schemas (api/storage.zod.ts, exported from@objectstack/spec/api):user,tenant,private,temp,attachments.GetPresignedUrlRequestSchema.scopeandInitiateChunkedUploadRequestSchema.scoperead it; theuserdefault and the description are kept.StorageScopeSchemais not touched.@objectstack/service-storage,SystemFile: thescopeselect builds its options fromUploadScopeSchema.options, in the enum's order. The labels and the comment explainingattachmentsstay local, in a label map keyed byUploadScope, so an enum member added without a label failstsc. The stored values and labels are unchanged (pinned byte-equal to the old literal list).@objectstack/service-storage,registerStorageRoutes: the one scope gate both upload-start handlers already asked (requireAcceptedUploadScope, from thepublicretirement) now asksUploadScopeSchema.safeParseand refuses everything else: any string, a case variant,null, a number. It runs before the size gate, before any row, URL or backend call. One gate, one status, one code. The formerpublic-only refusal is folded into it rather than left beside it, keeping its message family and, forpublic, itsacl 'public_read'remedy. An omitted scope is still the defaultuser, and a real engine fault still answers500.@objectstack/client:storage.uploadtypes itsscopeparameterUploadScopeinstead ofstring(one parameter type, one type import).getPresignedUrlandinitChunkedUploadtake the request types and narrow with them.upload-request-scope-closed,registry.tsregenerated. Changeset: specminor, clientminor, service-storagepatch, registered disposition. Nospec-changes.jsonor upgrade-guide regeneration is owed:check:spec-changesandcheck:upgrade-guideproject in memory and are green.api-surface,export-origins,declaration-mapandjson-schema.manifestshards forapi, and the two reference pages undercontent/docs/references(gen:docs).type-alias-convention.pin.test.tsgains the isomorphic pin forUploadScopeSchema(773 → 774, with its receipt), whichcheck:spec-parsed-aliasreads as its registry.Evidence (head
3e8227f5df, after mergingorigin/mainat1b99388505throughos-regen-merge.sh)All runs under
os-verify-lock.packages/spec/src/api/storage.test.ts: the enum lists the five in order and refusespublic. Each request accepts all five, keeps theuserdefault, and refusesavatars,public,Userand the empty string at parse, with issue codeinvalid_valueon pathscope.packages/services/service-storage/src/upload-scope-vocabulary.test.tsruns on a realObjectQLoverSqlDriver(sqlite in memory) with the realSystemFileandSystemUploadSession:scope: 'avatars'on each door →400 INVALID_REQUEST, the message names the five values, and there are zerosys_filerows, zero session rows, no presign and no backend initiate call;null,7andUser→400;200, and the engine stores it;user→500 INTERNAL.--project local: 642 files, 19157 passed, 1 todo.--project repo: 54 files, 915 passed.scripts/ablation-replace.mjs, wrap mode with trap restore, at3e8227f5df). The refusal condition inrequireAcceptedUploadScopeis replaced byreturn true: anchor 1 → 0, marker 0 → 1 → 0.avatarspin goes red withexpected 500 to be 400, which is the card's defect reproduced over the real engine. Thenull/ number / case pin goes red withexpected 200 to be 400, and thepublicpin goes red.b355ea0a5b== HEAD, withgit diff HEADempty.@objectstack/clienttypecheck exited 2 with TS2322 (stringnot assignable to the five-value union) instorage.upload, and@objectstack/client#buildfailed its DTS step. So the rebuilt spec declarations were what the consumer read. After the edit it exits 0.dispatch-gates --commandson the final diff derives 119 commands. All 119 ran at3e8227f5dfwith exit 0, includingcheck:dts-closure,check:skill-examples,check:dual-build-cjs-loads,check:i18n(service-storage: 7 bundles in sync) andcheck:type-check-debt.--ran: 119 derived, 119 run, 0 NOT MEASURED, 0 UNRUN.check:generated: 15 of 15 up to date after the merge.Reach (measured)
storage.upload(defaultuser), two README examples passinguser, and the dogfood upload sites: 7 sendattachmentsand 2 senduser. The service-storage and organizations tests send only vocabulary values. Nothing sends an off-vocabulary scope through a door.examples/**andapps/**: 0.20c6d351adand at main12ff256313: 2 adapter callers. The console sends no scope, and the record attachments panel sendsattachments. There are 0 calls to the SDK upload and 0 imports of the request types, so the Console Pin Gate is unaffected.packages/console/distin this checkout).Acceptance notes
nullscope, which used to be read as the defaultuser. This agrees with the published schema, which refusesnullat parse. It is pinned, and ruled to stand in6095219171. No measured caller sendsnull.STEP18_RATIONALEfragment, which was ruled not owed.sys_upload_session.scopestays a free text column. It mirrors the file row's scope, and only the chunked door writes it, after the gate. Noted, not filed.Generated by Claude Code