Reliability Matrix - what happens when the enforcement plane fails
Mergen sits in front of your agents' commands and edits, so what it does when it
breaks is part of what it promises. This page lists every way the gate can fail and
what happens in each case. It is generated from the same table mergen doctor --security shows, so it says what the product does, and every row names the test
that proves it.
Live in-product
GET /coverage/reliability (JSON, or &format=html) and mergen doctor --security
surface the LIVE posture of this machine: the availability mode, policy integrity,
pending default rules, every active gate-weakening setting with its warning, and this
matrix. Read-only. An operator can see the posture rather than assume it.
overallPosture is deliberately three-valued. A stock install reports advisory,
not hardened: several defaults (fail-open availability on a machine no organization
manages, the passthrough free cap, the advise shell gate) run and log but do not block. Only an
install where every axis actually blocks or reverts reports hardened.
The governing invariant
A failure of the plane may preserve or strengthen enforcement, but must never silently weaken it.
Stated precisely, because the stronger phrasing this doc used to carry ("no failure yields execution by breaking") is no longer true by default and has not been since 2026-09-19:
- On the daemon seam, unconditionally:
verdictForFailurehas no branch that returns PASS. A failure can only BLOCK or HOLD. - On the client seam (
gate-check): a gate that times out, or answers with an error other than 502/503/504, blocks in every mode, and an organization-managed machine always fails closed. On any other machineMERGEN_GATE_AVAILABILITY_MODE=fail-closedblocks too. The default isfail-open: on a machine no organization manages, when nothing is answering (connection refused or dropped, or a 502/503/504), the tool call proceeds unevaluated.
A genuine BLOCK verdict is never an availability question - it is enforced in either mode. The availability setting governs only the "we could not decide" case.
The executable spine is reliability-conformance.test.ts, which asserts this across
both seams and pins the default so a silent regression is caught.
The matrix
| Failure | Behavior | Invariant | Proven by |
|---|---|---|---|
| Gate/daemon unreachable | organization-managed machine -> BLOCK, whatever is set locally. Otherwise fail-open (default) -> ALLOW unevaluated; fail-closed -> BLOCK | Enforcement authority is local. Availability is a configured choice, not a silent one, and an organization's machines never fail open | reliability-conformance.test.ts, gate-exit-codes.test.ts, org-managed-fail-closed.test.ts |
| Hook timeout (gate running, no answer in time) | BLOCK in every mode: a running gate that has not answered may be holding the call for a person (Cursor failClosed also denies on timeout) | A timeout is not an absent gate | gate-exit-codes.test.ts, cursor-option-b.test.ts |
| Gate answers with an error (401, 413, 429, 500) | BLOCK in every mode, naming the way out | An error status is not an absent gate | gate-error-status-fail-open.test.ts |
| Connection dropped, or 502/503/504 from a router whose worker is gone | treated as unreachable: organization-managed -> BLOCK; otherwise fail-open (default) -> ALLOW; fail-closed -> BLOCK | Only "nothing is answering" takes the availability path | reliability-conformance.test.ts, gate-error-status-fail-open.test.ts |
| Unparseable hook payload | organization-managed -> BLOCK; otherwise fail-open (default) -> ALLOW; fail-closed -> BLOCK. A payload that cannot be read is a no-decision outcome, so it follows the availability mode rather than being independently blocked | Never guess a verdict - an unreadable payload is never given one | reliability-conformance.test.ts, gate-check-cli-harness.test.ts |
| Deliberate stop, then run | organization-managed -> BLOCK. Otherwise fail-open (default): ALLOWs unevaluated (bypass reopened 2026-09-19); fail-closed: BLOCK | An agent that can stop the runtime can bypass the gate under fail-open on an unmanaged machine. Set fail-closed where that is unacceptable | reliability-conformance.test.ts, org-managed-fail-closed.test.ts |
| Parser (L1) crash / hang | HOLD; reproducible poison-pill -> BLOCK; never PASS | B-FAILMODE monotonic - no failure condition returns PASS | gated-fail-mode.test.ts, gated-l1-client.test.ts, reliability-conformance.test.ts |
| Daemon L0 / IPC failure | BLOCK; never fall back to TCP/user-level | B-VERDICT-AUTH - an unverifiable peer or socket is a boundary breach | gated-fail-mode.test.ts, gated-ipc-e2e.test.ts |
| Bad / corrupt / tampered policy | last-known-good snapshot, never DEFAULT-weaken; else BLOCK. A stripped/downgraded hard-safety rule is restored on load unless the policy is signed with an ENV-ONLY key (an on-disk-key signature is tamper evidence, not admin authorization) | Integrity failure never yields a weaker policy | gated-l0-core.test.ts, policy-integrity-blunder.test.ts, immutable-rule-reinjection.test.ts |
| Policy predates a shipped hard-safety rule | the rule is delivered on load at every authority - a signature cannot carry intent about a rule that did not exist when it was signed. Editable seed rules are offered, not auto-applied (mergen policy sync-seeds) | The hard-safety floor is the same on an upgraded install as on a fresh one | policy-seed-delivery.test.ts, immutable-rule-reinjection.test.ts |
| Network / cloud outage | no effect - verdict is local & synchronous | Enforcement authority is always local; cloud is telemetry only | architectural-invariants.test.ts |
| Approval (HITL) backend outage | HOLD stays held; restart denies, never auto-approves | A delivery failure never auto-approves | hitl-hold-restart-deny.test.ts, hitl-delivery.test.ts |
| Audit store tampered | detected only when HMAC signing key is configured (hash-chained ledger); otherwise casual-tamper evidence only | Tamper-evident, not silently mutable | audit-integrity.test.ts, audit-integrity-surface.test.ts, action-ledger-prune-integrity.test.ts |
| Free-tier cap hit | passthrough (default): past 1,000 protected actions a month on Free, enforcement pauses. Nothing is evaluated, hard-safety BLOCKs included, until the month resets or the plan is upgraded. protect: every action is still evaluated and enforced. Paid plans have no cap | The one place billing disables safety, by product decision, and it is stated rather than implied | free-cap-passthrough.test.ts, plan-agnostic-security.test.ts |
The two-command bypass, stated plainly
Under the default fail-open, on a machine no organization manages, an agent with shell access can run:
mergen-server stop && <anything>
and the second command executes unevaluated. This is a deliberate product decision (2026-09-19) favouring availability on a developer's own machine over blocking when the runtime is down - a background daemon being unreachable should not brick every Bash call. It is named here rather than buried because a security control's known bypass belongs in its own documentation.
The fix is one setting:
MERGEN_GATE_AVAILABILITY_MODE=fail-closed
Recommended for shared machines, CI, and any deployment where the agent is less trusted than the developer running it. The stronger posture also removes the incentive to induce a gate failure: when breaking the gate stops work rather than unlocking it, there is nothing to gain by breaking it.
Recovery (two-tier, sanctioned + audited)
- User-tier - resolve holds (
approve/deny/cancel), inspect (doctor,status), and restart the user-level server. Recovery commands are an exact-match allowlist that short-circuits the gate, so a wedged gate never strands its own recovery (recovery-commands.test.ts). - Privileged-tier - restarting or repairing the root daemon needs elevation
(
sudo systemctl restart mergen-gated/mergen interception repair), documented and audited. A same-uid process cannot revive it; that is the point.
Evidence layer - which policy, version, identity and context decided
Every durable DecisionRecord carries the policy version (sha256 of the active
policy content) plus rule/controlId, the runtimeId that reached the verdict, the
identity (actor, agentId), the context (workspaceId, surface), and the
requested vs effective verdict with projectionReason (the cross-surface
projection trail). Provenance is stamped at each store's write choke point, not
threaded per call (activity-provenance.test.ts).
Honest residual
fail-openis the default on a machine no organization manages, and it is a real allow path. See the bypass section above.fail-closedis one environment variable away and is the recommended posture for shared, CI, and lower-trust deployments.- Audit head-sealing is off unless
MERGEN_AUDIT_SECRETis set. The hash chain detects a targeted row edit either way; without an env-supplied key, an adversary who rewrites the whole table can produce a fresh, internally consistent chain. - Policy authority defaults to
tamper-evident, notauthoritative. WithoutMERGEN_POLICY_SIGNING_SECRET, the policy is signed with the on-disk~/.mergen/secret, which the governed agent can read - so the signature proves the file was not corrupted, not that an administrator authorized it. - Windows is the least-verified tier. Ownership/ACL checks fail closed there by design; the privileged paths are verified on POSIX (see the per-OS live-verification runbooks).
- Live end-to-end for the privileged daemon and managed interception is a rooted-host runbook lane, not CI. The unit conformance here proves the logic; the runbooks prove the wiring.