fix(core): make _attachment@1 mimeType a property of the fileId (#65)#94
Merged
Conversation
First-recorded mimeType now wins: a later upload of already-stored bytes must declare a matching mimeType or is rejected with StackValidationError, closing the nondeterministic-Content-Type / dedup-escalation hole. Once a metadata record exists, fileId, size, and mimeType are immutable — filename is the only field update() may still change, per the issue's adopted amendments folding in fileId/size protection and mimeType immutability-after-create. Enforced in Stack.create()/Stack.update(), so ScopedStack inherits it through the same delegation path.
…he wire (#65) Adds fixtures for the create-time conflict check (matching mimeType succeeds, conflicting mimeType returns 422/validation) and the post-create mimeType-immutable-on-update rejection, so adapter-api and any server implementation share the same pinned wire behavior.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #65.
Stack.create(): creating an_attachment@1record now looks up existing metadata for the samefileId(cursor-walked past the 50-record page boundary per Core code treats one query() page as the complete result set (grants, attachment metadata, uploader checks) #50) and rejects a conflictingmimeTypewithStackValidationError(422). The first upload for afileIdestablishes it; a later matching upload still gets its own record (ownfilename/entityId).Stack.update(): once an_attachment@1record exists,filenameis the only mutable field — folding in the issue's adopted amendments:fileId/sizechanges are rejected, andmimeTypeis rejected if the patch touches it at all, even to restate the current value.Stack, soScopedStack.create()/update()inherit them automatically through the existing delegation path — no separate wiring needed.restoreVersion()needed no changes: sinceupdate()blocks any change to those three fields, every version snapshot of an attachment record already shares identical values by construction.docs/spec.md: updated the Attachment record section, the Attachments how-to section, and the wire Download section to state the first-recorded-wins/immutability rules, and dropped the now-inaccurate "each upload gets its own mimeType" language.packages/conformance-fixtures: added fixtures pinning the wire behavior (matching-mimeType create succeeds, conflicting-mimeType create → 422/validation, mimeType-touching update → 422/validation) soadapter-apiand any server implementation share the same pinned contract.Out of scope (per the issue): the served
Content-Typeresolution and?contentType/dangerous-type forcing (#66) live inhaverstack/server. This PR only makes the write-path invariant that determinism depends on.Test plan
pnpm -r run test— 566 tests passing across all packages, including 10 new tests inpackages/core/tests/stack.test.ts(conflicting/matching mimeType on create, conflict detection past the pagination boundary, a soft-deleted first record still establishing the type, the two-uploader/one-file scenario, and the four immutable-field-on-update cases) and 3 new conformance fixtures exercised bypackages/adapter-api/tests/conformance.test.tspnpm -r run lintpnpm -r run typecheckfor the changed packages (core,conformance-fixtures) — some other packages in the monorepo (wire-types,record-adapter-sqlite,record-adapter-sqljs) fail typecheck on a clean checkout too, pre-existing and unrelated (they need their workspace dependencies built first)Generated by Claude Code