mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 23:01:09 +02:00
feat(sync): thread actor through webapp sync client
Matches the wire contract the Go server just learned to persist. Every
PendingChange now carries the actor through the Dexie row into the POST
payload; SyncChange on the receiving side accepts an opaque actor blob.
- `sync.ts`
- `SyncChange.actor?: Actor` on the wire type; documented as opaque +
back-compat with pre-actor clients
- `PendingChange.actor?: Actor` — the pending-changes row already gets
an actor stamped by the Dexie hook, the type now reflects it
- `isValidSyncChange` accepts actor as an object or undefined, never
asserts internal shape (the payload is opaque by design)
- Push payload includes `actor: p.actor` alongside the other fields
- `module-registry.test.ts` — `pendingProposals` added to INTERNAL_TABLES.
It's a local-only staging table that intentionally does NOT sync
(approved writes run the underlying tool, which syncs normally).
Follow-up still open: when applyServerChanges writes a record from an
incoming change, stamp `__lastActor` + `__fieldActors` from the incoming
actor so the Workbench timeline attributes cross-device writes
correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bfa1c0260f
commit
615b1c23c3
2 changed files with 24 additions and 1 deletions
|
|
@ -37,7 +37,16 @@ import { db } from './database';
|
|||
// ─── Internal Dexie tables that are intentionally NOT in SYNC_APP_MAP ───
|
||||
// These hold local-only state (sync metadata, retry queues, activity log)
|
||||
// that must never leave the device.
|
||||
const INTERNAL_TABLES = new Set(['_pendingChanges', '_syncMeta', '_eventsTombstones', '_activity']);
|
||||
const INTERNAL_TABLES = new Set([
|
||||
'_pendingChanges',
|
||||
'_syncMeta',
|
||||
'_eventsTombstones',
|
||||
'_activity',
|
||||
// Local-only AI Workbench staging; approvals run the underlying tool
|
||||
// which writes via its module's sync path — proposals themselves never
|
||||
// leave the device.
|
||||
'pendingProposals',
|
||||
]);
|
||||
|
||||
describe('module-registry — structural invariants', () => {
|
||||
it('every appId is unique across module configs', () => {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import {
|
|||
} from './database';
|
||||
import { isQuotaError, cleanupTombstones, notifyQuotaExceeded } from './quota';
|
||||
import { emitSyncTelemetry, categorizeSyncError } from './sync-telemetry';
|
||||
import type { Actor } from './events/actor';
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -70,6 +71,13 @@ export interface SyncChange {
|
|||
fields?: Record<string, FieldChange>;
|
||||
data?: Record<string, unknown>;
|
||||
deletedAt?: string;
|
||||
/**
|
||||
* Attribution of who triggered the write. Opaque structured value
|
||||
* that survives the round trip through mana-sync / Postgres via a
|
||||
* JSONB column. Consumers treat a missing actor as `{ kind: 'user' }`
|
||||
* for back-compat with pre-actor clients.
|
||||
*/
|
||||
actor?: Actor;
|
||||
}
|
||||
|
||||
interface PendingChange {
|
||||
|
|
@ -81,6 +89,7 @@ interface PendingChange {
|
|||
fields?: Record<string, FieldChange>;
|
||||
data?: Record<string, unknown>;
|
||||
deletedAt?: string;
|
||||
actor?: Actor;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
|
|
@ -132,6 +141,10 @@ export function isValidSyncChange(v: unknown): v is SyncChange {
|
|||
if (c.deletedAt !== undefined && typeof c.deletedAt !== 'string') return false;
|
||||
if (c.eventId !== undefined && typeof c.eventId !== 'string') return false;
|
||||
if (c.schemaVersion !== undefined && typeof c.schemaVersion !== 'number') return false;
|
||||
// `actor` is opaque — we deliberately don't assert its shape here. A
|
||||
// malformed actor doesn't corrupt data; worst case the Workbench shows
|
||||
// "unknown" for that change.
|
||||
if (c.actor !== undefined && (typeof c.actor !== 'object' || c.actor === null)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1060,6 +1073,7 @@ export function createUnifiedSync(
|
|||
fields: p.fields,
|
||||
data: p.data,
|
||||
deletedAt: p.deletedAt,
|
||||
actor: p.actor,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue