For researchers and reviewers layer 3 of 3

The mechanism: one ordered check, two phases, a ledger that is never edited.

What onedoor checks, in which order, when the budget is spent, how a policy version comes into force, and what its records can and cannot show. Described for onedoor 0.8.1.

1 · The check order

Every action is one structured request, checked in a fixed order.

Scheduled, rule-fired, model-proposed or clicked by a person, every action is an ActionRequest: an action type, its parameters, its source and a rationale. One executor checks it against the policy table before anything touches the world. The first check that denies decides, with its own reason code.

The check order. The kill switch is read first and clamps every acting tier. Then policy lookup, which denies an undeclared action as default_deny; the tier, where effect floors and a missing undo can raise it and a tier-0 read is executed as a recorded no-op; bounds, which deny as bounds; the confirm step, where a tier-3 action is proposed, or denied as kill_switch while the switch holds; dry-run, recorded and not done; caps, which deny as budget_exhausted; and then permitted, with a report owed.Kill switchread firstclampsacting tiersPolicy lookupdeclared?denieddefault_denyTierfloors, undo?tier 0executed (read)Boundsparams sane?deniedboundsConfirm?tier 3? switch?proposedor kill_switchDry-runrehearsal?recordednot doneCapsbudget left?deniedbudget_exhaustedPermittedreport owedIN ORDER · THE FIRST CHECK THAT DENIES DECIDES · EVERY DECISION IS WRITTEN TO THE LEDGERAppend-only ledger: decision, reason code, frozen parameters, tier, budget at decision, policy version
The kill switch is read before any policy lookup and clamps every acting tier, so it outranks approvals already granted; it shows as the reason only when nothing stronger denies first. While it holds, an undeclared action still reads default_deny and an out-of-range one bounds, and one that passes both is denied as kill_switch, not proposed. Bounds are checked before a person ever sees a proposal. A tier-3 action is proposed before dry-run and caps; dry-run is resolved before caps, so a rehearsal never spends a real budget.

Default-deny

An action type no policy declares is refused as default_deny. Nothing is proposed, and nothing promotes itself.

Reversibility before autonomy

A tier 1 or 2 policy must name a compensating_command. Without one, the loader refuses to start, and an auto-tier action without an undo is demoted to a person's approval.

Effects share limits

Actions that declare the same effect (say money.egress) share one budget and one tier floor, however their tools are named.

2 · Two phases

Decide and reserve, act, then append what happened.

Caps are reserved inside the deciding transaction (BEGIN IMMEDIATE), so two concurrent requests cannot share the last slot. The action itself runs outside any database lock.

Two phases. The enforcement point asks; transaction A decides, reserves the budget and writes an intent row. The action runs outside any database lock under a hard timeout. Transaction B appends the outcome and settles or releases the reservation.Your enforcement pointonedoor (decision)The tool or systemPOST /v1/decideTx A: decidechecks · reserve caps (race-free)write the intent rowpermitted · intent_audit_idact, outside any lockwhat happenedPOST /v1/report · outcomeTx B: append the resultsettle, or release: not_attempted, failure + no_effectA hung tool cannot holdthe engine; a crash leaves"intended, unconfirmed".
The report's outcome decides the budget. success and timeout settle it (a timeout is doubt, so onedoor assumes the action happened). failure settles it too, unless the report adds "no_effect": true. Only a positive statement that nothing happened releases money held: not_attempted, or failure with no_effect, each recorded as its own event. A calls-per-day count is never given back. The in-process executor puts a hard timeout on the action; behind the HTTP service, your enforcement point owns its own timeout.

3 · Decision and enforcement

One decision point. Enforcement wherever your tools are called.

onedoor separates the classic authorization pair: a policy decision point, and enforcement points that ask it and then act. Any enforcement point, in any language, can ask the HTTP service. The same engine runs in-process behind the library and the integrations.

One decision point, many enforcement points. The HTTP service, the library, the MCP proxy, the LangChain middleware and the LiteLLM example all ask the same engine. The engine reads the policy version in force and writes one ledger. The Studio drafts and ratifies policy versions and reads the ledger. Operators hold the admin key for approvals and the kill switch.ENFORCEMENT POINTSHTTP clientPython libraryMCP proxyLangChain middlewareLiteLLM exampleonedoor engineone decision pointthe check orderpolicy version in forceOperatorsadmin key: approvals, kill switchPolicy Studiodrafts · ratify · read-only viewsLedger (SQLite)append-only · export · receiptsThe process that asks holds a decide key.The process that grants holds an admin key.
Approvals and the kill switch are operator acts. On the HTTP service they need an admin key, and they are not reachable from an agent's side of the MCP proxy. A person cannot approve their own proposal, and an approval presented by reference can be used only by the caller that proposed the action.

