Recovery Contract specification¶
Status: inert v0.3.0 framework; no mutation endpoint, tool, capability, or production construction is authorized.
This is the normative contract/fault specification. The architectural layering and remaining activation blockers are in TIER1_ARCHITECTURE.md.
Authoritative fields¶
| Field | Type | Source | Mutable | Sensitive | Digest-bound |
|---|---|---|---|---|---|
contract_id |
opaque identifier | contract service | no | no | context |
operation_id |
opaque identifier | contract service | no | no | yes |
idempotency_key |
SHA-256 | canonical bindings | no | no | yes |
capability |
*_WRITE enum |
accepted build policy | no | no | yes |
endpoint_symbol |
exact symbol | endpoint policy | no | no | yes |
http_method |
POST/PUT/PATCH/DELETE | endpoint policy | no | no | yes |
| protected target identity | ciphertext | authoritative READ | no | yes | yes |
target_identity_digest |
SHA-256 | canonical natural identity | no | no | yes |
lifecycle_locator |
non-negative integer | authoritative READ at protection time | no | no | idempotency + HMAC record |
target_fingerprint |
SHA-256 | capability target projection | no | no | yes |
verified_target_fingerprint |
optional SHA-256 | authoritative post-forward READ | once, with VERIFIED | no | HMAC record |
| protected normalized intent | ciphertext | typed caller request | no | yes | yes |
intent_digest |
SHA-256 | capability/endpoint/method/intent | no | no | yes |
| protected snapshot | ciphertext | authoritative READ | no | yes | yes |
snapshot_digest |
SHA-256 | snapshot projection | no | no | yes |
rollback_plan_version |
identifier | capability implementation | no | no | yes |
created_at / expires_at |
aware UTC instants | trusted clock | no | no | yes |
| confirmation digest/time | SHA-256 + UTC | owner confirmation authority | once | no | yes |
state / state_version |
enum + integer | authoritative store | yes | no | event-bound |
The caller supplies contract ID and matching typed request—not an authoritative contract object. The service loads the record by ID and validates all bindings.
Canonicalization and digests¶
- UTF-8 JSON, NFC Unicode, sorted object keys, compact separators, explicit null, stable array order.
- Floats are rejected unless a future typed capability defines an exact decimal representation.
- Non-string object keys, unsupported objects, and normalization collisions are rejected.
- Every digest uses
pfSense-MCP/Tier1/v1, a purpose name, and exact contextual components framed by unsigned four-byte lengths. Length framing prevents delimiter injection and context-boundary ambiguity. - Canonical inputs are bounded to 32 nested levels, 10,000 total nodes, 4,096 members per collection, 64 KiB per string, and 1 MiB encoded form. Integers are signed 64-bit; booleans are not integers. Invalid Unicode scalars fail.
- Target identity, target fingerprint, intent, snapshot, confirmation, and idempotency use separate domains.
- The lifecycle locator is security-relevant contract state and participates in
the idempotency digest and authenticated store record. The fresh
ResolvedTransportTargetis an ephemeral request projection: it is not persisted and does not alter semantic intent, target-identity, fingerprint, snapshot, or confirmation digest domains.
Target contract¶
Each capability defines one natural identity and one fingerprint projection. Preparation and execution require exactly one matching target. Numeric IDs are transport locators, never semantic resource identity. Where a capability has no independent incarnation marker, the locator captured at protection time is an immutable lifecycle continuity guard. Immediately after PREPARED -> EXECUTING commits, the target is re-read by natural identity; its fingerprint and current locator must match the protected contract. Missing, duplicate, substituted, drifted, or renumbered targets refuse before send. A locator change does not prove recreation, but it prevents proving non-recreation and therefore requires a new lifecycle.
target_fingerprint always remains the complete original pre-forward
precondition. After a successful mutation, the executor derives
verified_target_fingerprint from the authoritative post-forward READ and
persists it atomically with EXECUTING -> VERIFIED. Rollback compares its fresh
pre-rollback READ to that verified post-forward fingerprint, not to the
original fingerprint. This permits an authorized mutation to change a
fingerprinted field while still refusing any later unrelated change. The
protected snapshot and original fingerprint remain the recovery target.
Confirmation contract¶
The owner confirmation authority authenticates an actor and binds authority, algorithm, nonce, contract ID, operation ID, target digest and fingerprint, intent digest, issue time, and expiry. The store accepts confirmation only through a configured verifier; absence, refusal, or verifier failure is closed. Raw proof bytes are not persisted. Confirmation is accepted once while PREPARED and unexpired. Prompt text or an agent boolean is not owner authentication.
State machine¶
| From | To | Meaning | Authority |
|---|---|---|---|
| PREPARING | PREPARED | protected snapshot/bindings persisted | automatic |
| PREPARING | FAILED / EXPIRED | preparation refused or expired | automatic |
| PREPARED | EXECUTING | confirmed atomic acquisition + target reservation | automatic |
| PREPARED | FAILED / EXPIRED | proven refusal or expiry before acquisition | automatic |
| EXECUTING | VERIFIED | exact response plus semantic read-back | automatic |
| EXECUTING | FAILED | no effect or failure is proven | automatic |
| EXECUTING | RECONCILIATION | outcome is not provable | automatic escalation |
| VERIFIED | ROLLING_BACK | rollback acquires same target | automatic after request |
| ROLLING_BACK | ROLLED_BACK | restoration read-back verified | automatic |
| ROLLING_BACK | ROLLBACK_FAILED | failure/conflict proven | automatic escalation |
| ROLLING_BACK | RECONCILIATION | rollback outcome ambiguous | automatic escalation |
| RECONCILIATION | VERIFIED / FAILED / ROLLING_BACK / ROLLED_BACK / ROLLBACK_FAILED | recorded operator conclusion | manual only |
Every other transition is illegal. FAILED, ROLLED_BACK, ROLLBACK_FAILED, and EXPIRED do not reopen. State/version compare-and-set, idempotency uniqueness, and canonical-target reservation are atomic with value-free transition audit. The generic store refuses all manual-only edges; a separately reviewed resolver must authenticate evidence and record the conclusion before using one.
Persistence and integrity¶
- Persistent records contain protected artifacts, never plaintext target, intent, payload, snapshot, credential, or response.
- HMAC binds the complete canonical record to one store ID. The key is supplied externally, is at least 256 bits, and is never stored in the database.
- Store schema v6 requires
lifecycle_locator; legacy v5 records fail closed rather than inferring a guard. - SQLite uses durable transactions and owner-only directory/file permissions.
- Duplicated operation/idempotency identities and conflicting target reservations fail closed.
- HMAC and denormalized index columns are cross-checked on every load/scan.
- Startup verifies exact column types/nullability, primary and unique keys, and cascading foreign keys; matching column names alone are insufficient.
- Every state event has an HMAC and contiguous state/version chain. This detects row-level deletion, insertion, modification, and reordering, but not rollback of the entire database to an older internally consistent copy.
- Startup scans all authenticated records; EXECUTING and ROLLING_BACK move to RECONCILIATION without resend.
- Whole-database rollback is not locally detectable; production activation requires an external monotonic anti-rollback anchor or equivalent evidence.
- Encryption/key rotation, retention, backup, secure deletion, and quarantine policy remain owner-approved activation prerequisites.
Execution algorithm¶
execute(contract_id, request):
contract = authoritative_store.load_and_authenticate(contract_id)
require PREPARED, confirmed, unexpired, expected state_version
require exact policy(capability, endpoint, method)
recompute target and intent bindings from typed request
atomically reserve target and transition PREPARED -> EXECUTING
authoritative_read_by_natural_identity()
require exactly one target, refreshed locator, matching fingerprint
require protected snapshot/rollback/config-history policy valid
send exactly one bounded typed request; never retry
require exact accepted status and response shape
authoritative_read_after_write()
if semantic intent verified:
require fresh semantic identity and lifecycle locator still match the protected contract
atomically seal authoritative post-forward fingerprint and transition EXECUTING -> VERIFIED
elif no effect/failure proven: EXECUTING -> FAILED
else: EXECUTING -> RECONCILIATION
No generic execute tool is permitted. Every request/response rule is capability-specific.
A reconciliation authority declaring CONFIRMED_APPLIED must sign the exact
verified post-forward fingerprint and the exact observed lifecycle locator.
An authority declaring CONFIRMED_ROLLBACK_APPLIED must also sign the exact
observed lifecycle locator. Non-applied outcomes carry neither applied-state
binding. This prevents reconciliation from bypassing either the rollback
fingerprint or the integrity-protected incarnation-continuity guard.
Fault decisions¶
| Scenario | State | Automatic retry |
|---|---|---|
| Refused before durable acquisition | remains PREPARED or FAILED | no |
| Crash before store commit | prior authenticated state | no |
| Crash after EXECUTING commit, before send | RECONCILIATION on restart unless no-send is independently proven | no |
| Partial transmission/reset/timeout after send | RECONCILIATION | never |
| Response lost after pfSense commit | RECONCILIATION | never |
| Verification interrupted or malformed | RECONCILIATION | never |
| Missing/duplicate/drifted target before send | FAILED, zero send | no |
| Concurrent duplicate invocation | CAS refusal, zero send | no |
| Rollback conflict | ROLLBACK_FAILED | never force |
| Partial/ambiguous rollback | RECONCILIATION | never |
| Corrupt/foreign/replayed record | refuse/quarantine | no |
Manual reconciliation re-reads by natural identity, compares snapshot, intent, and current semantic state, records the operator conclusion, and never infers success from HTTP status or numeric ID alone.
Audit contract¶
Permitted: event/contract/operation IDs, capability, endpoint symbol, method, target and intent digests, state/version, timing, outcome, sanitized failure and exception class. Prohibited: arguments, raw identity, payload, intent, snapshot, response, credentials, exception messages, rollback content, and result values.
Required activation tests¶
- exhaustive legal/illegal transition matrix;
- canonicalization stability/domain separation/fuzz inputs;
- stale version, duplicate invocation/contract/operation/idempotency;
- concurrent same-target acquisition across store connections/processes;
- corruption of payload, MAC, indexes, metadata, store identity, and key;
- crash before/after every commit and send boundary;
- restart reconciliation with no blind resend;
- missing, duplicate, shifted-ID and fingerprint-drifted targets;
- timeout/lost response/partial send and malformed response;
- rollback conflicts and partial compound compensation;
- value-free schema/log/error/fixture/report scans;
- READ contract and WRITE-isolation regression.
Passing framework tests does not authorize a capability. Activation still requires the separate gates in TIER1_ROADMAP.md.