Skip to content

fix(core): split updateRecord into intent-revealing adapter operations#81

Merged
cuibonobo merged 2 commits into
mainfrom
claude/design-review-52-orzusi
Jul 10, 2026
Merged

fix(core): split updateRecord into intent-revealing adapter operations#81
cuibonobo merged 2 commits into
mainfrom
claude/design-review-52-orzusi

Conversation

@cuibonobo

Copy link
Copy Markdown
Member

Summary

  • StackRecordAdapter.updateRecord() was a grab-bag: Stack funneled content patches, permission replacement, version restore, and migration commits through one Partial<StackRecord> method, and APIAdapter forwarded whatever it was given straight into a content-only PATCH body — corrupting record fields (typeId, version, updatedAt) into content on a spec-conforming server, and silently breaking null-deletion, setPermissions(), restoreVersion(), and migrateAll() over the wire. associate(), dissociate(), delete(), and undelete() carried the same bug via a trailing updateRecord() call used only to bump version/updatedAt.
  • Replaces updateRecord() with patchContent(), setPermissions(), restoreVersion(), and commitMigration() on StackRecordAdapter — each maps 1:1 to its own wire endpoint (PATCH /records/:id, PUT /records/:id/permissions, POST /records/:id/restore/:version, new POST /records/:id/migrate) and bumps version internally, so no mutation needs a second round-trip through a different intent's endpoint.
  • Content merging now goes through a shared RFC 7396 helper (applyMergePatch) so Stack's client-side validation merge and every adapter's patchContent() implementation produce identical results.
  • Updates Stack, combineAdapters, MemoryAdapter (core), APIAdapter (adapter-api), SQLiteRecordAdapter (record-adapter-sqljs), and LocalAdapter (adapter-local) to the new contract.
  • Updates docs/spec.md: documents the new /migrate endpoint and the permissions request/response envelope ({ "permissions": [...] }).

Closes #52.

Test plan

  • pnpm -r build — all packages build
  • pnpm -r typecheck — clean across all 6 packages
  • pnpm -r lint — clean
  • pnpm -r test — all existing tests pass unchanged (258 core, 76 sqljs, 57 adapter-api, 17 adapter-local, 12 blob-adapter-disk), plus new coverage added for patchContent, setPermissions, restoreVersion, commitMigration, and version-bump-on-mutation for associate/dissociate

Generated by Claude Code

claude added 2 commits July 10, 2026 02:58
StackRecordAdapter.updateRecord() was a grab-bag: Stack funneled content
patches, permission replacement, version restore, and migration commits
through one Partial<StackRecord> method, and the API adapter forwarded
whatever it was given straight into a content-only PATCH body — corrupting
record fields into content on a spec-conforming server, and silently
breaking null-deletion, setPermissions(), restoreVersion(), and
migrateAll() over the wire. associate(), dissociate(), delete(), and
undelete() carried the same bug via a trailing updateRecord() call used
only to bump version/updatedAt.

Replace updateRecord() with patchContent(), setPermissions(),
restoreVersion(), and commitMigration() — each maps 1:1 to its own wire
endpoint (PATCH /records/:id, PUT .../permissions, POST .../restore/:version,
new POST .../migrate) and bumps version internally, so no mutation needs a
second round-trip through a different intent's endpoint. Content merging
now uses a shared RFC 7396 helper (applyMergePatch) so local adapters and
Stack's validation merge identically.

Updates StackRecordAdapter, Stack, combineAdapters, MemoryAdapter,
APIAdapter, SQLiteRecordAdapter, and LocalAdapter accordingly, plus spec
docs for the new /migrate endpoint and the permissions envelope.

Refs #52.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012KjkNN8wW5Abm5pEDVkKHM
The new patchContent() implementation added a real (value) import of
applyMergePatch from @haverstack/core — the package's first runtime
dependency on core's build output. Without a build step, vite couldn't
resolve it, so CI's test job (which runs before build) failed with
"Failed to resolve entry for package @haverstack/core". Add the same
src-aliasing vitest.config.ts already used by adapter-api and adapter-local.

feat(conformance-fixtures): add shared wire-protocol fixtures

Adds @haverstack/conformance-fixtures: request/response pairs for every
record-mutation endpoint (create, patchContent, delete/undelete,
associate/dissociate, setPermissions, restoreVersion, commitMigration),
pinning down the wire shapes #52 fixes — in particular that PATCH
/records/:id carries a content-only patch, never record fields.

The package is pure data with no test-framework dependency, so it can be
imported by both this repo and a server implementation's test suite.
adapter-api's new tests/conformance.test.ts drives APIAdapter through
every fixture; haverstack/server can drive its own handlers through the
same fixtures separately.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012KjkNN8wW5Abm5pEDVkKHM
@cuibonobo cuibonobo merged commit 2545a9e into main Jul 10, 2026
5 checks passed
@cuibonobo cuibonobo deleted the claude/design-review-52-orzusi branch July 10, 2026 13:34
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.

PATCH contract mismatch: spec defines content-only merge patch, APIAdapter sends record-field envelopes

2 participants