Recovery Readiness Audit Guide¶
Guide Maps¶
graph LR
family["Reproducible Research"]
program["Deep Dive DVC"]
guide["Capstone docs"]
section["Docs"]
page["Recovery Readiness Audit Guide"]
proof["Protected revision audit"]
family --> program --> guide --> section --> page
page -.checks against.-> proof
flowchart LR
contract["name protected revisions"]
migrate["populate the candidate remote"]
isolate["discard reviewer cache"]
restore["restore each protected revision"]
compare["compare identity and duration"]
decide["accept or block cutover"]
contract --> migrate --> isolate --> restore --> compare --> decide
A remote is not ready for cutover because dvc push completed or because the current
workspace can pull. Cutover is supportable only when every state the project promises to
retain can be restored and identified from the candidate remote without help from an
author's cache.
This audit turns that claim into a small experiment. It builds two distinct revisions of one DVC-tracked dataset:
release-2025-q1is a published three-row state retained for later auditmainis a current four-row state retained for collaboration and automation
Each revision has its own expected SHA-256 identity and row count. A reviewer receives a fresh Git clone, discards local DVC cache state, restores from the candidate remote, and compares the result with the contract.
The decision boundary¶
The audit accepts remote cutover only when all of these claims hold:
| Claim | Evidence |
|---|---|
| retention scope is explicit | at least two protected revisions are named in recovery-contract.json |
| Git history resolves | each protected revision can be checked out in a clean clone |
| candidate storage is complete | each revision can be pulled after its reviewer cache is removed |
| restored meaning is unchanged | SHA-256 and row-count claims match the restored artifact |
| verification does not repair storage | the recovery route contains no push, garbage collection, or remote reconfiguration |
| recovery speed is testable | a positive recovery-time objective is declared and each observed pull stays within it |
The time measurement in this local specimen is not a production service-level result. Its teaching purpose is to make the contract shape visible: objective first, observed duration second, decision last.
Why the audit creates separate reviewers¶
Testing both revisions in one workspace can leak evidence:
- the first restore can populate cache for the second
- the author's cache can hide an incomplete remote
- a changed pointer can be mistaken for changed content
- a successful current-state pull can be generalized to all history
The audit therefore gives every protected revision a separate clone and an empty cache. The candidate remote is the only permitted content source. This arrangement makes a missing historical object fail where it should fail.
The six cases¶
Start with summary.tsv. It places the intended decision beside the observed decision:
| Case | What is true | Correct decision |
|---|---|---|
| complete migration | candidate remote contains both protected DVC objects | ACCEPT |
| current-only copy | current state restores but the published release does not | BLOCK |
| release-only copy | the published release restores but current state does not | BLOCK |
| stale release claim | both objects restore, but the published identity claim is wrong | BLOCK |
| mutating recovery route | objects restore, but the proposed check includes dvc push |
BLOCK |
| missing time objective | objects restore, but recovery duration has no declared limit | BLOCK |
These cases separate mechanisms that are often collapsed into one vague statement: "the backup works." A remote can be reachable but incomplete. A pull can succeed while a claim is stale. A route can finish only because it repairs the storage it was supposed to evaluate.
Run the audit¶
From the course directory:
Or from capstone/:
Generated evidence is written under the repository's artifacts/audit/ tree. The source
specimen is never initialized or changed in place.
Read evidence from decision to mechanism¶
Use this order:
summary.tsvshows whether each case reached the expected cutover decision.report.jsonshows the checks that supported or blocked each decision.<case>/revision-evidence.jsonshows revision resolution, restore result, expected and actual identity, row count, and measured duration.<case>/receipts.jsonshows the exact Git and DVC commands, return codes, standard output, and standard error.specimen/recovery-contract.jsonshows what the repository promised before the drill.scripts/audit_recovery_readiness.pyand the focused test show how the promise is enforced.
Read receipts after the decision surfaces. Raw command output can explain a failure, but it should not force every reviewer to reconstruct the decision rule from scratch.
Investigate current-only migration¶
In CURRENT_ONLY_COPY, open the two revision records side by side.
Expected pattern:
| Revision | Restored? | Identity matches? | Meaning |
|---|---|---|---|
release-2025-q1 |
no | no | candidate remote omitted protected history |
main |
yes | yes | current workspace coverage is real but insufficient |
The mainline success is not a partial excuse. It is the reason this failure is easy to miss in an ordinary smoke test.
Then open that case's receipts.json and compare:
- the author command that copied current state
- the release reviewer's failed cache-empty pull
- the main reviewer's successful cache-empty pull
The contrast is direct evidence that remote reachability and historical coverage are different properties.
Investigate a stale content claim¶
STALE_RELEASE_CLAIM restores both artifacts. If review ended at the pull return code,
cutover would appear safe.
The release evidence instead shows:
restored: true- an actual SHA-256 derived from the recovered bytes
- an expected SHA-256 taken from the contract
content_claim_matches: false
Recovery has two layers:
flowchart LR
mechanism["Can storage return bytes?"]
meaning["Are these the promised bytes?"]
decision["May the project retire the old source?"]
mechanism --> meaning --> decision
Both layers must pass. Restoring unverified bytes is availability evidence, not release identity evidence.
Investigate a mutating route¶
The MUTATING_RECOVERY_ROUTE case restores both revisions and matches both content
claims. It still blocks.
A route containing dvc push can fill the candidate remote from whatever cache is
available to the operator. That may be useful during migration, but it is not an
independent cutover check. The audit separates:
| Activity | Storage permission | Purpose |
|---|---|---|
| migration | write | populate candidate storage |
| verification | read | test candidate storage as it stands |
| repair | write | correct a rejected candidate |
| re-verification | read | test again after the repair is complete |
Combining these activities produces evidence that cannot say whether the candidate was complete before the check began.
Use the result in a cutover review¶
An acceptable review note should name:
- the protected revision set
- the candidate remote under test
- the fact that reviewers started without artifact cache
- content-identity results for every protected revision
- observed durations and the declared objective
- the audit bundle location
- the explicit decision about retiring the legacy remote
A blocked note should name the failed check rather than saying only "migration failed." For example:
Candidate storage restores
mainbut notrelease-2025-q1. The published revision remains protected by retention policy, so the legacy remote cannot be retired. Copy the missing release object, preserve the failed receipts, and rerun the read-only audit.
That note preserves the failure and gives repair a bounded target.
What this audit does not prove¶
This local specimen does not prove:
- cloud-provider durability or regional redundancy
- credential rotation under a real identity system
- production-scale transfer performance
- recovery of every artifact family in another repository
- organizational approval for deleting or decommissioning storage
It teaches the evidence structure those broader checks need: explicit scope, empty-cache restore, content identity, measured duration, read-only verification, and a gated cutover decision.