4.0 KiB
Board API Key Audit Boundary
Board API key allow decisions authorize a request only when the matching security disposition is durable. PostgreSQL mutations and non-database effects have different atomicity constraints, so this document states the boundary explicitly.
PostgreSQL guarantee
server/src/security/board-key-audit-coupling.ts stages an allow disposition in
the request context.
- A mutation inside
db.transactionwrites the allow disposition on that transaction before the first domain mutation. Both commit or both roll back. - Direct
db.insert,db.update,db.delete, and mutatingdb.executecalls are replayed lazily inside a transaction with the staged disposition. An audit insert failure therefore rolls back the direct domain mutation. - Direct mutation-builder operations that cannot be replayed safely fail before
issuing SQL and must be moved into an explicit
db.transaction. - There is no successful untransacted fallback. Response settlement persists only successful no-mutation dispositions; it cannot convert an uncoupled mutation into success.
This boundary deliberately classifies unknown raw SQL as mutating. Read-only
raw SQL is limited to statements beginning with SELECT, SHOW, EXPLAIN,
TABLE, or VALUES.
Non-database side-effect review
The runtime route inventory denies undeclared board-key routes. Explicit denials also cover the MCP surface, tool calls and sessions, plugin action/bridge/data and webhook surfaces, skill-test execution, agent instruction-file mutation, and auth/claim/invite surfaces. Board keys can still reach authorized management routes whose handlers may combine PostgreSQL state with effects that PostgreSQL cannot roll back.
| Reachable route family | Effect outside PostgreSQL | Residual failure mode |
|---|---|---|
| Artifacts, attachments, company import/export, skills | Object storage or local filesystem writes/removals | An audit or domain rollback can leave an orphaned object/materialized tree; compensation can also fail. |
| Agents, heartbeat/runtime, workspaces, environments | Process start/cancel, git/worktree changes, or provider calls | A crash can occur between durable intent and execution/acknowledgement, causing a missed action or a retry. |
| Plugin and tool management | Plugin lifecycle, OAuth/provider, or network activity | Remote state cannot join the database transaction and may be applied despite a later local failure. |
| Activity publication, plugin events, assignee wakeups | In-process publication or adapter dispatch | A post-commit crash can miss delivery; retry after an ambiguous acknowledgement can duplicate delivery. |
The database audit coupling does not claim atomic rollback for these effects. Side-effecting handlers must use the following pattern when the effect matters to correctness or security:
- Persist the domain change, board-key allow disposition, and an intent/outbox row in one transaction.
- Execute the external effect only from the committed intent.
- Give the effect a stable idempotency key derived from the intent, not from a delivery attempt.
- Persist completion or failure so a worker can retry ambiguous outcomes.
- Use compensating cleanup for object/file/provider state and treat cleanup as retryable, not guaranteed rollback.
Existing durable wake requests and idempotency keys reduce duplicate/lost-work risk where they are used. Live UI/plugin publications remain best effort. A successful no-mutation request is audited during response settlement; if that audit write fails there is no PostgreSQL domain commit to roll back. A handler that performs only an external effect must therefore not rely on no-mutation settlement: it needs a durable intent/outbox boundary before the effect.
When a route becomes board-key reachable, security review must identify every filesystem, object-store, process, adapter, plugin, and outbound-network effect and either prove an existing outbox/idempotency boundary or record the residual risk and remediation owner.