The gateway (src/lib/agent/gateway.ts) is now the one seam every write
goes through: authorize() (NEVER_WRITABLE + dn_batches lock_*/stage),
planOps (schema validation via schema-cache.ts's new validateOp,
before-snapshots, $ref cross-checks, relation-id existence, the
sites/employees duplicate rule, capped at 500 ops), createPendingAction,
applyAction and undoAction. propose_changes is a registered tool;
apply/reject/undo are confirm-card routes under
/api/agent/actions/[id]/, same-origin only.
/api/chat now streams a full action card (`event: action`) while the
model itself only ever sees {actionId, status, problems} — the same
split extractCharts already does for chart numbers (extractActionCard).
The next user turn gets the outcome of the prior turn's action(s)
prepended to what the model reads (ChatEntry has no system role, so
this is the pragmatic equivalent of a system note). chat-view.tsx
renders action-card.tsx per assistant turn, hydrating from
GET /api/agent/actions/[id] when a thread reopens.
Deviations, both noted for the smallest-sensible-adjustment reason:
- Batches run as sequential individual PocketBase calls, not
pb.createBatch() — that's what makes $ref resolution exact rather
than dependent on the batch API's own (version-sensitive) request-
chaining syntax.
- Re-applying a "failed" action isn't specially deduped against ops
that already partially succeeded; only the single-attempt path is
implemented in this session.
Live check skipped: ask_threads/ask_messages/ask_log still don't exist
in the target PocketBase (Task 1's --apply is the user's own step).
Covered by mocked unit tests instead — 17 gateway tests plus the
confirm-card routes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>