Reconciling a registry#
A registry promises more than a stream: every request that reached it was decided under a published policy, recorded, and receipted, and each commitment key accepted at most once. Reconciling checks that promise from the evidence alone, with no access to Sigilbase.
What you need#
- The registry's evidence bundle and its decision log's, each exported from sequence 1. Export the log first and the registry after it: both are written in time order, so the verifier can tell a record made after the other export from one missing from it.
- Optionally, the receipts submitters hold, as a directory of JSON files (the PHP SDK's
exportReceipts()writes one). - The open-source verifier, 1.8 or later, with Sigilbase's receipt keys (built in, or
--receipt-keyswith the file fromGET /api/v1/receipt-keys).
php verify.php --reconcile filings.zip filings-decisions.zip --receipts receipts/
What it checks#
Both bundles are verified in full first. Then each rule, in order; a failure names the first rule broken, as reason in --json output.
| Rule | Reason when broken |
|---|---|
The log is this registry's (decision_log_for), both bundles start at sequence 1, and in each every received_at is no earlier than the one before |
NOT_DECISION_LOG, LOG_NOT_DENSE, NOT_TIME_ORDERED |
The log's first event is policy.committed |
FIRST_NOT_POLICY |
Each policy, its windows joined back from policy.windows, hashes to its policy_hash |
POLICY_HASH |
Each policy names this registry; versions run 1, 2, 3; only a policy under notice (an hour to a hundred years) is amended, never while an amendment is pending, and no sooner than its notice period |
POLICY_AMENDMENT |
| Every decision names the policy in force and fits it: an acceptance, replay or conflict inside its key's window, a replay or conflict repeating the key's logged acceptance, an early or late refusal early or late, an unknown key absent; a refusal for a pause or archive only while the log shows one, and nothing else then | DECISION_POLICY |
Every accepted decision matches exactly one registry event by entry hash, request digest and received_at, and every registry event matches exactly one acceptance |
ACCEPTED_UNMATCHED, PARENT_UNMATCHED |
| At most one acceptance per commitment key | DOUBLE_ACCEPTANCE |
Every window closes once, saying committed exactly when an acceptance came before close_at, and every window that closed more than five minutes before the log's last record has its close record |
WINDOW_CLOSE |
Every acceptance came from a credential the log shows able to submit at that point, from the opening credentials.snapshot and every issue, revocation and expiry since |
CREDENTIAL_NOT_VALID |
Every receipt supplied is logged at its decision_seq, field for field, and its event was sealed by its seal_by |
RECEIPT_NOT_LOGGED, RECEIPT_SUBJECT, MSD_BREACH |
A record past the other bundle's range is missing from it, and fails, when it came before that bundle's last record, or when it was due to be sealed (fifteen minutes after received_at, plus two minutes' grace) by the time that bundle was known complete: its latest checkpoint, or a trusted timestamp over one. Otherwise it may have been made after that bundle's export, and the run is unconfirmed until the bundle is exported again. An unconfirmed run never passes. Scope refusals, throttles and an invalid decision's reasons other than unknown_key are recorded but not checked: the log holds nothing to check them against.
A receipt Sigilbase signed whose decision is not in the log is proof the operator omitted a decision: only Sigilbase's receipt key could have signed it. The run also prints how many decisions the log holds, per outcome and per credential.
What it cannot prove#
A request that never reached Sigilbase cannot be shown by anyone: there is nothing to sign, record or receipt. Reconciling proves what was received and how it was decided, not what a submitter sent. A submitter can keep its own attempt log (the PHP SDK does) to show attempts that never got a receipt apart from decided ones.