docs(v2): define reimplementation design and plan
This commit is contained in:
@@ -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>/repository.json
|
||||
blobs/sha256/<first-two>/<digest>
|
||||
manifests/<backup-uuid>.json
|
||||
staging/<execution-uuid>/
|
||||
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.
|
||||
Reference in New Issue
Block a user