122 lines
5.1 KiB
Markdown
122 lines
5.1 KiB
Markdown
# Internal: Command Restore
|
|
|
|
## Purpose
|
|
|
|
Explain the implemented restore discovery, planning, installation, and
|
|
reporting flow in `internal/app`. User invocation belongs in
|
|
[CLI](../cli.md#session-restore), and the operator recovery procedure and
|
|
physical restore scope belong in
|
|
[Operations](../operations.md#restore-workflow).
|
|
|
|
Restore separates remote authority, local conflict policy, and filesystem
|
|
mutation so each remains testable independently.
|
|
|
|
## Discovery Contract
|
|
|
|
Discovery delegates current-state pointer and manifest loading to
|
|
`internal/artifacts`, then validates the result against the resolved request:
|
|
|
|
- campaign must match;
|
|
- session ID must match.
|
|
- run ID must match the pointer-selected committed run.
|
|
|
|
Restore treats any missing or invalid remote current state as a command error.
|
|
|
|
## Planning Contract
|
|
|
|
Restore planner action kinds:
|
|
|
|
- `download`;
|
|
- `skip_same`;
|
|
- `conflict`.
|
|
|
|
Planner behavior:
|
|
|
|
- a new-protocol restore uses only the selected commit's declared artifact set;
|
|
each action carries that artifact's immutable key, checksum, size, and
|
|
generation;
|
|
- remote-to-local mapping is traversal-safe;
|
|
- actions are sorted by local relative path and then remote key;
|
|
- planning opens the selected session root and inspects every target by its
|
|
normalized session-relative path. Missing ancestors produce a missing target;
|
|
an ancestor or leaf symlink is a non-regular conflict and is never opened for
|
|
hashing;
|
|
- force converts differing eligible regular files from conflicts to downloads;
|
|
directories, symlinks, and other non-regular targets remain conflicts.
|
|
|
|
For a non-dry-run restore, planning/classification happens only after acquiring
|
|
the session lock. Runner manifest/reuse checks acquire that same lock first.
|
|
|
|
Previous-cache readiness is resolved through `previouscache.Resolve` for restore,
|
|
prepare, status, and validation. Each requirement selects the unique
|
|
`published_output` for its canonical source in the pointer-selected commit,
|
|
then strips only that run's immutable `published/` prefix for its safe local
|
|
cache path. It never probes candidate objects or derives paths from manifests,
|
|
configuration, or basenames.
|
|
|
|
## Execution Contract
|
|
|
|
Execution order and safety:
|
|
|
|
- non-manifest downloads happen before manifest install;
|
|
- `manifest.json` installs last;
|
|
- downloads use sibling temp files, validate the complete object, then use an
|
|
atomic rename;
|
|
- manifest replacement is validated before rename;
|
|
- each committed object is verified against its declared checksum, size, and
|
|
generation before installation;
|
|
- installation reopens the selected session root, creates only validated
|
|
missing directory components with workspace modes, and rejects symlinked or
|
|
non-regular ancestors and leaves before replacement;
|
|
- a committed manifest already verified during discovery is retained for the
|
|
matching restore action and revalidated before installation, avoiding a
|
|
second body transfer;
|
|
- failed installs do not roll back files already written in the same execution.
|
|
- a durable `.restore-incomplete.json` marker is written before installation.
|
|
It blocks runners until a restore retry completes all verified installs and
|
|
the local manifest replacement, at which point it is removed.
|
|
- restored manifest local references are rebased beneath the selected local
|
|
session root. Unsafe relative references and producer-machine absolute paths
|
|
outside the manifest's producer session root are rejected; producer-local
|
|
spool paths and run-local diagnostic references are not restored as
|
|
authority. Cleanup remains ordinary restored stage state and is not reset or
|
|
synthesized by restore.
|
|
|
|
Audio restore path:
|
|
|
|
- uses `audio.MaterializeS3Audio`;
|
|
- integrates spool and S3 audio cache paths;
|
|
- reuses cached audio only when its opened regular file, content digest, and
|
|
identity sidecar all match the selected remote object version; otherwise it
|
|
refreshes through the validated download path.
|
|
|
|
## Reporting Contract
|
|
|
|
- dry-run mode prints a summary, performs no durable session writes, and may
|
|
read remote current-state or object-identity data to produce that summary;
|
|
- execution mode persists the canonical restore report described in
|
|
[Operations](../operations.md#restore-workflow);
|
|
- report includes plan counts, per-action status, and execution failures.
|
|
|
|
## Invariants
|
|
|
|
- restore uses committed remote current state as authority;
|
|
- one restore or status inspection observes the single pointer-selected commit
|
|
loaded at discovery; later pointer changes cannot add objects or substitute a
|
|
different run into its plan;
|
|
- a verified `current/commit-pointer.json` and its selected immutable commit
|
|
establish remote commitment; when absent, republish the session;
|
|
- restore does not execute pipeline stages.
|
|
|
|
## Implementation And Tests
|
|
|
|
- Discovery: `internal/app/restore_discovery.go`
|
|
- Planning: `internal/app/restore_plan.go`, `internal/previouscache`
|
|
- Execution: `internal/app/restore_execute.go`
|
|
- Reporting and command coordination: `internal/app/restore_report.go`,
|
|
`internal/app/restore.go`
|
|
- Tests: `internal/app/restore_discovery_test.go`,
|
|
`internal/app/restore_plan_test.go`,
|
|
`internal/app/restore_execution_test.go`,
|
|
`internal/app/restore_workflow_test.go`
|