Skip to content
BitExactdocs
Documentation
On this page

Retention and redaction lifecycle#

Regulated deployments need destructive and time-bound operations to be governed: two people for an irreversible erasure, an explicit retention schedule per workflow, and a legal hold that overrides both. BitExact makes each act tamper-evident evidence in itself.

Dual-control (two-person) redaction#

Redaction destroys committed values irreversibly (the field's commitment is kept, so the run still verifies, but the value is gone). Beyond the RBAC and CSRF gates, a redaction takes two people:

  1. Request — one principal names the steps/fields and a reason. bitexact redaction request <run> --steps 5 --fields 5:result.ssn --reason "PII" (or the run-detail Redact drawer). The request is recorded on the run's tamper-evident custody chain as redaction-requested.
  2. Approve — a different principal approves; only then is anything destroyed. bitexact redaction approve <run> --by <approver>. Self-approval is refused, and the refusal names the requester. The approval records, on the custody chain, the requester, the reason, the field count, and the pre → post head — the explicit re-seal.
  3. Reject clears the request without redacting, and takes a reason of its own (--reason, or reason in the API body - required), recorded on the custody chain as redaction-rejected beside the request it answers.

bitexact redaction status <run> shows what is pending on one run, and GET /api/redactions every pending request on the runs the caller may see - its run, steps, fields, reason, requested_by and requested_at - so an approver can find them. The run's own GET /api/runs/<run>/redaction names its requester, who may not approve it, and both say in dual_control whether this inspector's identities can complete a two-person redaction at all; so does the answer to a request.

The erasure itself is the existing on-chain redact_in_place: the redaction lands as a chained audit entry before any value is destroyed (write-ahead), and the run is re-verified after. The single-control bitexact redact remains as an admin break-glass primitive; the two-person lifecycle is the governed path.

Making dual-control mandatory. Set BITEXACT_REQUIRE_DUAL_CONTROL=1 to disable the immediate redact path entirely (CLI and API) so every redaction must go through request → approve. On the command line the actor is whatever --by is typed, so with dual control set bitexact redaction request and bitexact redaction approve refuse as well: both decisions are taken in the viewer, where each principal is authenticated. status and reject destroy nothing and stay available on the CLI. The two-person guarantee is only as strong as the identities your deployment asserts: it is meaningful under reverse-proxy/SSO auth, where the requester and approver are distinct proxy-asserted principals, and under named viewer tokens (<token>:<name>:<role>, principal token:<name> - docs/identity-access.md). An unnamed token names a role, not a person (the principal is token:<role>), so two holders of the same role's unnamed token are one principal and the approval is refused as self-approval.

Retention policy per workflow#

A workflow declares a retention schedule as a governance act on its append-only ledger:

bitexact workflow set-retention claims-triage --schedule adverse=7y,standard=3y

Each run commits its own retention_deadline to its chain (in a decision_record); that committed deadline — not the mutable policy — is the floor bitexact purge honors (see [#76]). bitexact retention <workflow> (and the workflow page's Retention lifecycle panel) reconcile the two: which runs are past their committed deadline and purge-eligible, which carry no committed deadline at all (a policy-application gap), and which are on legal hold. A declared policy with runs missing a deadline exits non-zero — a CI signal that the policy was not applied everywhere.

BitExact reports the gap between the declared schedule and the committed deadlines; it never rewrites a committed deadline, which stays evidence. The report's run_ids names the runs behind each summary count (past_due_purgeable, missing_deadline, on_hold).

What a purge would take. GET /api/retention/purge-preview?older_than=<days>&mode=delete|archive answers what bitexact purge --older-than <days> --delete (or --archive) would do now, over the runs the caller may see, and changes nothing: the runs it would remove (would_purge), and the ones it would refuse (blocked, each with blocked_by - hold, deadline, recording or unreadable - and the reason the command prints). It applies the command's own rule, so the preview and the purge agree on every run. purge reclaims the embedded store only; on the PostgreSQL backend the preview answers available: false and points at bitexact archive.

A legal hold (bitexact hold <run> --reason ... --by ... / the run header control) blocks purge and overrides the retention deadline: a held run is never purge-eligible regardless of its committed deadline. The hold is a chained hold audit entry on the run itself - permitted after the seal like a redaction claim, covered by a fresh recorder checkpoint when the recorder signs - and a release (bitexact release-hold <run> --reason ... --by ...) is a chained hold_release entry. Placing and releasing each require a reason - the matter or ticket the hold is for, and why it can end - recorded in the chained entry and, from the viewer, on the run's custody chain; an empty one is refused before anything is written. Nothing outside the chain places or lifts a hold: there is no marker file to delete, the hold travels with the run through export, migration and archive, and the standalone verifier verifies it with the rest of the chain. A hold does not make a run read as recent: retention ages a run by its evidence, not by the audit entries placed on it. A hold can be placed and released whatever the license's state, expired included, and so can a redaction be requested, decided and applied: legal hold and erasure are obligations owed to a court or a data subject, and a late renewal must never be the reason one is missed. Releasing a hold deletes nothing - bitexact purge itself waits for a valid license.