A write that the rules hold does not run until a person approves it. That covers a write of 11 to 50 rows and any DELETE. An approval counts only with the person’s passkey signature, and Postgres checks that signature.
The flow
Section titled “The flow”-
The agent sends a write that the rules hold. The engine checks it will run the same way twice, runs it in a transaction to collect the exact rows it would touch, cascaded rows included, then rolls the transaction back. A preview can never commit.
-
The engine records the change as held (
APPROVAL_PENDING) and returnsAPPROVAL_REQUIREDto the agent. The result holds the approval id, the time it expires, a one-line summary of the write, a link and a command. -
The agent shows you the message, with the link on its own line. The summary reads like
Delete 1 row in public.leads + 2 cascaded rows (public.lead_notes 2). -
You open the link, check the rows and approve with your passkey. If you lose the link, run the command again:
basalt approve --agent NAME --open APPROVAL_ID -
The agent repeats the same write with the approval id. The write applies once Postgres confirms the signature, and the agent gets
APPLIED.
By default the agent waits up to 90 seconds on that repeat for you to approve, then tells you the write is waiting. A later repeat applies at once.
What the approval is bound to
Section titled “What the approval is bound to”The signature covers exactly what you saw:
- the statement and its parameters;
- for every affected row, cascades included: the table, the operation, the key, the full old row, and the new values of the columns the statement sets.
Defaults, identity values and values set by your app’s triggers are not covered. A held write that contains a value that changes between transactions, such as now(), is refused with STATEMENT_REFUSED and the reason NOT_REPEATABLE. The agent should pass such a value as a parameter.
When the write is applied, Postgres recomputes the statement, the parameters, the row count and a hash of the rows. If any differ from what you signed, the write does not apply and a new preview is needed.
Who can approve
Section titled “Who can approve”Only you. The agent can open the link but cannot sign. No PIN is typed into the chat, because anything typed to the agent passes through the model and its vendor’s logs.
The signature is a WebAuthn passkey signature (ES256, P-256). The public key of your passkey is stored in basalt_meta when you run basalt init. Postgres verifies each signature. The engine, the CLI, the page and any relay that carries the signed data are not trusted to approve: a signature they did not get from your passkey is refused.
The pages that ask for your signature are static. They load no third-party code and send nothing over the network. By default the engine serves them on http://localhost:8787.
Expiry and codes
Section titled “Expiry and codes”An approval expires 24 hours after the hold. No answer means blocked.
| Code | Meaning |
|---|---|
APPROVAL_REQUIRED |
The write is held. The result carries the link. |
APPROVAL_EXPIRED |
Nobody approved within 24 hours. |
APPROVAL_INVALID |
The approval does not exist, is used, belongs to another agent, or the rows changed. |
See Errors for what each one tells the agent to do.
BasaltDB · Apache-2.0