Repository navigation
fix(spec)!: the chunked-upload init request declares filename and mimeType non-empty - #27887
Conversation
…e non-empty InitiateChunkedUploadRequestSchema.filename and .mimeType carry min(1), so the declaration says what the init door has always refused. The door keeps its presence check ahead of the scope gate: status, code and message are unchanged. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2fi9zfJhUTubyJYb6V
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2fi9zfJhUTubyJYb6V
…unked-init-filename-min1 Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2fi9zfJhUTubyJYb6V
📓 Docs Drift CheckThis PR changes 2 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 169 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 d5745438bab10523288d43fd94de3e7af521416d && git checkout d5745438bab10523288d43fd94de3e7af521416d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin da94c11337fdb4c37ee809e9748a76791a5199cc f5bb94f94e92b0929e3c415f692e4e50cbff8308 && git checkout -B drift-repro da94c11337fdb4c37ee809e9748a76791a5199cc && git merge --no-ff f5bb94f94e92b0929e3c415f692e4e50cbff8308
node scripts/docs-audit/affected-docs.mjs --json da94c11337fdb4c37ee809e9748a76791a5199cc
|
|
domain:spec#4 · marchtian · session_017ihL2fi9zfJhUTubyJYb6V 席位核对 · ACCEPT (lane part) · card #27801 · 2026-10-30T02:43Z
Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (HTTP 429 "You've reached your Fable limit", 2026-10-29T17:05Z) · head f5bb94f |
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract-review-claim: Generated by Claude Code |
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract reviewServed-tier: Inputs read: card #27801 (body, triage 6511379936, claim 6512389552, os-dev-report 6513641996), PR #27887 (body, 6 files, net diff against ① Derived judgments
② Semver levelChangeset Clause-②: no (narrowing) — matches what the diff publishes. ③ Boundary flags
④ Design judgment
Findings: no must-fix. No should-fix on the Implemented-by: VERDICT: PASS Generated by Claude Code |
Requested by 田騏 · project thread
Fixes #27801
Clause-②: no (narrowing)
Metadata-protocol: none
维护者速读
InitiateChunkedUploadRequestSchema)现在声明filename、mimeType不能为空(min(1)),参考文档同步标注“non-empty”。packages/spec/src/api/storage.zod.ts(两个min(1))、packages/services/service-storage/src/storage-routes.ts(入口的存在性检查为何保留的注释)、.changeset/27801-chunked-init-filename-min1.md(BREAKING 说明与 ADR-0087 处置)。pnpm --filter @objectstack/spec exec vitest run src/api/storage.test.ts,预期 65 个测试全过,其中包括“refuses an empty filename / mimeType”两条。Independent review: none run (quota); the PR waits for the seat's contract-review hand-off.
What changes
InitiateChunkedUploadRequestSchema.filenameand.mimeTypedeclaremin(1), and their descriptions read "Original filename (non-empty)" and "File MIME type (non-empty)". The published JSON Schema now carriesminLength: 1on both keys, andcontent/docs/references/api/storage.mdxis regenerated (2 lines).POST /api/v1/storage/upload/chunked) is unchanged in behaviour. Its presence check!body.filename || !body.mimeType || body.totalSize == nullstays where it is. A comment now says that it refuses nothing the schema parse accepts.27801-chunked-init-filename-min1.md,@objectstack/specpatch,fix(spec)!, with both declaration lines and the ADR-0087 dispositionnot-required (no-migration-prescription). This follows the precedent of the other request-declaration narrowings (26817-http-find-query-params-count-select.md,27683-search-fields-names-a-field.md).What happens to the door's truthiness check. I kept it, for three measured reasons:
min(1)the check is a strict subset of the schema's refusal set. A missing,'',null,0orfalsevalue failsz.string().min(1)as well, so the two never disagree about what is refused.filenamesent with an off-vocabularyscopewould get the scope refusal instead of the presence refusal. That is the ordering change the order forbids.filename/mimeTypefromfilename, mimeType, and totalSize are requiredtochunkedInitRefusalMessage's text. A missingtotalSizewould still need its own check ahead of the scope gate. Keeping the check means status, code, message and ordering stay byte-identical, which is what the ruling's "no behaviour change at the door" asks for.Tests
packages/spec/src/api/storage.test.ts:refuses an empty %s, naming that keycoversfilenameandmimeType. Each assertssuccess: false, exactly one issue,pathequal to the key, andcodeequal totoo_small.CONTROL: a one-character filename and mimeType parseasserts that'a'/'b'parse.packages/services/service-storage/src/storage-routes.test.ts:REFUSED 400 INVALID_REQUEST: an empty %s, which the declared schema refuses too, with no row writtencoversfilenameandmimeType. Each asserts status400,success: falseanderror.codeINVALID_REQUEST, thatInitiateChunkedUploadRequestSchema.safeParserefuses the same body, and that neithercreateFilenorcreateSessionwas called. No earlier test sent an emptyfilenameormimeTypeto this door. The existing chunked-init cases covertotalSize,chunkSizeandmetadataonly.Ablation (spec): removing each min(1) turns its own case red
Run with
node scripts/ablation-replace.mjs. The anchor hit once each time and the file was restored to its HEAD blob (e4b1a7313842), withgit diff HEADempty.filename: z.string().min(1)replaced byfilename: z.string():× refuses an empty filename, naming that key, Tests 1 failed / 64 passed (65).mimeType: z.string().min(1)replaced bymimeType: z.string():× refuses an empty mimeType, naming that key, Tests 1 failed / 64 passed (65).The door test is not ablated, because the door's behaviour did not change. Its schema-agreement assertion reads
@objectstack/spec/apithrough the builtdist/.Local runs
pnpm --filter @objectstack/spec exec vitest run src/api/storage.test.ts: Test Files 1 passed, Tests 65 passed.pnpm --filter @objectstack/service-storage test: Test Files 71 passed (71), Tests 1311 passed (1311).pnpm --filter @objectstack/service-storage typecheck: exit 0, andcheck:test-typecheckOK.pnpm --filter @objectstack/spec check:generated: 1 of 14 stale (content/docs/references/**) before--fix.--fixregeneratedgen:docsand re-checked green. After mergingorigin/main, the spec was rebuilt and all 14 generated artefacts are up to date.'':filename '': schemasuccess: true, door400 INVALID_REQUEST.filename '': schemasuccess: false, door400 INVALID_REQUEST.mimeType.Acceptance notes
GetPresignedUrlRequestSchema.filenameand.mimeType(storage.zod.ts:67-68) are still barez.string().POST /api/v1/storage/upload/presignedrefuses''for either with400 INVALID_REQUEST(storage-routes.ts:968-969), so this is the same declared-versus-enforced gap on the sibling door. The probe above shows it. That door does not parse its body with the schema at all. This belongs with the card split from storage: the chunked-upload init door does not enforce its declared totalSize/chunkSize, and the presigned request schema declares size as any number #27549 part (b).filename/mimeTypeis still admitted by both the schema and the door. The two agree, and this PR leaves that alone.Verification
All readings below are on head
f5bb94f94e, which is the branch merged withorigin/mainat755e28500.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 109 commands, and all 109 were run with each exit code captured before any pipe.dist/):check:skill-examples,check:dual-build-cjs-loadsandcheck:i18n. All 3 exited 0 after a workspace build.--ranreconciliation: "109 derived, 109 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero — all 109 recorded an exit code and none of them is 3)".node scripts/check-adr-0087-registration.mjs --base origin/mainexited 0 and reads the changeset as[BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription).pnpm --filter @objectstack/dogfood test, under the lock, closure built) gave VERDICT command-exit 1: Test Files 3 failed / 352 passed / 3 skipped (358), Tests 4 failed / 2864 passed / 20 skipped (2888). All 4 failures still fail when their files are run alone, and none touches storage:error-sentences-live.dogfood.test.tsand 1 inobject-hook-lifecycle-scratch.dogfood.test.tsassert that the/datadoor answers an undeclared hook abort with nocode. Commit21c4bdd804onmainmade that answerHOOK_ABORTED.platform-app-object-entry-views.test.tsnamessys_exchange_rateas undeclared. That object arrived onmainwith194e66c028.🤖 Generated with Claude Code
https://claude.ai/code/session_017ihL2fi9zfJhUTubyJYb6V