Engine
Guards
The repository cron only scans and decides. A separate local operator path may sign and submit with explicit live opt-in; these guards constrain that path and record refusals.
Two independent layers
A contract can be kept out of the engine’s reach two ways: by not being in the watched list, and by the write guard refusing it. These are deliberately separate, so that a misconfiguration and a code path have to fail together for a protected contract to be touched.
The list is documentation; the guard is a mechanism. That distinction was learned the hard way — a pre-flight document once claimed that leaving a subject out of the watched list was enough, because “the engine still scans and decides, the guard still refuses”. It does not: the engine iterates the watched contracts, so an unwatched subject is never selected and the guard is never consulted. Measured against the real config, that produced two decisions and no refusal anywhere.
Protected subjects
The guard refuses to write to these regardless of configuration, because they are natural-decay subjects whose ageing cannot be recovered once an extension resets it.
| Subject | Contract | Schedule |
|---|---|---|
| guinea-pig B | CCYGO7KQ6F… | Crosses 2026-09-20, expires 2026-09-21 |
| guinea-pig C | CCLW55OIED… | Crosses 2026-09-25, expires 2026-09-26 |
A refusal is recorded, not thrown
When the guard declines, the run records REFUSED BY WRITE GUARD with the subject named, and continues to the next contract. Two reasons, and both matter:
- One protected subject must not abort a run, or a guard on one contract would silence the engine for every other contract in the same pass.
- The refusal is the evidence. A refusal leaves no transaction, no hash and no explorer trace, so the only proof it happened is the line in the run record.
Before anything is signed
| Check | What it stops |
|---|---|
| mode | Omitted means dry-run. Live is explicit, never inferred. |
| network passphrase | Compared, not trusted. This is the mainnet guard. |
| payer resolution | Checked when the config is read. No fallback payer exists. |
| selected key / target | Only the entries named are touched; storage is never enumerated. |
| fee cap | A configured ceiling in stroops, applied per bump. |
| signed-envelope validation | The envelope is checked against what was planned before it goes out. |
| post-state check | The result is verified rather than assumed from a successful submission. |
These are the Stage 1 controls described in docs/POLICY-SIGNER.md. What is not implemented is described there too, and on Security and keys.
Overlapping runs
The local live path keeps an attempt journal and fails closed when a prior attempt is uncertain. An operator must retain that journal and reconcile its transaction hash against the chain before another attempt. Automatic cross-run reconciliation and an unattended live-submission scheduler are not shipped; the repository cron is decide-only. Do not use a lease timeout or a new journal path to bypass an uncertain attempt. See the engine setup guide.
Next