Skip to content

Sync API: change feed for Foundry catch-up, and field updates that merge - #927

Merged
keyxmakerx merged 3 commits into
mainfrom
claude/project-thread-bs6baq
Oct 3, 2026
Merged

keyxmakerx merged 3 commits into
mainfrom
claude/project-thread-bs6baq

Conversation

@keyxmakerx

@keyxmakerx keyxmakerx commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Requested by Key Maker · project thread

Fixes #908
Fixes #909
Security implication: new read endpoint returns ids only and is limited to DM-equivalent keys (Owner or co-DM), 403 otherwise; content is still fetched through the existing permission-checked reads.
Consumer-verified: n/a (no consumer yet; the Foundry module adopts it in keyxmakerx/Chronicle-Foundry-Module#114)
Mockup: n/a (nothing on screen)

What this changes

Chronicle now keeps a numbered list of what changed in each campaign (pages, notes, map pieces, calendar events; created/updated/deleted), and every live WebSocket message carries its number (seq). GET /api/v1/campaigns/:id/sync/changes?since=N lets the Foundry module catch up on everything it missed while offline, deletions included, instead of relying on /sync/pull, which can't see content changes. Step 2 of the approved plan in #907.

Also in this PR (step 1 of #907, approved on the Sign-offs before/after card): PUT /entities/:id/fields on the sync API now merges instead of replacing, so a Foundry actor edit no longer blanks Chronicle-only fields. New EntityService.MergeFields (absent keeps, null clears); the web attributes widget still uses the replacing UpdateFields. The module half of #909 (renames, debounce) is in the Foundry module PR.

Load-bearing lines

  • internal/plugins/syncapi/sync_change_bus.go: RecordingEventBus wraps the live bus where it is built in internal/app/routes.go, so every publisher is covered. Recording is best effort with a 500ms timeout; a failed record still publishes live.
  • internal/plugins/syncapi/sync_change_repository.go List: a 2-second settle window, because concurrent inserts can commit out of seq order and a cursor must not skip one still in flight.
  • resetRequired: true (with next = current head) when since is older than what retention kept (30 days); the client does a full resync.
  • Migration internal/plugins/syncapi/migrations/007_sync_changes: two new tables, additive, idempotent.

Honest deviations

  • The feed row is written after the domain write commits, not in the same transaction; a crash between the two loses that row (the client recovers on its next full resync).
  • Echo suppression is not in this PR; it moves to the module (Claude/fix navbar features page 8 r zu e #114), since publishers don't carry request context.
  • Repository SQL is not tested against a live database here (no DB in this environment); handler and bus are unit-tested with fakes.

Test plan

  • go test ./...: all packages pass; 5 new TestMergeFieldPatch cases; 19 change-feed tests (handler: since, limit clamp/default, hasMore, resetRequired, co-DM allowed, player 403, no key 403, bad since 400; bus: allowlist, seq stamping, publish when record fails)
  • go vet ./... clean
  • tools/check-migration-immutability.sh passes; migration-safety review: PASS
  • Routes snapshot regenerated (internal/wire/routes_snapshot.txt)
  • CI passes
  • Deploy: take the usual database backup first (new tables)

Tenet self-check

  • T-B1 security: DM-only endpoint, ids only
  • T-B2 plugin isolation: lives in syncapi; core migration untouched
  • T-B3 production UI: n/a
  • T-B4 dual-audience docs: syncapi and websocket .ai.md, docs/api/openapi.yaml

🤖 Generated with Claude Code

https://claude.ai/code/session_018uKa2E5HNdeubwBSD9Jn4n


Generated by Claude Code

claude added 2 commits October 2, 2026 20:23
…runing

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uKa2E5HNdeubwBSD9Jn4n
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uKa2E5HNdeubwBSD9Jn4n
@keyxmakerx keyxmakerx self-assigned this Oct 2, 2026
Foundry only sends the fields it maps, so a whole-map replace erased every
Chronicle-only field on each actor edit. Fixes #909 (Chronicle side).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uKa2E5HNdeubwBSD9Jn4n
@keyxmakerx keyxmakerx changed the title Sync API: change feed so the Foundry module can catch up after being offline Sync API: change feed for Foundry catch-up, and field updates that merge Oct 2, 2026
@keyxmakerx
keyxmakerx marked this pull request as ready for review October 3, 2026 00:09
@keyxmakerx
keyxmakerx merged commit 1bca08b into main Oct 3, 2026
10 checks passed
@keyxmakerx
keyxmakerx deleted the claude/project-thread-bs6baq branch October 3, 2026 00:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants