Failure postures
Every part of the system has a designed answer to one question: “what happens when I break?” There are two answers — refuse, or carry on and make a note — and which one a part gives is not an accident.
The fail-closed side — enforcement
Section titled “The fail-closed side — enforcement”| Component | When this breaks… | …the system does this |
|---|---|---|
| Policy loader | The rules file is missing, unreadable, or fails validation | Default deny. No rules means no permissions, not “anything goes”. The failed load is also recorded as a violation event. |
| Approval client | The dashboard is unreachable, slow, returns an error, or sends back something malformed | Every failure collapses to one answer: unavailable — and unavailable means denied. You cannot get a yes out of a broken approval system. |
| Scope manifests | The manifest file is missing, expired, or won’t parse | Treated as no manifest at all — deny by default. A corrupted grant is no grant. |
| Landlock install | The kernel sandbox can’t be installed at startup | The shim exits with an error before launching the agent. The agent never runs unrestrained; the failed start is logged as shim-init-failed. |
| Landlock on old kernels | The kernel speaks an older sandbox version | Rules are masked down conservatively to what the kernel supports — never silently widened. |
| Morpheus daemon sandbox | Its opt-in Landlock fails to install | The daemon exits rather than run unsandboxed. |
| Product policy | An action arrives with no classification, or with a risk band the code doesn’t know | Unclassified → deny. Unknown band → escalate to a human. There is no default-allow branch for any kind of principal. |
| DLP | It finds something — or the layers disagree | DLP can only tighten a decision: an allow can become an escalation, a deny never becomes an allow. |
| Ledger signing | Anything at all goes wrong while checking a signature | The signature is invalid. Every error path verifies to “no”. |
| Chain limits | A tool call crosses tenant lines or exceeds budget | Blocked — and the cross-tenant check runs first, before any counting. |
| Bot GitHub tokens | The broker can’t produce gh-token-soho / morpheus-gh-token |
A loud error, not an empty string. Pushing with the wrong identity would silently corrupt the review flow, so this one secret fails closed where others fail soft. |
The fail-soft side — reporting
Section titled “The fail-soft side — reporting”| Component | When this breaks… | …the system does this |
|---|---|---|
| violations.jsonl writer | The log file can’t be written | Silent no-op. Logging never blocks enforcement — the deny already happened; only the note about it is lost. |
| Dashboard bridge | Mirroring an event to the database fails for any reason | Every exception is swallowed. The JSONL file is canonical; the database is a convenience mirror. |
| Session reporting | Opening or closing a session record fails | Best-effort with a 2-second timeout; closing twice is harmless. Work is never held up by bookkeeping. |
| Broker client (ordinary secrets) | The broker is down, slow, or answers nonsense | A warning in the log and an empty value back to the caller — the feature that needed the secret degrades, the process survives. (The two bot tokens above are the deliberate exception.) |
The edge cases worth knowing about
Section titled “The edge cases worth knowing about”Why this split matters
Section titled “Why this split matters”Put the two tables together and a pattern shows: the system is built so that breaking it only ever makes it stricter. Kill the dashboard — approvals deny. Corrupt the policy — the gate denies. Crash the sandbox — agents don’t start. The only things an attacker gains by breaking parts of Steelmoth are lost convenience and a quieter logbook — never a wider door.

