EDR 0021 Status: accepted Implementation: partial
Connect as the operator where the database can, and never let a driver retry a write
Pilots use pooled, dynamically-credentialled connections. Where the engine allows, a session authenticates as the individual operator via IAM so the database's own audit names them; reads may route to replicas.
What exists: One rule of this record is built and tested: transparent retry is OFF for writes, so a commit whose answer never arrives is reported as indeterminate rather than replayed — internal/pilot, with the classification that tells a refused commit from a lost one. Nothing else is: no connection identity, no read routing, no pooling policy, and no session settings pinned on a target.
TL;DR
Four decisions about how a Pilot actually reaches a database.
- Credentials are minted per connection, never stored. On AWS, an RDS or Aurora IAM auth token generated from the Pilot's task role; on GCP, Cloud SQL IAM authentication; elsewhere, a dereferenced secret as a documented carve-out (EDR-0005).
- Where the engine can, the session authenticates as the individual operator. A role may declare
identity: per_operator, in which case the database user is derived from the operator's federated subject and the token is minted for that user.session_userin the database is then the human, so the database's own audit trail names them independently of Marque's logbook — a second record that survives a Marque compromise. - Transparent driver retry is disabled on every execution transaction. A failover-aware wrapper
is used for connection management and topology awareness, but a driver that silently replays a
write after failover breaks the execution fence
(EDR-0011) by applying a statement Marque did not
count. Failover mid-execution must surface as
indeterminate, not as a quiet retry. - Reads may go to a replica, under a freshness bound. Read-only statements route to a reader endpoint when the request tolerates it; anything in a task that has already written goes to the writer, gated on the replica having applied that write's position (ZFN-25).
Every session sets application_name to the marque identifier, so a DBA looking at a running query
can see what authorised it without asking anybody.
Context
EDR-0005 settled where credentials live and EDR-0006 settled that a role's grants are the outer bound. Neither says how a connection is actually obtained, pooled, or routed — and the answers turn out to have security consequences rather than merely operational ones.
The strongest of those is identity. A role like settings_writer is a shared database identity: the
database sees settings_writer doing things, and which human is knowable only from Marque's
logbook. That is a single point of truth for attribution, held by the system whose compromise is
exactly the scenario in which you would most want a second opinion. Cloud IAM database
authentication makes a better answer available: mint the token for a database user that corresponds
to the person, and the target's own logs — pg_stat_activity, pgaudit, the engine's audit
extension — attribute independently. Two records that must agree is meaningfully stronger than one.
The retry point is the non-obvious one. Modern failover-aware driver wrappers exist precisely to hide a failover from the application, and hiding it is usually correct. Here it is dangerous: Marque's entire double-apply defence assumes that Marque decides when something is retried (EDR-0011). A driver that transparently replays a write on a new writer applies a statement outside that accounting, and the operator's budget of one has silently become two. The convenience feature and the safety property are in direct conflict, and the safety property wins (ZFN-2).
Decision
Credentials and pooling
Connections are pooled per (target, role, identity). A pool is bounded and the sum of a Pilot's
pools is bounded below the target's connection limit — a tool that exists to protect production must
not be able to exhaust its connections
(ZFN-18).
Auth tokens are short-lived, so a pool refreshes credentials on its own schedule and re-mints on an authentication failure rather than failing the request (ZFN-35). On AWS the connection is managed by the failover-aware advanced driver wrapper, configured for IAM authentication and topology awareness, with the retry behaviour below.
Operator identity at the database
A role declares one of:
identity |
Database user | Attribution | Cost |
|---|---|---|---|
shared |
the role's own user | Marque's logbook only | one pool per role; the default, and the only option where IAM auth is unavailable |
per_operator |
derived from the operator's federated subject | the database independently names the human | a pool per operator; user provisioning per operator |
pooled_with_role |
the role's user, then SET ROLE to an operator role |
current_user names the human; session_user does not |
one pool; weaker — see below |
per_operator is preferred wherever the engine and provisioning allow, and is the recommended
default for sensitive and critical roles.
pooled_with_role is a documented compromise, not an equivalent. SET ROLE is reversible by the
session, so it narrows privilege and attributes only as long as nothing in the session resets it.
The checkable-statement grammar forbids SET ROLE and unrecognised function calls
(EDR-0007), which is what makes it defensible at all —
but it is a defence in Marque's parser rather than in the database, and that is the wrong place for
one (EDR-0006 exists precisely to avoid relying on it).
Use it for high-volume standing-order traffic where per-operator pools are impractical, and record
the choice. Note also that a human-approved out-of-grammar statement — one that reached a person
precisely because the parser could not bound it — may reset the role, degrading the session to the
shared baseline for its duration. Attribution, not containment, is what is lost; the role's grants
still bound it.
A per_operator user holds no privilege beyond the role it maps to — ideally by being a member of
that role and nothing else. Otherwise per-operator identity quietly becomes per-operator authority,
and the role stops being the outer bound (EDR-0006, whose
role introspection extends to these derived users).
Provisioning is not Marque's job. Marque never issues GRANT or creates database users. A
per_operator role names the mapping from federated subject to database user; if the user does not
exist or lacks rds-db:connect, the connection fails with that reason. Creating and revoking those
users belongs to whatever provisions database access — and an operator removed from the identity
provider should also lose the database user, which is a real integration someone has to build.
Verify positively. On connect the Pilot asserts the session's actual session_user (and
current_user under pooled_with_role) matches what the role declares, and refuses on mismatch. A
lazily-initialised pool otherwise hides broken database authentication indefinitely: no connection
attempt, no error, quiet logs, and the first symptom arrives during an incident.
Retry, failover and the fence
- Transparent driver retry is off for execution. The wrapper may reconnect and refresh topology;
it may not replay a statement or a transaction. A failover during an execution transaction
produces an aborted transaction and an
indeterminateoutcome recorded against the nonce (EDR-0011) — the honest answer, which a human then resolves. - Rehearsals may be retried automatically, because a rehearsal cannot commit (EDR-0010).
- Read-only statements may be retried automatically, per the
safeannotation (EDR-0020).
Read routing
A statement the parser establishes as read-only may be served by a reader endpoint. Two constraints:
- Read-your-writes, carried by a client-supplied version token. After a write, the Pilot returns
the writer's commit position on the response; the client presents it on subsequent reads, which then
go to the writer or wait for a replica that has applied it. The token lives in the request/response
shape (EDR-0020), so it works identically for an
agent task, a
marque psqlsession and a one-shot execution — none of which share a server-side session. Silently serving a stale read after a write an operator just made is how somebody concludes their change did not work and applies it again (ZFN-25). - Staleness is reported, never hidden. A result served from a replica says so, with the observed lag. An operator checking whether their fix worked must be able to tell "not yet replicated" from "did not happen".
Rehearsal of a write always runs on the writer — a replica is read-only, so a write rehearsal there fails rather than measuring anything. Rehearsal of a read may use a replica.
Observability at the target
Every session sets application_name to marque:<mrq_id> (or marque:rehearse:<req_digest>), so
the marque is visible in pg_stat_activity, slow-query logs and the engine's audit output. This is
close to free and is the difference between a DBA seeing an unexplained UPDATE and seeing one they
can trace to an approval in one query.
Consequences
Easier.
- Attribution stops depending solely on Marque. Under
per_operator, the database's own audit is a witness independent of the control plane — the thing you want when the question is whether the Harbourmaster was tampered with. It is not independent of the Pilot: the Pilot chooses which operator's token to mint, so a compromised Pilot can attribute its own activity to any operator it can mint for. The cross-check is the logbook, and a reconciliation job that diffs the target's audit against it. - Least privilege can be genuinely per-person where it matters, enforced by the database rather than by policy.
- No stored database passwords in the common deployment, and rotation is invisible.
- A DBA can trace any running statement back to its approval without leaving their usual tools.
Harder.
per_operatormultiplies pools. Fifty operators against one target is fifty pools, each with idle connections, against a connection limit that is often low on managed engines. This is the reasonpooled_with_roleexists, and the reason it will be tempting.- Provisioning database users per operator is real integration work that Marque deliberately does not do, and a half-built version of it produces confusing "role does not exist" failures for exactly the new joiner who is least able to diagnose them.
- Disabling transparent retry makes failovers visible to operators as failed executions that need a new marque. That is correct and it will be perceived as flakiness. The error text has to explain that Marque refuses to retry a write it cannot prove did not apply.
- Read routing adds a decision to every read and a way to be subtly wrong. Reporting staleness rather than hiding it is the mitigation, and it means operators see a detail they did not ask for.
application_nameputs a marque identifier in the target's logs, which is a small disclosure to whoever can read those logs. It names no statement and no data.
New obligations.
- Pool counts, idle connections and the sum against the target's limit are monitored. A Pilot approaching the limit must shed rather than exhaust (ZFN-13).
- The
session_userassertion is tested against a deliberately misconfigured role, since the failure it guards is silent by nature. - Any use of
pooled_with_roleis recorded with an owner and reviewed, like the credential carve-out in EDR-0005.
References
- ZFN-2 — correctness beats the driver's convenience feature.
- ZFN-25 — track the position a client has seen.
- ZFN-35 — refresh and re-fetch on auth failure.
- ZFN-9 — federated identity rather than stored credentials.
- EDR-0011 — the fence a transparent retry would break.
Changelog
- 2026-08-15: Accepted.
- 2026-08-16: Amended after the expert panel's should-fix pass: bounded the "independent witness" claim to independence of the control plane, required a per-operator user to hold no privilege beyond its role, and named the read-your-writes carrier as a client-supplied version token.