4 · Policy versions

A policy set is a version, named by its digest, changed only by ratifying.

The Studio keeps drafts in their own store. Nothing in a draft touches the rules in force. A draft becomes the version in force only through ratification: a page that shows the new version's digest, what it replaces and every changed rule in sentences, and asks for a name.

The loader's own words

Every draft is checked by the engine's own loader, stage by stage. What it would refuse at start-up is listed apart from how a loadable rule will behave at decision time; the two are never merged.

A receipt per ratification

Each ratification seals a receipt: the version, the one it replaced, the time, the name given, and the kill switch's state. The Verify page re-checks it, with the receipt and the policy snapshot downloadable.

No un-ratify

To go back, you ratify again: a new version and a new receipt. Nothing decided under an earlier version is changed. The name given is what the store knows, not an authenticated identity.

Ratified: the new version's full digest in force, the version it replaced, the receipt digest, the time, the session name operator on duty, and the kill switch not engaged at ratification.
Real screenA ratification, from the shipped payments examples.

5 · The ledger

Append-only rows, and checks that say what they rest on.

Every decision, result, denial, dry-run and kill-switch block is a row with a typed reason code. Database triggers forbid updates and deletes. The export writes one JSON object per row with a .sha256 file beside it.

StateMeaning
verifiedChecked against something the store does not hold: a trusted key id passed in from outside, or a published anchor root.
self_consistentThe signature matches a key in the same store, or a proof checks against a root the store itself carries. Real information, and not independence: whoever can write the store supplies both halves.
absentNot produced for this row, for example rows newer than the last anchor, or signing not switched on. Shown as absent, not as a fault.

The receipt page (python -m onedoor.viewer) is static and read-only. Every value on it is read from a verified artifact; if the evidence does not check out, it shows the failure state and none of the receipt's values.

6 · With onetrace

A decision reference, joinable to the export by digest.

When a deployment declares its name (ONEDOOR_ISSUER), each decision that permits, denies or proposes carries a small reference (dry-runs and tier-0 reads carry none): format onedoor-decision-ref/1, the request id, a digest of the decision's ledger row, the verdict (permit, deny or propose) and the issuer. Without a declared name, no reference is returned: never a guessed one.

With onetrace. The gated step asks onedoor and gets a decision with a decision reference. The step records the reference in its receipt. onedoor exports its ledger with a sha256 file beside it. onetrace-verify reads the run and the export, and matches each reference to a row by its digest.Gated stepapprove_paymentonedoordecision + decision_refdeciderefStep receiptdecisions: [ref]ot.decision(ref)decisions.jsonl+ decisions.jsonl.sha256onedoor.exportonetrace-verify--decisionseach reference matchesa row by digestno step acted on a denyOPTIONAL · EACH PRODUCT WORKS WITHOUT THE OTHER
onetrace records the reference in the step's receipt; onetrace-verify --decisions checks that gated steps cite a decision, that a step that acted cites a permit, and that each reference matches a row of the export by digest. Either product's records stand on their own without the other.

7 · The limits

What the mechanism does not cover.

Parameters, not intent

Rules match parameters. A command assembled indirectly carries nothing to match; a URL pattern matches text unless you opt into the URL block, which matches the canonical target. Use an egress control that fails closed for anything that matters.

Who times the action

The in-process executor puts a hard timeout on the act. Behind the HTTP service, onedoor never sees the act itself: your enforcement point owns its timeout, and reports timeout when it cannot tell what happened.

The documented deployment

One process, one SQLite writer behind an internal lock. Several writers on one store, without that, can see a window between checking a cap and consuming it.

One obligation

onedoor implements one obligation, present_bound: the permit may be used only at the audience it names. In 0.8.1 the in-process executor and the MCP proxy refuse to act on any permit that carries it, since neither presents to an audience; the LangChain middleware does not check it.

8 · Coming next

The permit in every caller's hands, and the recipient's signed reply.

Part of this is in 0.8.1 already; no dates for the rest. The receiving system checks a signed permit on its own, without calling onedoor, and next signs back what it did:

  • Bound to the request (in 0.8.1). When a policy asks for one, onedoor issues a permit naming the exact action by digest, the key that must sign the request, a short lifetime and a one-time id; the request must carry its body's digest. The library returns it; the decision service does not hand it to its caller yet. Next: every surface does.
  • Checked in a fixed order by the recipient (in 0.8.1). onedoor.permit holds the recipient's check: thirteen steps, stopping at the first failure, with the permit used up only at the last one, so a refused request never burns it.
  • A signed confirmation back (next; not built yet). The recipient signs which permit and which request it acted on, and whether the effect happened, was refused or is unknown. A onetrace record can then show an action authorized and its effect confirmed, and check both.

Back to the overview, or start on the Developers page.