Skip to content

RootSign v0.3.1 — sync receipts

Latest

Choose a tag to compare

@oabolade oabolade released this 20 Oct 23:15
· 7 commits to main since this release
2ee98c6

RootSign v0.3.1 — sync receipts

One feature, one gap closed. v0.3.0's evidence model could say a record was
missing (INCOMPLETE) or altered (TAMPERED). It had no way to say a
record was observed and refused — that outcome lived in terminal
scrollback and vanished with it. A reviewer examining a spool file later could
not tell "never uploaded" from "uploaded and rejected," which are very
different facts in an investigation.

rootsign-admin sync now writes a sync receipt: the chain-of-custody log
for the upload, kept beside the evidence and never written onto it.

Implements ADR-015. Raised by
issue #37 — the project's
first community design proposal.

What you get

spool/receipts/<session_id>.sync.json          written on every real attempt
spool/synced/receipts/<session_id>.sync.json   follows the file once accepted

Each attempt records where it went, who ran it, the SHA-256 of the spool file
as replayed, and a per-record outcome:

Bucket Meaning
accepted the store took it
duplicate the store already had it (DUPLICATE_EVENT)
rejected refused, with error_code, retryable, and the sequence
not_attempted never presented — solicitation had already halted

Plus halted_at_sequence and a disposition of fully_accepted, partial
(the store refused something — fix it and re-run) or transport_failed (the
network gave up — try again later).

The buckets partition every record in the file, exactly once. A reviewer
can confirm a receipt accounts for the whole exhibit by arithmetic, without
trusting whoever wrote it. The suite asserts it on every receipt it builds.

Receipts are append-only: re-running after a fix adds attempt 2 rather
than overwriting attempt 1, so the history of how the evidence was handled
survives. --dry-run contacts nothing, observes nothing, and writes nothing.

In the evidence bundle

rootsign export of a session that has a receipt now includes
sync-receipt.json, hashed in manifest.json like every other file, and
verification.json gains a replay block summarizing the latest attempt.

A session with no receipt is byte-identical to what v0.3.0 produced — the
replay key is absent, not null. This is additive under bundle v1.0; there is
no format break and no version bump.

Also in this release

  • sync no longer prints INCOMPLETE for a rejected record. It prints
    REJECTED. INCOMPLETE is the verdict word for "a record is missing," and
    a record the store refused is the opposite — observed, and declined.
    Conflating them on the command that writes the receipt was the exact
    confusion ADR-015 exists to remove. stdout wording only; exit codes and the
    verdict enum are unchanged.

  • Replay records the whole batch's answers. Fail-fast now stops at the
    batch boundary rather than the response boundary. The endpoint answers a
    batch index-aligned, and spec §7 rule 3 forbids a rejection from aborting
    later elements — so records after a refusal are usually accepted.
    Previously those answers were discarded, which under-reported what had
    actually been delivered.

  • Numeric PII is now redacted — behaviour change (#41). RedactionConfig
    tested rules only against values that were already str, so an SSN, account
    number, routing number or NPI arriving as a JSON number passed through
    unredacted. Because redaction runs before hashing (ADR-006), the raw value
    reached both the hash input and the stored input_redacted. Matching now
    tests the value's string form.

    The pattern still decides, so this is not blanket redaction by key:
    {"ssn": 42} survives, and {"count": 123456789} survives. Containers
    still recurse; a value whose string form cannot be produced fails closed.

    Consequence worth noting before you upgrade: a payload carrying numeric
    PII now hashes differently than it did under v0.3.0, because the hash is
    taken over the redacted payload. Records already stored are unaffected —
    this changes what newly recorded actions hash to. If you compare a digest
    captured under v0.3.0 against a v0.3.1 re-run of the same payload, expect a
    mismatch; that is this fix, not tampering.

    FinancialPIIConfig and HealthcarePIIConfig are the most affected, since
    their patterns (account_number, routing_number, npi, plus ssn and
    credit_card from the standard set) are pure-digit and so match exactly the
    fields most likely to be serialized as numbers.

Not in this release

  • No wire change. Receipts are client-side artifacts. The ingest spec is
    untouched apart from a note recording that PREDECESSOR_REJECTED was
    considered and rejected (see below).
  • No receipt signing. Receipts are content-hashed and ride under the
    bundle manifest. Signing remains ADR-014 Decision 5's deferral.
  • No cross-implementation fixture vectors. Issue #37's broader ask —
    vectors independent implementations can run — is tracked separately. The
    schema frozen here is its prerequisite.
  • replay-pending does not write receipts yet. The writer lives in a
    shared module so that command can adopt it without duplication.

A design note worth recording

The drafted amendment proposed a PREDECESSOR_REJECTED error code, on the
premise that a store which refused sequence N cannot accept N+1. The spec
says the opposite.
§7 rule 3 forbids a rejection from aborting later batch
elements, and §8.3 forbids rejecting a record for a prev_action_hash the
store has never seen — because doing so "would erase the only evidence that
anything was lost."

So no cascade exists for such a code to name, and adding it would have meant
overturning the strongest evidence-preservation rule in the spec. It was
withdrawn. Fail-fast is kept for a different and better reason: ADR-013
Decision 4's resume contract wants delivery to stay a prefix, so the spool
file alone can answer "where does the next run start."

Upgrading

Nothing to do. No configuration, no migration, no schema change. The first
rootsign-admin sync after upgrading starts writing receipts; spool files
written by earlier versions replay exactly as before.