3The rules
Each rule says what must never happen, who it holds against, what enforces it, and where the evidence is. Rules G1 to G4 are kept by the gate, off the ARBITER server. Rules S1 to S5 are kept on the server.
Kept by the gateoff the ARBITER server
G1No payment without the whole chain of authority
A payment forms only when ARBITER asks, under a key an owner granted for that company, to pay a payee an owner listed for that company, AND a person whose passkey an owner activated and granted for that company approves the exact terms on the gate's own page, within the limit the owner set for that passkey.
- Holds against
- A1 to A5
- Enforced by
- The gate, a Cloudflare Worker in an account the ARBITER server holds no credential for (
gate/worker.js: intakeChecked, decideChecked, verifyAssertion). It holds the only credential that pays.
- Evidence
- The 16 October run (section 8). Root on the server, holding the database and the broker's own key, made 15 attempts on the gate: 13 were refused outright, the passkey it registered stayed pending and could do nothing, and the $49,999.99 it asked for waited for a person, who rejected it. One payment formed: the one the owner approved on their phone. 21 outcomes as predicted, 0 unexpected. The gate's own tests (113 checks) and the end-to-end test (32 checks) run on every change.
A request pays at most once, and a failed payment is never retried: ARBITER must ask again, and a person must approve again.
- Holds against
- A1 to A5
- Enforced by
- An approval moves the request from
pending to executing in the same transaction that records the approval, before the payment is attempted; a second approval finds it no longer pending (decideChecked).
- Evidence
- The gate's tests send ten approvals of one request at once: one executes, nine are refused.
G3Authority changes only by an owner's signature
Who may approve, for which company, up to what limit, which payees, and which ARBITER keys may ask: each of these changes only by an owner's passkey signature over the exact change, made within the last five minutes, applied once.
- Holds against
- A1 to A5
- Enforced by
opStart, opFinish, applyOp. The first owner was made once, with a setup code that only the Cloudflare account can set; setup then closed for good, even if that owner is later revoked. The last active owner cannot be revoked. A passkey anyone registers is pending and can do nothing until an owner activates it.
- Evidence
- The 16 October run: root tried to make itself owner through setup (refused: closed for good) and to make its own passkey an owner, signing with that passkey (refused: pending, not active).
G4The gate's record is public, and anyone can check every signature in it
Every event at the gate (setup, registrations, owner changes with their signatures, requests with ARBITER's signature, approvals and rejections with the person's signature, refusals, payments) is appended to a hash chain that anyone can read. Every final outcome is a receipt signed by the gate's own Ed25519 key.
- Holds against
- A1 to A5. Nothing on the ARBITER server can write to it.
- Enforced by
append (each entry's hash covers the one before it) and receipt.
- Evidence
verify_gate_log.py, one file of about 330 lines that needs only Python and the cryptography package, replays the whole log from its first entry: the chain, every owner change, request, approval and receipt. Section 8 freezes the log's head as of this version.
Kept on the serverARBITER's own layers
S1The company's rules decide before the AI acts
No model is called for a request until the company's rules have decided it. A block stops it. A review holds it until a person decides. Only "proceed" reaches the model. A rule the evaluator cannot read, or a payload value of the wrong shape, sends the request to a person, never through. Every model answer is checked against the platform's and the company's principles before it is released; one that fails is blocked or held for review, and a check that runs out of time counts as a failure.
- Holds against
- A1 to A3
- Does not hold
- A4 and A5, which control the code that applies the rules.
- Enforced by
- The executor's pipeline (
executor/main.py: the decision commits before any model call), executor/jsonlogic.py (strict mode), executor/cle_engine.py.
- Evidence
- The 13 October dress rehearsal (88 of 89 checks; the one failure is explained in its log); the sandbox, where anyone can write a rule and test it.
S2Companies are walled off by the database
A request authenticated for one company can neither read nor write another company's records.
- Holds against
- A1 to A3
- Does not hold
- A4 and A5. The executor can take the database role that crosses companies, which it needs for background work.
- Enforced by
- PostgreSQL row-level security, enabled and forced, on every table that holds a company's records. The schema contract fails if a new such table lacks it. Three tables are exempt, each named with its reason in
test_arbiter.sh check 1: the sign-in accounts, which a super-admin lists across companies by design; usage counts, read only with the company named in the query; and one table nothing reads. Request connections use a role that cannot bypass it; the role that can is reached only through one function, which requires a reason and logs it. A static check fails the build if any other way to the database appears.
- Evidence
- The guided demo's step 4 and every sandbox: one company's key asks for another's record and gets "not found". The dress rehearsal's isolation checks.
S3Records are add-only, chained, and copied off the server
The audit log, the execution certificates and the effect receipts are add-only for every application role. Each is a hash chain. Within about five minutes, each chain's head is copied to write-once storage (S3 Object Lock in compliance mode, which not even that storage account's own root can shorten) in a separate cloud account. The copies are checked hourly on the server, and every six hours from GitHub with read-only access and no stored key.
- Holds against
- A1 to A4. The executor's login cannot rewrite any of it.
- Against A5
- Detected, not prevented. Root can rewrite the database. It cannot change the copies already made, so a rewrite of anything already copied shows up in the next check. A record is unprotected for the seconds before it is sealed.
- Enforced by
- Migrations 30 and 47, the
anchor sidecar, verify_receipts, .github/workflows/receipt-anchors.yml.
S4A certificate cannot be changed without its check failing
Each execution gets one certificate, issued once and never reissued, signed with Ed25519. It records who asked (as a role), what was asked (a hash), which rule decided and the hash of the whole rule set, the model and the hash of its instructions, the principles checked, the reviewer's role if a person decided, and what became of it. Anyone can check one with the public key at /.well-known/arbiter-certificate-keys: any change to any field makes the signature fail.
- Holds against
- A1 to A3
- Does not hold
- A4 and A5, which hold the signing key and can sign a false certificate. Whether a certificate is the instance's own record is answered by
POST /certificates/verify (on_record) and by S3. Against the server itself, the evidence is the gate's receipts (G4), not ARBITER's certificates.
S5Inside the server, neither the tool nor the executor can pay on its own
This is the server's own layer for payments made through it rather than through the gate (the demonstration ledger). The tool's database login cannot write the ledger or create a permit. A permit comes only from the effect broker, after it has verified a person's passkey signature over the exact terms, by a passkey the operator confirmed; each permit pays once.
- Holds against
- A1 to A4
- Does not hold
- A5, or a shell in the database or the broker. That is why the gate (G1 to G4) exists. A payment bound to the gate does not rely on S5.
- Evidence
- The 13 October run from inside the executor: 38 attempts refused, 1 effect formed (the sanctioned one), 0 outcomes other than predicted.
Who each rule holds against
A1 outsider · A2 other company · A3 model, tool · A4 executor code · A5 root on server
4G1 written out exactly
For every payment instruction the gate's credential sends, there is exactly one request r and one approval a such that:
- When the gate received r:
- its text has exactly the fields
account, amount, currency, expires_at, gate, id, intent, issued_at, kind, tenant, v, names this gate, and is signed (Ed25519) by an ARBITER key whose status is active and whose companies include tenant(r);
account(r) is a payee listed for tenant(r);
amount(r) is greater than zero, issued_at is within five minutes of the gate's clock, and expires_at is in the future and at most 24 hours after issued_at.
- When a was recorded:
- r was
pending and had not expired;
- a is a WebAuthn signature by a passkey p whose status is active and which is granted for
tenant(r) with a limit at least amount(r);
- the signature is over SHA-256 of the exact terms
{v, kind: "approve", gate, request: id(r), request_sha256: SHA-256 of r's text, tenant, account, amount, currency};
- it was made for the gate's own origin, not from inside another site, by an authenticator that reports the person present and verified (PIN or biometric), with a signature counter that did not go backwards;
- in the same transaction, r became
executing.
- The sets in 1 and 2 (active ARBITER keys and their companies, payees, active passkeys, grants and limits) change only by an owner operation (G3).
The credential that pays (EFFECT_SLACK_URL) is read in one place in the gate's code, the function effect, and that function is called in one place, after the transaction in 2.
That is the whole claim for G1, and it is the one a reviewer should try to break.
A gap this statement shows
Item 1 is checked when the request arrives, not again at approval. If an owner unlists a payee, or revokes ARBITER's key, a request already waiting can still be approved until it expires (24 hours at most). It still needs a person with a granted passkey to approve it, within that passkey's limit. The fix, re-checking item 1 inside the approval's transaction, is planned for the next gate release.
8The evidence, frozen
Each run log states where it started (the code and the setup), what was changed (the attacks), the protected effect, the receipts, the routes tried, and the results. It is published as it printed. A file's SHA-256 here lets anyone confirm they have the same bytes.
-
code 1366ca637 refused, 1 formed, 0 failures
sha256 8b835c1501ef35667ec9a328480948842772c1cf0c7a88c3a456eb796749d748
-
code 4ad364738 refused, 1 formed, 0 other than predicted
sha256 aed031ca02191ea73cfb0fba4dafc8c2fa6c8d8965555e9535a600fecd8c4205
-
code 615c73888 passed, 1 failed (explained)
sha256 89cb11d6877c67d8ac3d44830fdc3299b8e86263f42b6bc99553b7d51bfaf40e
-
code ab0a67d21 as predicted, 1 formed, 0 unexpected
sha256 6d4b8202017f02bf3fb785a126c07db3274454845971f9ee8381be048d6f4c4b
-
sha256 5cf33bb32dbb8ca7eb7f3dde6b27c449093b5ad62ed098846d3da2d1a6e81f6d
The gate's log, frozen at this version
Entry 27, head hash
60ac6b5b51a2397159c135b545a1988acf5062b4d847f38b07329d5725a89803
The log only grows. Run the checker against it and confirm that entry 27 still has this hash: then entries 1 to 27 are the ones this document describes.
What anyone can reproduce today, without trusting us:
- Every signature in the gate's log, with the checker.
- The bytes of every run log, with the hashes above.
- Any ARBITER certificate, with the public key.
- S1, S2 and S4 by hand, in a sandbox of your own: demo.sovereignarbiter.com/#sandbox (the gate is not connected to the demo).
What needs our code: re-running the attack scripts themselves. The source is private today. No change reaches production until all 11 of its CI jobs pass on that exact commit.