diff --git a/docs/superpowers/plans/2026-07-27-backup-tool-v2-implementation.md b/docs/superpowers/plans/2026-07-27-backup-tool-v2-implementation.md new file mode 100644 index 0000000..6321930 --- /dev/null +++ b/docs/superpowers/plans/2026-07-27-backup-tool-v2-implementation.md @@ -0,0 +1,275 @@ +# Backup Tool v2 Implementation Plan + +**Status:** Ready for execution after branch kickoff +**Date:** 2026-07-27 +**Design authority:** [`../specs/2026-07-27-backup-tool-reimplementation-design.md`](../specs/2026-07-27-backup-tool-reimplementation-design.md) +**Replaces:** [`2026-05-11-backup-tool-implementation.md`](./2026-05-11-backup-tool-implementation.md) for all v2 work + +## 1. Reason for Existence + +This plan converts the decision-closed design into dependency-ordered, test-first, independently reviewable milestones. It prevents framework-first rebuilding: repository format, publication safety, restore, and crash recovery become executable contracts before scheduling, optimization, UI breadth, or v2.1 adapters. + +A milestone is complete only when its named behavior tests pass, its failure path is proved, its design decisions and acceptance invariants are traceable, and the working tree contains no unrelated changes. + +## 2. Scope and Guardrails + +- V2 is a clean rewrite. It MUST NOT contain `/api/v1`, legacy DB readers, importers, converters, timestamp-directory parsers, or v1 payload access. +- Preserve v1 only as a Git tag/branch for historical reference before deleting executable v1 code. No v1 file or test is adapted into a compatibility layer. +- V2.0 includes local and SSH/SFTP sources, local repositories, restore, optional repository encryption, recovery bundles, webhooks, and email. +- PostgreSQL, MySQL, and generated TAR download are v2.1-only capabilities and MUST remain absent from v2.0 OpenAPI, UI, image, and capability metadata. +- Certified topology is one node, SQLite/WAL, one scheduler process, one worker process, and one or more web processes. Web processes MUST perform no backup, restore, scheduler, or repository I/O. +- Every task follows red → green → refactor. Preserve failing-then-passing command output in PR/CI evidence. +- Do not start a dependent milestone until its dependency gate is green. Do not split an atomic set from §6 across releases. + +## 3. Target Project Layout + +```text +backend/ + pyproject.toml + alembic/versions/ + src/backup_tool/ + api/{app,deps,errors,pagination,etag,idempotency,sse,routers/,schemas/} + adapters/{base,local,sftp,postgres,mysql}.py + db/{engine,models,repositories}.py + domain/{manifest,paths,exclusions,transitions,cron,retention}.py + execution/{enqueue,claims,leases,runner,retry,cancellation,progress}.py + notifications/{events,dispatcher,webhook,email,retry}.py + observability/{logging,metrics,health}.py + repository/{layout,blob_store,manifest_store,staging,publisher,verifier,restore,reconcile,gc,encryption}.py + security/{auth,secrets,redaction,repository_crypto,recovery_bundle,ssrf}.py + services/{sources,jobs,repositories,backups,restores}.py + scheduler/service.py + {cli,config,ids,clock}.py +frontend/src/ + api/generated/ + app/ + pages/ + components/ +contracts/repository/v1/ +openapi/v2.json +tests/{unit,contract,integration,fault,security,e2e,performance,fixtures}/ +tools/{export_openapi,forbidden_v1_scan,leakage_scan,check_traceability,run_scale_certification}.py +docs/{runbooks,security,release}/ +``` + +Module rules: domain code imports no FastAPI, SQLAlchemy, filesystem, Paramiko, SMTP, or HTTP client; adapters and repository code implement ports; services own use cases; routers translate HTTP only; ORM and API schemas never act as domain models. + +## 4. Dependency Spine + +```text +M0 contracts + └─ M1 runtime/DB ─ M2 auth/API + └─ M3 repository ─ M4 sources/jobs ─ M5 execution + └─ M6 full backup/verify/restore + ├─ M7 incremental/exclusions + ├─ M8 scheduling + ├─ M9 retention/GC/reconcile + ├─ M10 SSH/SFTP + └─ M11 encryption/recovery + └─ M12 notifications ─ M13 UI/API ─ M14 operations ─ M15 v2.0 +M15 ─ M16 PostgreSQL ─ M17 MySQL ─ M18 TAR/download ─ v2.1 +``` + +M7-M11 MAY be developed on separate branches after M6, but merge in dependency order and never use concurrent writers in one worktree. + +## 5. Milestones + +### M0 — Freeze protocol and test foundation + +**Depends on:** approved design. +**Deliver:** lock Python/Node/tool versions; create package/test layout and `Makefile`; commit canonical repository/manifest JSON schemas, golden valid/invalid fixtures, normalized-path vectors, state-transition table, error-code catalog, capability schema, and deterministic fault-point names. Tag/archive v1, then remove `backend/app`, `backend/backup`, old tests, and handwritten `frontend/src/api/client.ts`. +**Verify:** `python tools/forbidden_v1_scan.py . && python -m pytest tests/contract/test_repository_format.py tests/contract/test_no_v1.py -q` +**Commit:** `chore(v2): establish protocol and test foundation` + +### M1 — Runtime, UUIDv7, SQLite/WAL, and Alembic baseline + +**Depends on:** M0. +**Deliver:** typed config; canonical source/repository/restore allowlists; key-file owner/mode checks; UUIDv7 IDs; UTC clock port; role CLIs; SQLite foreign keys/WAL/busy timeout; complete explicit schema, constraints, indexes, and first hand-reviewed migration. Startup checks migration state and never calls `create_all`. +**Verify:** `cd backend && python -m alembic upgrade head && python -m alembic downgrade base && python -m alembic upgrade head && python -m pytest ../tests/unit/test_config.py ../tests/integration/test_migrations.py -q` +**Commit:** `feat(v2): add validated runtime and metadata store` + +### M2 — Admin setup, authentication, secrets, audit, and API conventions + +**Depends on:** M1. +**Deliver:** guarded one-admin setup; Argon2id; secure session/CSRF; hashed scoped API tokens; envelope-encrypted write-only secrets; request IDs; append-only audit; RFC 9457 problems; stable cursor pagination/sort; ETags; request-digest idempotency; truthful `/livez` and `/readyz`. +**Verify:** `python -m pytest tests/unit/test_redaction.py tests/contract/test_api_conventions.py tests/integration/test_auth.py tests/security/test_auth.py -q && python tools/leakage_scan.py` +**Commit:** `feat(v2): secure identity secrets and API conventions` + +### M3 — Initialize and inspect repositories + +**Depends on:** M2. +**Deliver:** allowlisted local repository init; `repository.json`; immutable compression/encryption choice; canonical JSON; plaintext SHA-256 blob identity; capacity probe; minimum free bytes/percent; safe partial-init cleanup; path/symlink containment. Encryption interfaces exist, but encrypted creation remains capability-disabled until M11. +**Verify:** `python -m pytest tests/contract/test_repository_format.py tests/integration/test_repository_init.py tests/security/test_repository_paths.py -q` +**Commit:** `feat(v2): initialize immutable content repositories` + +### M4 — Typed local sources and repository-targeted jobs + +**Depends on:** M3. +**Deliver:** adapter protocol; normalized POSIX entries; bounded local streaming; symlink/cross-filesystem/special-file policy; source probes; typed source/job CRUD; immutable source kind; archive semantics; repository IDs instead of destination strings; enabled, mode, exclusions, retention, and `allow_empty`. +**Verify:** `python -m pytest tests/unit/test_paths.py tests/unit/test_exclusions.py tests/integration/test_local_source_api.py tests/security/test_source_containment.py -q` +**Commit:** `feat(v2): add local sources and repository jobs` + +### M5 — Durable execution lifecycle + +**Depends on:** M4. +**Deliver:** monotonic states; `202` execution resource; `409` active execution; DB-enforced one nonterminal execution/job; disabled-job check inside enqueue; leases, heartbeats, retries using one execution ID, attempts, cancellation, reason codes, redacted progress/logs, SSE with polling fallback, graceful shutdown, stale-worker fencing, startup reconciliation. +**Verify:** `python -m pytest tests/unit/test_execution_transitions.py tests/integration/test_queue_leases.py tests/fault/test_worker_loss.py -q` +**Commit:** `feat(v2): add durable leased execution lifecycle` + +### M6 — Atomic full backup, verification, and restore + +**Depends on:** M5. +**Deliver:** unique staging; capacity preflight; adapter-only reads; chunked hashing; immutable blob install; canonical complete manifest; fsync/atomic rename where supported; object verification; metadata visibility only after durable publish; reconciliation marker; metadata/full verification; durable dry-run/selected restore; `fail|skip|replace`; destination containment; result manifest; integrity states. +**Verify:** `python -m pytest tests/integration/test_full_backup_restore.py tests/fault/test_publication_crashes.py tests/security/test_restore_paths.py -q` +**Commit:** `feat(v2): publish verified snapshots and durable restores` + +### M7 — Exclusions, independent incrementals, and empty-source safety + +**Depends on:** M6. +**Deliver:** versioned gitignore semantics shared by every adapter; compatible-baseline selection; requested/effective mode; `baseline_missing`; blob reuse; declared metadata-trust rules; independently restorable complete manifests; empty-source opt-in and vanished-source confirmation. +**Verify:** `python -m pytest tests/unit/test_exclusions.py tests/integration/test_incremental.py tests/integration/test_empty_source.py -q` +**Commit:** `feat(v2): add safe deduplicated snapshots` + +### M8 — Deterministic durable scheduling + +**Depends on:** M6. +**Deliver:** one schedule/job; semantic five-field cron; IANA timezone; next run/last result; `(schedule_id, nominal_utc)` uniqueness; immediate create/update/delete synchronization; 15-minute grace; coalesce/no-overlap; exact DST gap/fold behavior; disabled job/schedule rejection; same enqueue service as manual execution. +**Verify:** `python -m pytest tests/unit/test_cron_dst.py tests/integration/test_scheduler_live_sync.py tests/fault/test_schedule_delivery.py -q` +**Commit:** `feat(v2): schedule idempotent timezone-aware executions` + +### M9 — Retention, tombstones, reconciliation, and safe GC + +**Depends on:** M6 and M7. +**Deliver:** union policy for keep-last/days/daily/weekly/monthly/pins; newest protection; preview/apply; archive without payload deletion; tombstone/audit; grace plus second reference scan; `deletion_failed`; confirmed purge/report; bidirectional reconciliation; unknown-object quarantine; shared-blob safety. +**Verify:** `python -m pytest tests/unit/test_retention.py tests/integration/test_gc.py tests/fault/test_deletion_failure.py tests/integration/test_reconcile.py -q` +**Commit:** `feat(v2): add retention reconciliation and safe GC` + +### M10 — Strict SSH/SFTP parity + +**Depends on:** M6 and M7. +**Deliver:** typed remote config; write-only auth; pinned known-host key; fail-closed probe and execution; bounded SFTP enumeration/streaming; timeouts/backpressure; common paths/exclusions; source-consistency metadata; transient classification; no local traversal fallback. +**Verify:** `docker compose -f tests/compose.integration.yaml up -d sshd && python -m pytest tests/integration/test_sftp_backup_restore.py tests/security/test_sftp_host_keys.py -q` +**Commit:** `feat(v2): add pinned-host SFTP backups` + +### M11 — Optional repository encryption and offline recovery + +**Depends on:** M6. +**Deliver:** AEAD stored objects; associated metadata; key IDs and rotation; immutable policy; explicit confidentiality/equality-leakage threat model; separately passphrase-protected recovery bundle; non-disclosing validation; fresh-host metadata/key recovery and encrypted restore. Enable encrypted repository creation only after the host-loss test passes. +**Verify:** `python -m pytest tests/integration/test_encrypted_repository.py tests/integration/test_recovery_bundle.py tests/security/test_crypto_leakage.py -q` +**Commit:** `feat(v2): encrypt repositories and recover keys offline` + +### M12 — Signed webhooks and auditable email + +**Depends on:** M2, M5, and M7-M11. +**Deliver:** complete operational event catalog covering execution, schedule, retention/GC, reconciliation, SSH, encryption/recovery, restore, verification, capacity, and security outcomes; typed filters/subscriptions; stable event IDs; durable outbox/deliveries/attempts; webhook signature/version and rotation; at-least-once bounded retry; SMTP transient retry; manual test/retry; history; rate limits; write-only credentials; redirect/DNS/IP/private-network/rebinding SSRF policy. +**Verify:** `python -m pytest tests/contract/test_notification_contract.py tests/integration/test_all_operational_events_deliver.py tests/integration/test_notifications.py tests/fault/test_notification_retries.py tests/security/test_webhook_ssrf.py -q` +**Commit:** `feat(v2): add webhook and email delivery` + +### M13 — OpenAPI-generated client and operator UI + +**Depends on:** M7-M12. +**Deliver:** deterministic committed OpenAPI; generated TypeScript client only; drift CI; Setup/Login, Dashboard, Sources, Repositories, Jobs/Schedule, Executions/SSE, Backups/Verify/Restore/Delete Preview, Security/Recovery, Notifications/History, and Audit. Implement loading, empty, validation, partial failure, reconnect, session expiry, responsive, keyboard, focus, contrast, reduced-motion, and screen-reader states. Hide v2.1 controls completely. +**Verify:** `python tools/export_openapi.py --check openapi/v2.json && npm --prefix frontend run api:generate && git diff --exit-code -- openapi/v2.json frontend/src/api/generated && npm --prefix frontend test -- --run && npm --prefix frontend run build && npx --prefix frontend playwright test` +**Commit:** `feat(v2): ship generated-client operator workflows` + +### M14 — Production roles, observability, recovery, and packaging + +**Depends on:** M13. +**Deliver:** one pinned non-root OCI image with `web|scheduler|worker|migrate|admin`; same-origin proxy; one scheduler and worker; no reload or embedded roles; role-aware readiness; structured logs; required metrics/alerts; metadata/repository/key/upgrade/disaster runbooks; restart persistence; safe shutdown; SBOM/provenance; v2.0 image contains no DB dump clients. +**Verify:** `docker compose build --pull && docker compose run --rm migrate upgrade && docker compose up -d && python -m pytest tests/e2e/test_compose_v2.py tests/security/test_container.py -q && docker compose down` +**Commit:** `build(v2): package isolated roles and recovery operations` + +### M15 — Certify and release v2.0 + +**Depends on:** M0-M14. +**Deliver:** reference-host definition; workloads for 100 jobs, 1M entries or 10 TiB logical/backup, and 100k cataloged backups; throughput/CPU/memory/DB/lag/pagination/restore/verify evidence; bounded-memory and accepted-regression gates; complete fault/security/leakage/E2E evidence; truthful capabilities/docs; removal of all v2.1 controls. +**Verify:** `python tools/run_scale_certification.py --jobs 100 --entries 1000000 --backups 100000 && python tools/assert_capabilities.py --release v2.0 --include local,ssh,restore,webhook,email --exclude postgresql,mysql,tar-download` +**Commit:** `release: certify backup-tool v2.0.0` + +### M16-M18 — V2.1 vertical slices + +- **M16 PostgreSQL (depends on M15):** secret-safe bounded `pg_dump`, TLS, tool/server matrix, full-only manifest, cancellation/error classification, typed source CRUD/probe, capability metadata, OpenAPI/generated client/UI form, and v2.1 OCI dump-client packaging land atomically → verify: `python -m pytest tests/contract/test_postgres_source_api.py tests/integration/test_postgres_adapter.py tests/security/test_dump_secrets.py tests/security/test_v21_container.py -q && make check-openapi && npm --prefix frontend run build && python tools/assert_capabilities.py --release v2.1 --include postgresql` → `feat(v2.1): add PostgreSQL logical snapshots`. +- **M17 MySQL (depends on M16):** secret-safe bounded `mysqldump`, TLS/locking consistency, compatibility matrix, full-only manifest, typed source CRUD/probe, capability metadata, OpenAPI/generated client/UI form, and OCI client verification land atomically → verify: `python -m pytest tests/contract/test_mysql_source_api.py tests/integration/test_mysql_adapter.py tests/security/test_dump_secrets.py tests/security/test_v21_container.py -q && make check-openapi && npm --prefix frontend run build && python tools/assert_capabilities.py --release v2.1 --include postgresql,mysql` → `feat(v2.1): add MySQL logical snapshots`. +- **M18 TAR/download and v2.1 release (depends on M17):** verify every blob/tag before streaming manifest-derived safe entries; traversal/link/device/disconnect tests; generated API/client/UI capability; then rerun every v2.0 gate, both DB matrices, OpenAPI drift, Compose/E2E, leakage, and reference-host regression → verify: `make check && make test-integration && make test-fault && make test-security && make test-e2e && make check-openapi && make check-traceability && make check-v1-absent && python -m pytest tests/integration/test_postgres_adapter.py tests/integration/test_mysql_adapter.py tests/security/test_dump_secrets.py tests/integration/test_tar_download.py tests/security/test_tar_paths.py -q && python tools/run_scale_certification.py --regression-from docs/release/v2.0-evidence.md && python tools/assert_capabilities.py --release v2.1 --include local,ssh,postgresql,mysql,restore,tar-download,webhook,email && npx --prefix frontend playwright test` → `release: certify backup-tool v2.1.0`. + +## 6. Atomic Sets and Stop Rules + +The following land as indivisible reviewed sets: publication + reconciliation marker + crash matrix; execution state + uniqueness + leases + cancellation + recovery; auth + secrets + redaction + audit; restore record + containment + verification + atomic placement; retention + tombstone + reference scans + quarantine; scheduler parser + uniqueness + DST/misfire + live sync; encryption + recovery bundle + host-loss restore; notification outbox + signing + SSRF + SMTP + retry/history; OpenAPI + generated client + drift check + corresponding UI. + +Stop and request an ADR/design amendment if implementation requires changing manifest canonicalization, blob identity, external IDs, process topology, delivery guarantee, schedule semantics, encryption confidentiality, retention precedence, v2.0/v2.1 capability split, or any D1-D21 decision. Stop a milestone when a failure test is nondeterministic, a destructive test can reach operator data, a zero-test selection can pass, or a secret/path canary appears in any output sink. + +## 7. Traceability + +| Decisions | Owner and proof | Decisions | Owner and proof | +| --- | --- | --- | --- | +| D1 | M0-M2: UUID/API/no-v1 contract | D2 | M2: concurrent setup test | +| D3 | M1, M5, M14: role/process test | D4 | M1, M14: pragmas/topology test | +| D5 | M2, M5: idempotency/lease tests | D6 | M3-M4: repository-only job/path tests | +| D7 | M0, M3, M6: manifest/independent restore | D8 | M3, M11: immutable policy/AEAD tests | +| D9 | M0, M4, M7, M10: shared matcher corpus | D10 | M8: cron/timezone/DST tests | +| D11 | M4-M5, M8: disabled enqueue tests | D12 | M4, M9: archive/restorability tests | +| D13 | M13: generated-client zero-diff test | D14 | M1: Alembic/no-`create_all` test | +| D15 | M10, M15-M17: capability gates | D16 | M15: certified reference-host report | +| D17 | M11: fresh-host recovery drill | D18 | M0, M15: forbidden-v1 scan/runtime test | +| D19 | M13, M15, M18: capability/route gates | D20 | M12-M13, M15: channel/event E2E | +| D21 | M12: stable-ID retry/history tests | — | — | + +| Invariants | Owner and proof | Invariants | Owner and proof | +| --- | --- | --- | --- | +| I1 | M6: publication crash matrix | I2 | M8: replayed occurrence uniqueness | +| I3 | M5: concurrent active-job starts | I4 | M5-M6: transition/restart matrix | +| I5 | M10, M16-M17: adapter-spy tests | I6 | M6-M7: parent-free restore corpus | +| I7 | M9: retain/GC/restore property tests | I8 | M4, M9: archive inventory test | +| I9 | M9: deletion failure/retry test | I10 | M2, M11-M12, M14: canary scan | +| I11 | M10: host-key fail-before-read test | I12 | M7, M10, M16-M17: matcher parity | +| I13 | M5, M8: all-trigger disable tests | I14 | M8: live-sync/misfire/DST tests | +| I15 | M0, M15: protected-v1 no-access test | I16 | M13, M18: OpenAPI/client zero diff | +| I17 | M6: full restore matrix | I18 | M6, M11, M18: corruption matrix | +| I19 | M7: empty/vanished-source test | I20 | M3-M4, M6, M10, M18: containment | +| I21 | M11: recovery-bundle host-loss drill | I22 | M12: webhook/email retry history | + +`tools/check_traceability.py` MUST require every D1-D21 and I1-I22 row to name a milestone, test path, runnable command, and immutable evidence artifact. + +## 8. Whole-Project Verification + +```bash +make test-fast # unit + contract; nonempty marker assertion +make test-integration # temp DB/repository roots only +make test-fault # deterministic failpoints +make test-security # canaries, containment, auth, SSRF +make test-e2e # production-like Compose +make check-openapi # export, generate, zero diff, type/build +make check-traceability # D1-D21 and I1-I22 evidence links +make check-v1-absent # static + runtime protected-v1 fixture +make check # format, lint, type, all non-scale tests +git diff --check +git status --short +``` + +Fault/destructive tests MUST allocate hermetic temporary source, repository, restore, DB, key, SMTP, and webhook targets. Test teardown MUST reject paths outside its temp root. CI MUST assert each selected marker collected at least one test. + +## 9. Release Gates + +**V2.0:** M0-M15 green; every D1-D21 item applicable to v2.0 and I1-I22 has immutable evidence; no secret canary; fault matrix green; OpenAPI/client diff clean; local and SSH restore corpus green; encrypted fresh-host recovery green; notifications retry across process restart; Compose roles/non-root/persistence green; scale report approved; capabilities exclude PostgreSQL, MySQL, and TAR download. + +**V2.1:** v2.0 gates remain green; PostgreSQL/MySQL compatibility matrices pass; dump credentials never enter argv/logs; TAR corruption/path/disconnect tests pass; generated API/client/UI exposes only installed capabilities; performance regression remains within the accepted threshold. + +## 10. Execution Discipline + +Use one feature branch/worktree per milestone. Keep commits small within the milestone but merge only when its atomic gate is closed. Conventional commit title matches the milestone; PR body links RED/GREEN evidence, design IDs, invariants, commands with exit codes, changed files, residual risks, and rollback. Run a fresh-context security/reliability review after M2, M6, M9, M11, M12, M14, and before each release. + +**Next action:** start M0 by creating the contract fixtures and forbidden-v1 test before scaffolding application modules. + +## 11. Plan Verification + +```bash +python - <<'PY' +from pathlib import Path +p = Path('docs/superpowers/plans/2026-07-27-backup-tool-v2-implementation.md') +s = p.read_text() +for marker in ['M0', 'M15', 'M18', 'D1-D21', 'I1-I22', 'verify:', 'Release Gates']: + assert marker in s, marker +assert s.count('**Verify:**') >= 16 +print('v2 implementation plan: OK') +PY +``` diff --git a/docs/superpowers/specs/2026-07-27-backup-tool-reimplementation-design.md b/docs/superpowers/specs/2026-07-27-backup-tool-reimplementation-design.md new file mode 100644 index 0000000..95901f4 --- /dev/null +++ b/docs/superpowers/specs/2026-07-27-backup-tool-reimplementation-design.md @@ -0,0 +1,368 @@ +# Backup Tool v2 Reimplementation Design + +**Status:** Decision-closed baseline +**Date:** 2026-07-27 +**Supersedes:** [`2026-05-11-backup-tool-design.md`](./2026-05-11-backup-tool-design.md) for new implementation work +**Evidence base:** current backend, frontend, tests, deployment files, README, and prior design +**Normative language:** MUST, MUST NOT, SHOULD, and MAY have their RFC 2119 meanings. + +## 1. Reason for Existence + +This document is the implementation contract for a from-scratch v2. It separates behavior that exists, behavior that is broken, and previously proposed features; then defines one coherent product, integrity model, API, architecture, and delivery boundary. An implementation is conformant only when its tests prove the acceptance invariants in §22. + +## 2. Executive Decision + +Backup Tool v2 is a self-hosted, single-organization backup appliance for a trusted administrator. It backs up local files, SSH/SFTP trees, PostgreSQL databases, and MySQL databases into administrator-defined local repositories; schedules and monitors work; verifies, restores, retains, and safely deletes backups. + +V2 is a clean protocol revision, not a line-for-line rewrite. It keeps useful domain concepts—Source, Job, Schedule, Execution, Backup, dashboard, REST API, React UI, SQLite, manual and cron triggers—but rejects accidental compatibility with unsafe or nonfunctional behavior. + +The central design decision is an immutable, content-addressed repository. Each backup has a complete versioned manifest and is independently restorable; incremental mode reduces transfer/storage by reusing blobs rather than creating fragile restore chains. + +## 3. Current-State Audit + +| Area | Current evidence | Classification | V2 treatment | +| --- | --- | --- | --- | +| Source CRUD | `local`, `ssh`, `database`; arbitrary JSON config | Implemented | Preserve concept; replace with typed configs and write-only secrets | +| Job CRUD | Source, strategy, destination path, excludes, enabled | Implemented | Preserve; target a repository ID, add typed policy fields | +| Manual run | In-process FastAPI background task; returns message only | Implemented but fragile | Durable execution resource; return `202` and execution ID | +| Scheduling | One five-field cron row per job; startup-only sync | Partly implemented | Durable, timezone-aware, immediately synchronized scheduler | +| Local backup | Full recursive `copy2` into timestamp directory | Implemented | Replace storage protocol; preserve local source capability | +| SSH/database backup | Adapter connects, engine still traverses a local path | Broken | Implement real adapter enumeration/stream/dump paths | +| Incremental | Links latest full metadata but copies every file | Broken/misnamed | Complete manifests plus deduplicated blobs | +| Exclusions | Stored but ignored by engine | Broken | Gitignore-compatible normalized-path matching | +| Retention | Engine reads nonexistent job fields | Unreachable | First-class policy, preview, audit, safe GC | +| Backups | List/get/delete metadata; payload deletion inconsistent | Partly implemented | Snapshot catalog, verification, restore, download, tombstone deletion | +| Executions | List/get with basic counters and raw error string | Implemented | State machine, progress, reason codes, cancellation, protected logs | +| Settings | Arbitrary string key/value API; UI does not save | Placeholder | Typed settings only; no arbitrary public key/value store | +| Dashboard | Backend aggregate exists; frontend calls different routes | Contract mismatch | One generated OpenAPI contract | +| Security | No auth; credentials stored and returned in plaintext | Unacceptable | Mandatory admin auth, encrypted secret references, redaction | +| Integrity | Aggregate hash excludes paths/metadata; no restore/check | Insufficient | Per-blob digest, authenticated manifest, verify and restore | +| Deployment | API embeds worker/scheduler; SQLite; Docker/nginx | Implemented | One image/package, explicit web/scheduler/worker roles | + +Current tests establish only source/job CRUD, 404 behavior, local full backup, manual-run acknowledgement, schedule creation, and incremental-without-baseline fallback. They are migration evidence, not authority to preserve weak status codes, exact error strings, timestamp paths, or checksums. + +## 4. Product Definition + +### 4.1 Users and jobs to be done + +- **Administrator:** configure repositories, sources, credentials, jobs, schedules, retention, and security. +- **Operator:** run/cancel jobs, inspect progress and failures, verify backups, restore data, and review storage health. +- A single person MAY hold both roles. V2 has no tenant boundary or general-purpose RBAC. + +### 4.2 Required workflows + +1. Complete first-run admin setup and repository initialization. +2. Add and test a local or SSH/SFTP source without exposing its secret; add PostgreSQL/MySQL sources when v2.1 capabilities are installed. +3. Create an enabled job with source, repository, mode, exclusions, schedule, and retention. +4. Run manually or by cron; observe queue, progress, logs, outcome, and effective strategy. +5. Browse, filter, verify, pin, restore, or request deletion of a committed backup; download becomes available after v2.0. +6. Preview and apply retention; run repository reconciliation and garbage collection. +7. See dashboard health, capacity, next runs, active work, failures, and verification status. +8. Configure signed webhooks and email subscriptions, inspect delivery history, and export an offline key-recovery bundle. + +### 4.3 Initial release scope + +V2.0 MUST ship local and SSH/SFTP sources, local repositories, optional-at-creation repository encryption, full and deduplicating incremental backups, exclusions, durable execution, cron, retention, verification, restore, authenticated UI/API, signed webhooks, email notifications, offline recovery bundles, Docker deployment, and operational diagnostics. PostgreSQL/MySQL sources and generated TAR downloads are committed v2.1 scope. V2 provides no v1 API, metadata importer, or legacy-payload compatibility. + +### 4.4 Non-goals + +Multi-tenancy, enterprise RBAC, horizontal workers, distributed scheduling, continuous data protection, database PITR/WAL/binlog capture, cloud repositories, tape management, cross-repository deduplication, and source-native snapshot orchestration are deferred. + +## 5. Normative Decisions + +| ID | Decision | Consequence | +| --- | --- | --- | +| D1 | External resources use UUIDv7 and `/api/v2`; no v1 API or compatibility shim exists | Clean protocol break; sortable non-enumerable IDs | +| D2 | One install serves one organization and one initial admin | Security remains strong without premature tenancy | +| D3 | Web, scheduler, worker, and migrate are separate runtime roles from one codebase/image | API never performs backup I/O; no duplicate embedded schedulers | +| D4 | SQLite/WAL is the v2 metadata DB and supports one scheduler plus one worker process | Simple single-node deployment; horizontal scale explicitly unsupported | +| D5 | Execution claims live in the DB with leases, heartbeats, idempotency keys, and retries | Restart-safe, effectively-once publication | +| D6 | Jobs target configured repositories, never arbitrary destination paths | Enforce containment, capacity, permissions, and repository policy | +| D7 | Backup = complete logical snapshot manifest over immutable content-addressed blobs | Every retained backup restores independently | +| D8 | Repository encryption is operator-selected at creation; compression/encryption policies are then immutable | Plaintext and encrypted repositories are supported without mixed-policy ambiguity | +| D9 | Exclusions use gitignore semantics over normalized relative POSIX paths | Same predictable result across adapters | +| D10 | One cron schedule per job; IANA timezone is required | Preserve simple model; make DST behavior explicit | +| D11 | Disabled jobs reject manual and scheduled starts | One unambiguous meaning for `enabled` | +| D12 | Source/job deletion archives configuration and stops work; it never silently deletes backups | Data lifecycle remains explicit and auditable | +| D13 | OpenAPI generates the TypeScript client and CI rejects drift | Frontend/backend cannot diverge silently | +| D14 | Alembic is the only schema evolution path; startup never calls `create_all` | Repeatable upgrades and rollback planning | +| D15 | V2.0 ships local and SSH/SFTP sources; PostgreSQL/MySQL follow in v2.1 | Remote file backup is launch scope without delaying on database dump compatibility | +| D16 | Certified single-node scale is 100 jobs, 1M entries or 10 TiB per backup, and 100k backups | Release tests use a documented reference host and publish throughput rather than claiming a hardware-independent rate | +| D17 | Encrypted repositories use a separately passphrase-protected offline recovery bundle | Host loss is recoverable without weakening live key storage | +| D18 | V2 is a clean start with no v1 metadata or payload migration | Reimplementation carries no backward-compatibility code or legacy format liability | +| D19 | Generated TAR download follows in v2.1; verified restore is the v2.0 extraction path | Initial release keeps one secure data-output workflow | +| D20 | Signed webhooks and email notifications ship in v2.0 for all operational event families | Alerts are real product behavior, not placeholder settings | +| D21 | Notifications retry transient failures and preserve delivery history | Webhooks are at-least-once with stable event IDs; email delivery remains auditable | + +## 6. System Architecture + +```text +Browser/CLI -> Web API -> SQLite metadata + durable execution queue + | ^ + v | + SSE event stream Scheduler (single leader) + | + v +Source <- adapter <- Worker -> Repository (staging, blobs, manifests, quarantine) + |-> verify / restore / retention / reconcile / GC +``` + +- **Web:** session/API-token auth, validation, OpenAPI, CRUD, dashboard queries, SSE, static UI. MUST NOT enumerate sources, run dumps, copy files, or mutate repository objects. +- **Scheduler:** computes due occurrences and transactionally enqueues them. Exactly one supported instance with SQLite. +- **Worker:** leases executions, performs bounded blocking/async I/O, reports heartbeats/progress, publishes backups, restores, verifies, and reconciles. +- **Migrate/admin:** Alembic upgrades, repository init/check, secret-key rotation, offline recovery-bundle export/validation, and metadata backup/restore. +- **Frontend:** React/TypeScript responsive SPA using only generated client types. Production is same-origin behind one reverse proxy; FastAPI static serving MAY be used only if packaging tests prove it. + +## 7. Domain Model and Invariants + +| Entity | Required data | Key invariants | +| --- | --- | --- | +| User | UUID, username, Argon2id hash, state, timestamps | First run creates one admin; hash never returned | +| API Token | owner, hash, scopes, expiry/revocation | Plain token shown once | +| Secret | encrypted payload, key ID, purpose, version | API returns reference/status only | +| Repository | name, root, format version, compression/encryption policy, state | Root under configured allowlist; policy immutable after init | +| Source | name, kind, typed public config, secret refs, state | Type changes require replacement; test result stored separately | +| Job | source, repository, requested mode, excludes, retention, enabled | At most one active execution; archived refs remain readable | +| Schedule | job, cron, timezone, misfire grace, overlap policy, enabled | At most one per job; `(schedule, nominal time)` unique | +| Execution | job, trigger, state, attempt, lease, progress, reason, timestamps | Monotonic transitions; every terminal state has reason code | +| Backup | execution, manifest ID/digest, logical/stored bytes, integrity, pin/tombstone | Immutable after commit except lifecycle/integrity annotations | +| Restore | backup, selection, destination, overwrite policy, state/result | Durable execution with containment and audit | +| Audit Event | actor, action, resource, outcome, request ID, time | Append-only; secrets excluded | +| Notification Subscription | channel, event filters, destination config/secret, state | Webhook URL or email recipients are validated; secret fields are write-only | +| Notification Delivery | event ID, subscription, attempt, state, response class, timestamps | Durable retry history; payload contains no secret material | +| Idempotency Record | actor, key, operation, request digest, response resource | Same key+payload returns same result; mismatch is `409` | + +All times MUST be timezone-aware UTC RFC 3339 externally. Foreign keys, uniqueness, delete behavior, and indexes MUST be explicit. Public list APIs MUST use stable cursor pagination. Raw ORM objects MUST NOT serve as API/domain models. + +## 8. Typed Configuration + +| Input | Default | Rule | +| --- | --- | --- | +| `DATABASE_URL` | SQLite under data dir | Absolute, startup-validated; SQLite foreign keys, WAL, busy timeout enabled | +| `DATA_DIR` | `/var/lib/backup-tool` | Metadata and runtime state only | +| `REPOSITORY_ROOTS` | none | Required allowlist of canonical destination roots | +| `LOCAL_SOURCE_ROOTS` | none | Required allowlist for local sources | +| `MASTER_KEY_FILE` | none | Required before storing secrets; mode/owner checked | +| `PUBLIC_BASE_URL` | loopback URL | Used for cookies, redirects, and origin validation | +| `CORS_ORIGINS` | empty | Same-origin default; explicit exact origins only | +| `WORKER_CONCURRENCY` | `1` | V2 SQLite limit remains one active job globally by default | +| `MIN_FREE_BYTES` / `MIN_FREE_PERCENT` | documented safe values | Repository preflight and alert thresholds | +| `LOG_LEVEL` | `INFO` | Structured logs; runtime override may be typed setting | + +Environment MUST control deployment invariants. Typed DB settings control operator preferences such as default timezone, audit retention, verification cadence, SMTP transport, and notification retry limits. Notification destinations and credentials use typed subscription/secret resources, not arbitrary settings. Unknown settings MUST be rejected. + +## 9. Source Adapter Contract + +Every adapter implements `validate_config`, `probe`, `enumerate_entries`, `open_content`, `capture_consistency_metadata`, and `close`. Engine code MUST obtain source bytes only through this contract. + +Common rules: stream with backpressure; bounded timeouts/concurrency; normalized relative paths; common exclusion matcher; preserve directories, files, symlinks, and selected mode/mtime metadata; do not follow external symlinks; report unsupported special files explicitly; classify errors as auth, trust, unavailable, permission, timeout, source-changed, unsupported-entry, transient-I/O, or internal. + +| Adapter | Typed fields and behavior | +| --- | --- | +| Local | canonical root, cross-filesystem flag, symlink policy; root must be allowlisted | +| SSH/SFTP | host, port 22, username, remote root, auth secret, pinned known-host key; unknown/changed keys fail closed | +| PostgreSQL | host/socket, port 5432, database, username, secret, TLS, dump format; stream `pg_dump`, record tool/server versions; full logical snapshots only | +| MySQL | host/socket, port 3306, database, username, secret, TLS, consistency/locking policy; stream `mysqldump`; full logical snapshots only | + +A source probe validates connectivity, trust, permissions, required native tooling, and capability metadata but MUST NOT persist or return credentials. + +## 10. Repository and Backup Protocol + +```text +/repository.json + blobs/sha256// + manifests/.json + staging// + quarantine/ +``` + +`repository.json` declares repository UUID, format version, digest algorithm, compression, encryption, and creation metadata. Blobs are immutable and addressed by SHA-256 of plaintext content; stored representation MAY be compressed/encrypted and MUST use authenticated encryption when enabled. + +Each manifest is canonical JSON and binds format version, repository/source/job/execution IDs, requested/effective mode, UTC times, source consistency metadata, exclusion policy/version, normalized path, entry type, logical size, blob digest, selected portable metadata, link target, metadata-support flags, aggregate logical/stored counts, encryption key ID, and manifest digest/signature. + +- **Full:** enumerate and read every included file; verify every new stored object. +- **Incremental:** compare against latest compatible committed manifest and reuse blobs. Metadata shortcuts MAY skip reads only when adapter evidence is declared reliable; a full run remains the correctness fallback. +- Missing incremental baseline becomes effective full with reason `baseline_missing`, preserving the useful current behavior explicitly. +- Every manifest is complete. `parent_backup_id` MAY record lineage but MUST NOT be needed to restore. +- Empty sources fail unless job explicitly sets `allow_empty`; an unexpectedly empty previously nonempty source always requires operator-visible confirmation. +- Publication uses unique staging, capacity preflight, temp objects, fsync where supported, atomic rename, verification, metadata commit, and reconciliation markers. Wall-clock names never identify new backups. +- A backup is visible as committed only after its manifest and all referenced blobs are durable and verified. + +## 11. Execution, Concurrency, and Recovery + +```text +queued -> preparing -> running -> verifying -> committed + | | | |-> failed + | | |-> cancelling -> cancelled + | |-> failed + |-> cancelled +``` + +Enqueue returns a durable execution immediately. Only one nonterminal execution per job is allowed; duplicates return `409` with the active resource. Scheduler occurrence keys and client `Idempotency-Key` prevent duplicate publication. Worker claims use lease expiry and heartbeat. + +Retries reuse the execution ID and increment attempt only for classified transient errors. Cancellation is cooperative and cannot turn a committed backup into cancelled. Process loss before publication leaves recoverable staging; startup reconciliation either resumes a safe step, requeues, or fails with `worker_lost`. Errors expose stable code plus redacted operator text; sensitive diagnostics remain in access-controlled structured logs. + +Progress includes phase, files/bytes scanned, read, stored, deduplicated, warnings, throughput, and heartbeat time. Blocking filesystem, Paramiko, and subprocess work MUST run outside the web event loop. + +## 12. Scheduling + +Schedules MUST persist five-field cron, IANA timezone, enabled state, next occurrence, last enqueue outcome, misfire grace, and overlap policy. API writes MUST use semantic cron validation and take effect without restart. + +Defaults: 15-minute misfire grace, coalesce missed occurrences into one, prohibit overlap, skip nonexistent DST wall times, and execute repeated DST wall time once at its earliest instant. Enqueue is transactionally unique by schedule and nominal UTC occurrence. Disabled schedule or job does not enqueue. + +## 13. Retention, Deletion, and Garbage Collection + +Policies MAY combine keep-last, keep-for-days, daily, weekly, monthly, and pinned backups. Keep rules form a union: a backup survives if any rule keeps it. The newest committed backup is retained unless the operator explicitly purges the job. + +Retention MUST provide preview and apply operations. Deletion MUST first create a tombstone/audit event; GC removes unreferenced blobs only after a grace period and a second reference scan. Failed physical deletion remains `deletion_failed` and retryable. Unknown objects are quarantined, never automatically destroyed. Source/job archive does not delete backups. Explicit purge requires confirmation and produces a durable report. + +## 14. Restore, Download, Verification, and Reconciliation + +Restore MUST be durable and support dry run, path selection, destination allowlist, and overwrite policy `fail|skip|replace`. It MUST verify every blob before staged/atomic placement and write a result manifest. Unsafe absolute/traversal paths, device entries, and escaping symlinks are rejected. + +Verification modes are metadata-only and full-content; scheduled scrubs MAY run periodically. Backup integrity is `unverified|verified|degraded|corrupt`. V2.0 exposes verified restore only. V2.1 download streams a generated TAR from a committed manifest and never exposes repository paths. Reconciliation checks DB-to-storage and storage-to-DB, repairs known interrupted transitions, quarantines unknown data, and never silently erases evidence. + +## 15. HTTP API Contract + +Conventions: `/api/v2`; UUID strings; RFC 3339 UTC; JSON; cursor pagination; stable sort; `201` create; `202` enqueue; `204` successful idempotent delete where applicable; RFC 9457 problem details with stable `code`; `Idempotency-Key` on enqueue/destructive mutations; optimistic version/ETag on mutable config. + +| Resource | Required operations | +| --- | --- | +| Auth | setup, login/logout, session, password change; API-token create/list/revoke | +| Sources | list/create/get/update/archive; probe/test; credential rotate | +| Repositories | list/create/get; probe/capacity; verify/reconcile/GC | +| Jobs | list/create/get/update/archive; enqueue execution; retention preview | +| Schedules | get/create-or-replace/update/disable/delete; next occurrences | +| Executions | list/get/cancel/retry; event/log stream via SSE with polling fallback | +| Backups | list/get/pin/unpin; verify; tombstone/delete; purge preview; v2.1 download | +| Restores | list/create/get/cancel; result report | +| Notifications | subscriptions CRUD/test; event catalog; delivery history/retry | +| Dashboard | health/capacity summaries, active/recent executions, next runs, failures | +| Settings/Audit | typed settings get/update; paginated audit events | +| Administration | export/validate offline recovery bundle; rotate keys; metadata backup | +| Operations | `/livez`, `/readyz`, version/capabilities; admin actions remain authenticated | + +OpenAPI is committed/generated in CI; generated frontend client code MUST have no handwritten duplicate resource interfaces. + +## 16. Frontend Information Architecture + +Routes: Setup/Login, Dashboard, Sources, Repositories, Jobs, Job Detail/Schedule, Executions, Execution Detail, Backups, Backup Detail/Restore, Settings/Security, and Audit. + +Dashboard shows repository health/capacity, active queue, enabled jobs, next runs, backup count/logical/stored/deduplicated bytes, recent failures, and verification warnings. Source/repository forms are type-driven and include test actions. Job creation is a guided flow. Execution detail uses SSE progress and redacted logs. Backup detail exposes integrity, manifest summary, pin, verify, restore, and deletion preview; download appears only when v2.1 capability metadata enables it. Settings includes webhook/email subscriptions, test delivery, retry history, and recovery-bundle export. + +Every view defines loading, empty, validation, partial failure, offline/reconnect, expired-session, unsupported-capability, and responsive states. Destructive actions require impact preview and explicit confirmation. Unsupported or unreleased controls are absent, not disabled mockups. Keyboard navigation, visible focus, semantic labels, contrast, reduced motion, and screen-reader announcements are acceptance requirements. + +## 17. Security and Privacy + +- First-run admin auth is mandatory before non-loopback exposure. Passwords use Argon2id; browser sessions use Secure/HttpOnly/SameSite cookies plus CSRF protection. +- API tokens are hashed, scoped, expiring, and revocable. Same-origin is default; CORS is off unless exact origins are configured. +- Secret values are write-only, envelope-encrypted with an external master key, versioned, redacted everywhere, and independently rotatable. Encrypted installs MUST support export and validation of a recovery bundle encrypted under a separate recovery passphrase; the bundle is never stored beside the live master key. +- SSH host keys are pinned. TLS verification is on for database connections. Native dump commands receive secrets without command-line/process-list exposure. +- Repository and data directories are owned by a non-root service user with restrictive modes. Canonical path containment and symlink policy are enforced before I/O. +- Audit events cover auth, config, execution, restore, verification, deletion, GC, secret rotation, and migration. Logs, errors, manifests, metrics, and audits MUST NOT contain credentials. +- Backup encryption uses authenticated encryption; threat model states whether manifests, filenames, sizes, and deduplication equality are confidential. +- Webhook targets MUST pass scheme, DNS/IP, redirect, and private-network policy checks to prevent SSRF. Webhook signatures use a rotatable secret and stable event ID. SMTP credentials are write-only secrets. + +## 18. Reliability, Performance, and Operations + +Certified v2.0 target: one node, one worker, 100 jobs, 1 million entries or 10 TiB logical data per backup, and 100,000 cataloged backups. Release tests MUST run on a documented reference host, publish throughput and resource use, and reject material regression from the accepted baseline; no hardware-independent minimum throughput is claimed. All list APIs paginate. Enumeration and transfer stream with bounded memory. Disk preflight, minimum free space, per-source timeouts, chunk sizing, graceful shutdown, and rate limits are configurable. + +`/livez` proves process liveness. `/readyz` verifies migrations, DB access, role health, and required repository access. Structured logs correlate request, job, execution, backup, and restore IDs. Metrics include queue age, schedule lag, duration, throughput, logical/stored/deduplicated bytes, failure class, retries, lease expiry, capacity, verification failures, and GC outcomes. Alerts cover repeated failure, stale lease, corrupt backup, low capacity, missed schedule, and repository unavailability. + +Metadata backup/restore, master-key handling, repository recovery, interrupted-upgrade rollback, and full disaster recovery require runbooks. Graceful shutdown stops new claims and leaves work at a restart-safe checkpoint. + +## 19. Deployment and Packaging + +One versioned OCI image exposes `web`, `scheduler`, `worker`, and `migrate` commands. Compose runs same-origin reverse proxy/UI, one web, one scheduler, one worker, persistent metadata, and one or more mounted repositories. Images run non-root, pin dependencies, include only required dump clients, publish SBOM/provenance, and pass persistence/permission health tests. + +Python 3.12 LTS and a current Node LTS SHOULD be the initial build baseline; exact versions are locked in implementation planning. Production MUST NOT use reload mode. Multiple web processes are allowed only when neither scheduler nor worker starts inside them. + +## 20. Compatibility and Migration + +V2 is a clean installation. It provides no `/api/v1`, compatibility shim, v1 database importer, legacy-directory reader, or payload converter. Existing v1 installations remain separate and MUST be decommissioned or archived by their operator; v2 never opens or mutates their database or backup directories. + +Migration within v2 uses forward Alembic revisions and repository-format migrations with preflight, metadata/repository backup, resumability, and explicit rollback instructions. Old integer IDs, timestamp directories, status codes, arbitrary settings, and aggregate checksums are not v2 contracts. + +## 21. Delivery Sequence + +1. **Protocol foundation:** repository/manifest spec, Alembic baseline, auth/secrets, local adapter, durable queue/leases, full backup, verification, restore, and fault injection. +2. **V2.0 complete product:** deduplicating incremental, exclusions, schedules, retention/GC, reconcile, strict SSH/SFTP, generated client/UI, optional repository encryption, offline recovery bundle, signed webhooks, email, delivery history, and certified scale tests. +3. **V2.1 sources and extraction:** PostgreSQL/MySQL dumps, native-tool/version checks, integration fixtures, resource limits, and generated TAR downloads. +4. **Operational hardening:** periodic scrubs/restore drills, performance tuning, richer notification templates, and optional PostgreSQL metadata design. + +Each phase is releasable only if its controls are truthful in API capability metadata, UI, and documentation. + +## 22. Acceptance Invariants + +1. Committed backup implies durable verified manifest and all referenced blobs. +2. Duplicate delivery of one schedule occurrence produces at most one committed backup. +3. Concurrent starts cannot create two active executions for one job. +4. Crash at every state transition leaves valid committed data or recoverable/quarantined staging. +5. SSH/database bytes flow only through their adapter; no local-path fallback exists. +6. Every committed backup restores without another backup manifest. +7. Retention cannot make any retained backup unrestorable. +8. Source/job deletion cannot silently delete backup payloads. +9. Failed physical deletion remains visible and retryable. +10. Credentials never appear in read APIs, OpenAPI examples, logs, errors, manifests, audits, or metrics. +11. Unknown/changed SSH host keys fail before data transfer. +12. Same normalized paths and rules produce identical exclusions across adapters. +13. Disabled jobs cannot be enqueued manually or by schedule. +14. Schedule create/update/delete takes effect without restart and has deterministic DST/misfire behavior. +15. V2 contains no code path that opens, imports, converts, or mutates v1 metadata or payload formats. +16. Frontend types/routes are generated from and match committed OpenAPI. +17. Restore tests cover empty files, Unicode, large files, symlinks, permissions, source deletions, and interrupted writes. +18. Blob, manifest, or authentication-tag corruption is detected before restore publication. +19. Empty or unexpectedly vanished sources cannot silently replace a good backup. +20. Backup/source paths cannot escape configured allowlists through traversal or symlinks. +21. Recovery-bundle validation proves required keys are present without exposing plaintext key material; restore works after simulated host loss. +22. Webhooks are signed and delivered at least once with stable event IDs; webhook and email transient failures retry and remain visible in delivery history. + +## 23. Required Test Matrix + +Unit: state transitions, exclusion semantics, manifest canonicalization, retention selection, cron/DST, error mapping, secret redaction, event filtering, and notification retries. Contract: OpenAPI golden, problem details, pagination, idempotency, ETag conflicts, webhook schema/signature, and generated client compile. Integration: each adapter, DB migrations, queue leases, crash recovery, repository publish, GC, restore, recovery bundle, SMTP, webhook delivery, and auth/CSRF. Fault injection: process kill, disk full, permission loss, network drop, source mutation, DB lock, checksum corruption, deletion failure, SMTP rejection, and webhook timeout. E2E: setup through restore, notification history, recovery export, and deletion preview in production-like Compose. Security: path traversal, symlink escape, host-key change, token scope/revocation, webhook SSRF/redirect, signature rotation, and secret leakage scan. Performance: certified scale targets with bounded memory and published reference-host throughput. + +## 24. Resolved Product Decisions + +| Area | Approved choice | +| --- | --- | +| Source release | V2.0 includes local and SSH/SFTP; PostgreSQL/MySQL ship in v2.1 | +| Encryption | Operator-selected at repository creation; policy remains immutable | +| Scale | Certify 100 jobs, 1M entries or 10 TiB per backup, and 100k backups on a published reference host | +| Key recovery | Passphrase-protected offline recovery bundle with export and validation workflows | +| Backward compatibility | None: no importer, legacy reader/converter, or v1 API shim | +| Download | Verified restore in v2.0; generated TAR download in v2.1 | +| Notifications | Signed webhooks and email both ship in v2.0 for all operational event families | +| Delivery | Retry transient failures, preserve delivery history, and use stable webhook event IDs for at-least-once delivery | + +## 25. Risks and Mitigations + +| Risk | Mitigation | +| --- | --- | +| Content-addressed storage adds complexity | Specify protocol first; golden repository fixtures; crash/corruption tests | +| SQLite limits concurrency | State single-node limits; one scheduler/worker; design claims behind repository interface | +| Metadata shortcuts miss changed content | Declare trust rules; periodic/full modes; verification and scrub | +| Encryption key loss destroys recoverability | External key backup runbook, startup checks, explicit threat model | +| Notification endpoints create SSRF, spam, and secret risks | Validate targets, sign webhooks, constrain retries/rates, protect SMTP credentials | +| DB dumps vary by server/client | Record versions/options; integration compatibility matrix; fail unsupported pairs | +| Dedup leaks equality/size information | Document threat; repository isolation; encryption policy fixed at creation | +| Scope expands before integrity works | Enforce delivery sequence and capability truthfulness | + +## 26. Verification + +The design artifact itself is valid when it contains normative decisions, current-state treatment, architecture, domain model, protocol, state machine, API/UI, security, operations, migration, tests, acceptance invariants, and open decisions: + +```bash +python - <<'PY' +from pathlib import Path +p = Path('docs/superpowers/specs/2026-07-27-backup-tool-reimplementation-design.md') +s = p.read_text() +required = ['Current-State Audit', 'Normative Decisions', 'System Architecture', + 'Repository and Backup Protocol', 'Acceptance Invariants', + 'Compatibility and Migration', 'Resolved Product Decisions'] +assert all(x in s for x in required) +assert s.count('MUST') >= 20 +print('design-spec: OK') +PY +``` + +**Next step:** create the implementation plan beginning with repository/manifest protocol fixtures and failure-oriented acceptance tests—not framework scaffolding.