Skip to content

fix(core): make _attachment@1 mimeType a property of the fileId (#65)#94

Merged
cuibonobo merged 2 commits into
mainfrom
claude/issue-65-design-review-kyt29s
Jul 14, 2026
Merged

fix(core): make _attachment@1 mimeType a property of the fileId (#65)#94
cuibonobo merged 2 commits into
mainfrom
claude/issue-65-design-review-kyt29s

Conversation

@cuibonobo

Copy link
Copy Markdown
Member

Summary

Closes #65.

  • Stack.create(): creating an _attachment@1 record now looks up existing metadata for the same fileId (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 conflicting mimeType with StackValidationError (422). The first upload for a fileId establishes it; a later matching upload still gets its own record (own filename/entityId).
  • Stack.update(): once an _attachment@1 record exists, filename is the only mutable field — folding in the issue's adopted amendments: fileId/size changes are rejected, and mimeType is rejected if the patch touches it at all, even to restate the current value.
  • Both checks live in Stack, so ScopedStack.create()/update() inherit them automatically through the existing delegation path — no separate wiring needed.
  • restoreVersion() needed no changes: since update() 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) so adapter-api and any server implementation share the same pinned contract.

Out of scope (per the issue): the served Content-Type resolution and ?contentType/dangerous-type forcing (#66) live in haverstack/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 in packages/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 by packages/adapter-api/tests/conformance.test.ts
  • pnpm -r run lint
  • pnpm -r run typecheck for 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

claude added 2 commits July 14, 2026 17:28
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.
@cuibonobo cuibonobo merged commit 57e64b5 into main Jul 14, 2026
5 checks passed
@cuibonobo cuibonobo deleted the claude/issue-65-design-review-kyt29s branch July 14, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Served Content-Type is nondeterministic when multiple _attachment records share a fileId — make mimeType a property of the bytes

2 participants