Initial commit with CI/CD blueprint
Some checks failed
Automated Container Build / build-and-push (push) Failing after 5s
CI / Backend (Python) (push) Failing after 9s
CI / Frontend (TypeScript) (push) Failing after 7s

This commit is contained in:
Elijah 2026-07-15 16:12:53 -07:00
commit bed2e6cfb6
3 changed files with 539 additions and 0 deletions

View file

@ -0,0 +1,25 @@
name: Automated Container Build
on:
push:
branches:
- main
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Log into Local Registry
run: |
echo "${{ secrets.FORGEJO_PAT }}" | docker login git.elijahkuntz.com -u "${{ gitea.actor }}" --password-stdin
- name: Build and Push Image
run: |
# Force the entire image path string to lowercase dynamically
IMAGE_PATH=$(echo "git.elijahkuntz.com/${{ gitea.actor }}/${{ github.event.repository.name }}:latest" | tr '[:upper:]' '[:lower:]')
docker build -t "$IMAGE_PATH" .
docker push "$IMAGE_PATH"

71
.forgejo/workflows/ci.yml Normal file
View file

@ -0,0 +1,71 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
backend:
name: Backend (Python)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
working-directory: backend
run: |
pip install -e ".[dev]"
- name: Lint (ruff)
working-directory: backend
run: ruff check .
- name: Type check (mypy)
working-directory: backend
run: mypy app/
- name: Test (pytest)
working-directory: backend
run: pytest --tb=short -q
env:
PAPERJET_DATABASE_PATH: ./test.sqlite
PAPERJET_PDF_STORAGE_PATH: ./test_pdfs
PAPERJET_THUMBNAILS_PATH: ./test_thumbs
frontend:
name: Frontend (TypeScript)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js 22
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
working-directory: frontend
run: npm ci
- name: Lint (eslint)
working-directory: frontend
run: npm run lint
- name: Type check (tsc)
working-directory: frontend
run: npx tsc -b
- name: Test (vitest)
working-directory: frontend
run: npx vitest run

443
Drive_v2_final_plan.md Normal file
View file

@ -0,0 +1,443 @@
# Drive v2 — Final Architecture and Implementation Plan
Status: approved baseline. This document is the source of truth until superseded by ADRs in the repository. It is written for both human and AI implementers. Normative keywords MUST, MUST NOT, SHOULD, and MAY follow RFC 2119 meaning. Section numbers are stable and may be referenced from agents.md, ADRs, and commit messages.
---
## 1. Product Definition and Deployment Context
Drive v2 is a clean-replacement, personal, single-owner, self-hosted file platform. It is built in a new repository. The v1 application remains read-only as a behavioral reference; no schema, auth code, filesystem handlers, or UI components are copied from it.
Deployment target (fixed, not generic):
- Unraid 7.3.1, Docker.
- Cache: two-drive btrfs mirror, exclusive share (FUSE bypassed), no mover.
- Array: 6 data disks + 1 parity, 54 TB, accessed through `/mnt/user` via shfs/FUSE. Array shares CANNOT be exclusive; the implementation MUST NOT assume FUSE bypass for array paths.
- Reverse proxy: Nginx Proxy Manager on the same host.
- OnlyOffice Document Server: already running in a separate container on the same host.
- Single owner. Multiple concurrent sessions, devices, and API tokens are supported. There are no other users.
- Clients: desktop Chromium-based browsers (Brave), Android Chromium browsers, future desktop sync client.
- Initial import corpus: a few hundred GB. Largest working files: 200-300 GB ZIP archives.
## 2. Non-Negotiable Invariants
These are the promises the system makes. The automated invariant test suite (Section 14) encodes them. AI implementers MUST NOT weaken, bypass, or modify these without a human-approved ADR.
- I1. Committed metadata MUST never reference unavailable, incomplete, or unverified content.
- I2. Managed content objects are immutable once committed. Nothing modifies a committed object in place. New content means a new object and a new revision.
- I3. Every metadata mutation and its change-feed event commit in one PostgreSQL transaction.
- I4. All sizes, offsets, ranges, and counters are 64-bit. Every transfer path streams; no code path may buffer an entire file in memory.
- I5. Resources are identified by ID, never by path. Paths are derived presentation data.
- I6. No authoritative state lives only in process memory: no in-memory-only uploads, jobs, locks, or queues.
- I7. Only the storage adapter touches managed array paths. Only application services coordinate database plus storage mutations. Enforced by CI (Section 5).
- I8. Errors are never silently discarded.
- I9. Directory listing and search MUST never scan the array at request time.
- I10. The server never applies silent last-write-wins. Stale mutations fail with 409 and current server state.
- I11. In-flight (uncommitted) uploads MAY be lost on unclean shutdown. Committed data MUST NOT be. Crash recovery protects commits, not transfers.
- I12. GC MUST NOT remove any object referenced by a live node, an active replacement-recovery record, an active editor session, or an incomplete commit intent.
## 3. Capacity Contract
- No intentionally low application-level file-size limit. Default administrative upload ceiling: 1 TiB, raisable.
- Stable releases are qualified with real 300 GB-class upload, download, checksum, restart, and ZIP64 tests. Larger files are supported up to the configured ceiling but not separately certified.
- One-time Phase 0 validation at 10 million synthetic nodes and 500,000-descendant subtrees proves the schema and query plans. These are NOT recurring release gates (Section 14).
- Unraid share minimum free space MUST be set to at least 400 GB (above the largest expected file class).
- Free-space enforcement happens at upload-session creation (declared size vs. eligible-disk free space plus safety margin) and per accepted chunk. Finalization adds no bytes; a finalization-time space check is redundant and MUST NOT be the primary control.
## 4. Feature Scope
### 4.1 Stable v1
- First-run admin setup; password auth; session management; password recovery; TOTP 2FA; scoped API tokens.
- Responsive file browser: breadcrumbs, grid/list, sorting, keyset pagination, virtualized rendering, keyboard navigation.
- Folder create; file upload/download/rename/move/copy/trash/restore/permanent delete; multi-select; drag-and-drop and folder upload; resumable upload; cancellation; conflict handling; progress and speed.
- Streamed range downloads, bulk downloads, streamed folder-archive downloads.
- Filename and metadata search.
- Pinned/favorite files.
- Trash: 30-day default retention, configurable.
- Replacement recovery: 7-day default window, configurable or disableable, with API and UI (Section 7.4).
- Dashboards: storage, transfers, jobs, integrity findings, sessions/tokens, activity. (UI polish for these may be minimal at v1; backend data model is required.)
- Scheduled integrity scrubbing and mark-and-sweep GC (Section 12.3).
- pgBackRest backup with verified restore; blob manifest export (Section 12.4).
- Required server-side bulk importer (Section 8.3).
- OpenAPI-documented `/api/v1`; installable PWA (Section 10.4).
- Previews: images (JPEG, PNG, GIF, WebP, SVG, BMP), ranged audio/video, PDF (PDF.js), bounded text/Markdown/CSV/JSON/logs, source code with highlighting, lazy viewport thumbnails.
- Light/dark themes.
### 4.2 Post-v1 native modules (order-flexible; see Section 15)
- ZIP/ZIP64 and 7z archive viewing; durable zip/unzip jobs.
- STL and 3MF 3D preview.
- Public link sharing (expiry, optional password, download limits, revocation).
- OnlyOffice viewing/editing/creation/templates (Section 11).
- Calendar with ICS subscription feeds (Section 11.2).
- Mirrored-folder desktop sync client.
- WebDAV: not planned; a read-only facade over application services MAY be added later (ADR recorded). No third-party plugin system, ever; extensions are native compiled modules.
## 5. Architecture
### 5.1 Backend
Go, `net/http`, Chi, `pgx`, `sqlc`, versioned forward-only migrations.
Layers:
- Domain: identity, hierarchy, naming, revisions, conflicts, trash, replacement recovery, sessions, changes, job states. No HTTP, SQL, or filesystem imports.
- Application: transactional use cases (create, upload finalize, rename, move, copy, trash, restore, import, preview preparation, editor-session commit).
- Adapters: PostgreSQL repositories, array object storage, preview tools, OnlyOffice, calendar serialization, backup tooling, clock/ID providers.
- Transport: thin handlers conforming to OpenAPI. Parse input, call one application service, serialize output.
- Workers: PostgreSQL-backed jobs with leases, heartbeats, retries, cancellation, progress, `FOR UPDATE SKIP LOCKED`.
### 5.2 Boundary enforcement (CI, not prose)
The layer rules above MUST be enforced mechanically via import-restriction linting (depguard or go-arch-lint in golangci-lint). A forbidden import is a build failure. Minimum rule set:
- `transport` MUST NOT import `database/sql`, `pgx`, or `os` file APIs.
- `domain` MUST NOT import transport, adapters, `net/http`, `pgx`, or filesystem packages.
- Only the storage adapter package may reference managed storage path roots.
- Repositories MUST NOT import transport.
Rationale: AI implementers respect failing checks with near-perfect reliability and prose rules intermittently. Every architectural rule that can be a lint rule MUST be a lint rule.
### 5.3 Frontend
React, TypeScript (strict), Vite, TanStack Router, TanStack Query.
- TypeScript API client generated from OpenAPI; drift check in CI.
- Feature modules: auth, files, uploads, search, trash, replacements, previews, jobs, settings; later sharing, office, calendar.
- Server state in TanStack Query only; a small UI store for selection/view state; upload-session references persisted in IndexedDB.
- Preview renderers lazy-loaded, selected by server-declared capability, never by a central extension switch.
- Virtualized large lists. Accessible primitives, keyboard interaction, shared design tokens.
- MUST NOT: global window event buses, multi-thousand-line components, localStorage for auth material.
- NOT required: Storybook, full cross-browser matrix.
## 6. Storage Design
### 6.1 Unraid topology
Cache (exclusive btrfs share, real filesystem semantics):
- PostgreSQL data directory.
- Application configuration and secrets.
- Bounded preview cache (50 GiB default, LRU, configurable, evicted under pressure).
Array (shfs user share, FUSE semantics assumed):
- Managed objects and in-progress partials.
- pgBackRest repository and blob manifests.
The implementation MUST NOT claim or rely on cross-directory shfs rename atomicity.
### 6.2 Physical object layout (opaque IDs, same-directory promotion)
Content is logically addressed by SHA-256 in PostgreSQL. Physical objects use opaque IDs:
```text
/storage/.drive/
objects/<shard>/<object-id>.partial
objects/<shard>/<object-id>.blob
recovery/postgres/
```
- Shard is derived from the object ID at creation time.
- A partial and its completed object live in the same shard directory; promotion is a same-directory `.partial``.blob` rename. No cross-directory rename ever occurs after the final hash is known.
- PostgreSQL maps SHA-256 → object ID. Deduplication is the database's unique SHA-256 constraint; if concurrent uploads race, one blob row wins and the redundant physical object is deleted only after the winning object is confirmed available.
- Consequence and mitigation: the filesystem is no longer self-describing. The database is the only hash→object map. Therefore blob manifest export (Section 12.4) is a v1 REQUIREMENT with freshness monitoring, not a periodic nicety.
- No `quarantine/` directory. Suspect content is marked unavailable in metadata and reported.
### 6.3 PostgreSQL on btrfs (ADR-001, decided)
- PostgreSQL stays on the mirrored btrfs cache with copy-on-write ENABLED. `chattr +C` is rejected: it disables btrfs data checksums and defeats mirror self-heal.
- PostgreSQL page checksums enabled at initdb.
- Monitor DB size, WAL growth, fragmentation, cache-pool capacity.
- Accepted cost: CoW fragmentation at single-user scale, covered by btrfs scrubs and pgBackRest.
### 6.4 Core tables
`users`, `sessions`, `api_tokens`, `nodes`, `blobs`, `upload_sessions`, `commit_intents`, `replacement_recovery`, `changes`, `jobs`, `preview_assets`, `audit_events`, `settings`, `import_runs`, `import_items`. Later: shares, archive_indexes, calendar tables, editor_sessions. Module tables MUST NOT add columns to core file tables.
`nodes`: UUIDv7 id, owner, parent, name, case-folded name key, type, current blob id, size, MIME, monotonic revision, created/modified/trashed timestamps.
## 7. File and Hierarchy Semantics
### 7.1 Portable naming
- Unicode NFC normalization; case-folded uniqueness within a folder.
- Reject: control chars, `/`, `\`, `.`, `..`, Windows reserved device names, trailing spaces/dots.
- Deterministic conflict names: `name (conflict N).ext`, extension preserved.
- Identical validation rules published in OpenAPI and enforced server-side.
### 7.2 Conflict policies
- Default API behavior on name collision: `409 Conflict`. Never silently rename an API caller's request.
- Explicit modes: `keep_both` (deterministic suffix), `overwrite` (requires passing revision precondition), `skip`.
- Importer default: `keep_both`, with every rename recorded in the import report.
### 7.3 Large subtree operations
- Move: mutates only the root node's parent and revision; emits ONE change event; clients derive descendant effects from hierarchy.
- Trash: marks only the root as a trash root. Descendants become invisible via ancestor visibility, not row updates. O(1).
- Restore: clears the root trash state, resolves root-name conflicts. O(1).
- Permanent subtree delete: durable background job. Blob reclamation is separate scheduled GC.
- Search returns indexed candidates then applies batched ancestor-visibility filtering. Direct node access checks the ancestor chain so stale IDs cannot reach descendants of trashed roots. This filtered search is the performance-critical query in the system; Phase 0 validates it explicitly.
### 7.4 Replacement recovery
- On overwrite, the prior blob reference enters `replacement_recovery` with a 7-day default expiry (configurable, disableable).
- GC cannot collect actively referenced recovery blobs (I12).
- `/api/v1/replacements` and a "Recently replaced" UI: list timestamp/size/checksum, restore a prior blob as a NEW current revision, show storage consumed.
- This is a short safety window, not version history. OnlyOffice saves flow through it too; document sizes make the cost negligible.
## 8. Transfers
### 8.1 Resumable uploads (TUS-compatible)
- 64 MiB default chunks, sequential, exact offset/length enforcement.
- Session record persists destination, declared size, offset, expiry.
- Space check at session creation and per chunk (Section 3).
- Survives graceful restarts and connection interruptions (the common case: closed tab, WiFi drop, container update).
- Crash posture (I11): the incremental hash checkpoint is NOT required to survive unclean shutdown. After a crash, recovery MAY discard incomplete partials or re-hash them from disk; failing the upload is acceptable. Do not engineer durability for uncommitted transfers.
### 8.2 Commit protocol
1. Create persistent upload session; reserve target name.
2. Stream chunks to the `.partial` object.
3. Persist offset (and hash state, best-effort) per accepted chunk.
4. On completion: verify length, finalize SHA-256, fsync object and parent directory as supported.
5. Create commit intent.
6. Promote `.partial``.blob` (same directory).
7. One PostgreSQL transaction: insert-or-reuse blob, create/update node, increment revision, add replacement-recovery record if overwrite, emit change event, complete intent.
8. Startup/periodic recovery workers finish or roll back incomplete intents (this protects I1/I11 and MUST run on every start).
9. Redundant deduplicated objects deleted only after the committed blob is confirmed available.
### 8.3 Required importer
CLI inside the application image:
```text
drive import --source /imports/data --destination / --conflict keep_both [--dry-run]
```
- Sources mounted read-only; sources inside managed storage are rejected; symlinks are not followed (reported); hardlinking into managed storage is PROHIBITED (a later source edit would mutate an "immutable" blob).
- Copies through the same storage and commit services as uploads; streams SHA-256 during copy; deduplicates against existing blobs; preserves mtimes and empty folders where available.
- Commits per file or bounded batch; persists run/item state; resumable and idempotent; pause/cancel supported.
- Reports: copied, deduplicated, renamed, skipped, invalid, failed, changed-during-import.
- Dry-run: fast scan (counts, bytes, invalid names, symlinks, case collisions); full analysis optionally hashes sources to estimate dedup.
- Sized for the real corpus: a few hundred GB initial ingest. No multi-batch migration ceremony is required; run it, read the report. The importer is an ingestion tool, not a database backup substitute.
### 8.4 Small files
TUS-per-file overhead is accepted for v1 and measured, not hidden. Post-v1 candidates: batch small-file endpoint, upload-archive-and-unpack with strict limits, desktop-client batching.
## 9. API and Sync Contract
OpenAPI 3.1 is the public source of truth and lives in the repository.
Endpoints: `/api/v1/setup`, `/sessions`, `/api-tokens`, `/nodes`, `/nodes/{id}`, `/nodes/{id}/children`, `/nodes/{id}/content`, `/uploads`, `/search`, `/trash`, `/replacements`, `/changes`, `/jobs`, `/previews`; `/health/live`, `/health/ready`, `/metrics`. Later: `/shares`, `/office`, `/calendar`.
Rules:
- Opaque keyset cursors; no offset pagination on large collections.
- RFC 9457 problem responses with stable machine-readable error codes.
- `ETag` + `If-Match` or `expected_revision` on mutations; stale → `409` with current metadata (I10).
- `Idempotency-Key` on create, finalize, copy, move, import, and destructive requests.
- Standard `Range`/`Content-Range`/`Content-Disposition` and cache validators.
- Request IDs propagated through logs and job records.
- `/changes?cursor=` is the durable authority; SSE provides hints only. Change/tombstone retention: 365 days; expired cursor → `410 Gone` → full rebaseline.
- Folder move/trash/delete events apply to the subtree rooted at the referenced ID (one event, Section 7.3).
### 9.1 Sync conformance mirror (permanent tool)
Phase 2 builds a one-way download mirror: baseline the tree, poll `/changes`, download content, apply rename/move/trash/delete, persist cursor, survive restart, handle tombstones, folder moves, forced rebaseline, verify checksums. It stays in the repository forever as the sync-contract regression harness. Full bidirectional sync remains Phase 8 and consumes the contract this tool proves.
## 10. Capability Providers
The server returns per-node capabilities; the frontend maps manifest types to lazy renderers. Each provider defines recognition, resource limits, preparation mode, manifest schema, caching, cleanup, and security validation. Adding a preview means adding a provider + manifest + renderer + fixtures, never editing the file manager.
### 10.1 Core v1 providers
direct-image, ranged-media, pdf, bounded-text, source-code.
### 10.2 Thumbnails
- Lazy generation on first need; batched prefetch limited to the visible/near-visible viewport.
- Deduplicated by source blob hash + variant; served from the bounded LRU cache.
- Eager whole-library generation at ingest is REJECTED. Never enqueue thumbnails for the entire imported corpus.
### 10.3 Archives (post-v1)
- ZIP/ZIP64: ranged central-directory reads without extraction; async indexing; paginated entry browsing; individual-entry download. Tested against real 300 GB ZIP64 archives and high entry counts.
- 7z: listing and whole-archive operations. Individual-entry extraction only when bounded; solid archives may require decompressing preceding block data, so the provider MUST estimate cost, warn, and reject beyond configured CPU/temp-space/ratio limits.
- Protections: traversal, entry-count, total-output, expansion-ratio, nesting limits; cancellation and cleanup; no synchronous extraction in an HTTP handler.
### 10.4 PWA
- Installable manifest; service worker caches ONLY the app shell and safe static assets; MUST NOT cache authenticated file bytes, downloads, or previews.
- Web Share Target: feature-detected Android/Chromium enhancement. Shared files enter a recoverable pending upload feeding the normal streaming pipeline (never buffered in service-worker memory); ordinary upload is the fallback.
- Offline browsing/content is out of scope for v1.
## 11. OnlyOffice and Calendar
### 11.1 OnlyOffice integration model (with existing Document Server container)
Operating model, stated explicitly because it is commonly misunderstood: OnlyOffice Document Server holds the working copy internally during an editing session. Keystroke autosave happens INSIDE Document Server, not against Drive storage. Drive receives content only at discrete callback events (periodic forcesave if enabled; final save on session close). Therefore continuous editing is fully compatible with immutable blobs (I2): every callback save is a normal staged commit producing a new blob and revision.
Requirements:
- Editor config document key MUST incorporate node ID + revision, so a committed revision invalidates Document Server's cached session key and stale content is never served.
- Callbacks MUST be JWT-authenticated with a shared secret; Document Server host allowlisted; SSRF protections on all outbound fetches.
- Document Server fetches content through short-lived scoped content URLs tied to the editor session, not user session cookies.
- Callback commits carry `expected_revision`; on mismatch (file replaced mid-session) the save lands as a conflict copy, never a clobber (I10).
- Active editor sessions pin their source blobs against GC (I12).
- Saves flow through staging + commit protocol (Section 8.2). Forcesave-generated revisions pass through replacement recovery normally; office-document sizes make this cheap. No direct filesystem access by Document Server, ever.
- Blank documents and templates create nodes through the normal node service.
### 11.2 Calendar (isolated module, own tables, `/api/v1/calendar`)
- Calendars, events, all-day and timed events, IANA time zones, recurrence with exceptions and moved instances, reminders, attachments, ICS import/export.
- Recurrence MUST use a maintained RFC 5545 library (selected by ADR with conformance fixtures covering DST boundaries, EXDATE/RDATE, moved instances). Hand-rolled RRULE evaluation is PROHIBITED.
- Read-only ICS subscription feeds: `/api/v1/calendar/feeds/<opaque-secret>.ics`. Tokens are high-entropy, stored hashed, scoped to one calendar, revocable, rotatable; UI warns that URL possession grants read access. Writes remain via UI/API. No CalDAV.
- PWA calendar views and notifications where browser support permits.
## 12. Security, Reliability, Operations
### 12.1 Security
- Argon2id with versioned parameters; server-side revocable sessions in `HttpOnly`/`Secure`/`SameSite` cookies; CSRF protection on cookie-authenticated mutations; hashed scoped API tokens; no long-lived browser JWTs.
- Trusted-proxy configuration; rate limiting on validated client IP; strict CSP and security headers; MIME-sniff protection; symlink rejection; archive protections (10.3); OnlyOffice callback auth and SSRF protection (11.1).
- Application-level encryption at rest: not included (use Unraid/disk encryption if desired), preserving dedup and recovery practicality.
### 12.2 Shutdown and power
- UPS with Unraid-managed safe shutdown strongly recommended and documented.
- On SIGTERM: stop new sessions and finalizations, let in-flight chunk writes reach a recoverable boundary, release job leases, flush logs, exit within the container stop timeout.
- Deployment docs MUST specify raising the Docker container stop timeout above Unraid's 10-second default and aligning Unraid shutdown timers, otherwise the SIGTERM grace period exists only on paper.
- Unclean restarts ALWAYS run commit-intent and upload-session recovery. Losing in-flight uploads on unclean shutdown is acceptable by design (I11).
### 12.3 Scheduled array work
Scrub and GC MUST support: configurable time windows, bandwidth/IOPS limits, pause/resume, checkpoints at shard boundaries (no full restarts), automatic throttle/pause during interactive transfers, and (where feasible) bounded concurrently-active disks. Integrity failures mark blobs unavailable and create actionable findings.
### 12.4 Backup and recovery
- pgBackRest with WAL archiving to the array; documented retention; backup failures surfaced in health and admin UI; periodic restore test into an isolated PostgreSQL instance.
- Blob manifest export (object ID ↔ SHA-256 ↔ size ↔ node path snapshot) written to the array on a schedule. Manifest freshness is monitored and ALERTED like backup freshness, because with opaque object IDs the database is the only map (Section 6.2).
- Documentation states plainly: parity, the cache mirror, and on-server pgBackRest are availability layers on one physical machine, not backups. Independent/off-server backup is recommended for metadata and irreplaceable content.
### 12.5 Observability
Structured JSON logs (request/user/node/upload/job IDs). Prometheus metrics: request latency/errors, transfer throughput, active/interrupted sessions, job depth/retries/failures, import progress, DB/WAL/preview-cache growth, array and cache capacity, integrity findings, backup and manifest freshness, disk-job activity, replacement-recovery storage.
## 13. Testing Policy (Recalibrated)
Principle: a test earns permanence if it protects against silent data corruption, or if it is automated and free to run forever. Manual drills against loud, recoverable failures are cut.
Permanent, automated (CI and release gates):
- Unit/property tests: naming, hierarchy, revisions, dedup, conflicts, replacement recovery, cursor ordering, GC eligibility.
- Integration tests for every mutation (PostgreSQL + storage).
- Failure injection around partial write, fsync, promotion, transaction commit, event emission, cleanup.
- Process/container kill tests at every commit boundary, including on the real shfs path during development. These are the crash tests that matter.
- Restart tests during upload, import, finalization, preview, archive jobs, scrub, GC, backup.
- Sync-conformance harness runs (Section 9.1).
- Playwright critical flows: setup, login/2FA, upload, pause/resume, CRUD, conflicts, previews, search, trash, replacement restore, jobs, session expiry. One inexpensive accessibility smoke test on login + primary file workflow. Automated Chromium only; manual release check on Brave desktop and Android Chromium.
- Security tests: traversal, symlinks, malformed ranges, CSRF, token revocation, proxy IPs, archive bombs, SSRF, unauthorized access.
- Import tests: restart, dedup, collisions, invalid names, changed sources, read-only sources, empty folders.
Release gates using real data:
- Real 300 GB upload/download with checksum verification; real 300 GB ZIP64 browse without full extraction or unbounded memory (once the archive module ships).
- pgBackRest restore drill per stable release.
One-time Phase 0 validation (NOT recurring gates):
- 10-million-node directory/search benchmarks (pagination < 250 ms p95, indexed search < 1 s p95 on the reference host).
- 500,000-descendant move/trash/restore/search/delete semantics, including ancestor-visibility-filtered search performance.
- Same-directory promotion protocol exercised on the real `/mnt/user` array path under process/container termination.
- PostgreSQL-on-btrfs (CoW) benchmark on the real cache.
Explicitly NOT required:
- Recurring power-loss testing. OPTIONAL curiosity in a disposable Unraid VM (trial key, small virtual parity array) if ever desired; NEVER on the production server.
- Hash-checkpoint durability across unclean shutdown (I11).
- 1 TiB or 16 TiB certification tiers; 10M-node recurring benchmarks; full browser matrix; Storybook.
## 14. AI Implementation Policy
Most code will be written by AI agents (multiple models over time). The repository, not chat history, is the agents' world.
- This plan's contracts live IN the repository: OpenAPI spec, SQL schema + sqlc queries, ADRs, per-module docs. agents.md references them by path and section.
- Contract-first is anti-hallucination infrastructure: agents cannot invent endpoints (generated client + drift check) or columns (sqlc compile failure). The human owns the OpenAPI spec, schema, and sqlc queries as reviewed artifacts; agents implement between them. Own the interfaces, delegate the implementations.
- Architecture boundaries are lint failures, not guidelines (Section 5.2).
- The invariant test suite (encoding Section 2) is AGENT-IMMUTABLE: it lives in a dedicated directory that agents may not modify without explicit human approval; any diff touching it requires human review. Making a red test green by editing the test is the canonical failure mode this prevents.
- Every AI change: small, independently testable, reviewed against the invariant it implements. Dependency APIs verified against primary documentation; no invented methods. No new dependencies without approval. No unrelated "misc fixes" in a change.
- Every data mutation ships with success, failure, restart, cleanup, and idempotency tests.
- Architectural changes require an ADR before code.
- A feature is complete only when errors, cancellation, restart, observability, and recovery behavior are covered.
- agents.md (TBD) stays short and operational: build/test/lint commands, boundary rules, the Section 2 invariants restated as MUST, forbidden patterns, pointer to ADRs. Long instruction files degrade agent compliance.
## 15. Implementation Phases
Phases 5-7 are order-flexible after Phase 4; the calendar module has no dependency on archives or sharing and MAY be pulled earlier given heavy real-world use.
### Phase 0 — Validate foundations
- Freeze and inventory the old repository. Create new repo, CI (lint boundaries from day one), ADR process, threat model, UI direction, OpenAPI skeleton, Compose environment.
- Run the one-time validations from Section 13: 10M-node benchmark, 500k-descendant semantics, promotion protocol on the real array path under kill testing, Postgres-on-btrfs benchmark, real 300 GB streaming + hashing prototype.
- Exit when storage recovery and metadata performance meet documented targets.
### Phase 1 — Domain, database, authentication
- Migrations, repositories, node hierarchy, naming, revisions, replacement recovery, change feed, jobs, sessions, 2FA, API tokens, health, metrics, audit events.
- Storage objects, commit intents, recovery sweep, reference tracking, GC eligibility.
- Exit when failure injection proves I1: metadata can never reference unavailable objects.
### Phase 2 — Transfers, importer, sync contract
- TUS uploads with cancellation/resumption; range/bulk/folder downloads; search; favorites; trash; jobs; integrity checks; backup tooling; manifest export.
- Importer with dry-run modes; ingest the real few-hundred-GB corpus as the acceptance test.
- Permanent one-way mirror conformance tool.
- Real 300 GB upload/download/restart/checksum validation.
- Exit when upload, import, and change-feed recovery survive repeated interruption.
### Phase 3 — Web application and PWA
- File manager, uploads, navigation, search, trash, replacement recovery UI, settings, jobs, activity, storage dashboards.
- Core preview providers and lazy viewport thumbnails.
- Installable PWA with feature-detected Share Target.
- Responsive, keyboard, and end-to-end tests. First release candidate.
### Phase 4 — Stable v1 hardening
- Full automated suite green; real 300 GB gates; scheduled scrub/GC behavior on spinning disks; security review; container scanning; upgrade test; isolated restore drill.
- Publish Unraid, proxy, container stop-timeout, UPS, backup, restore, importer, capacity, and troubleshooting docs.
- Release after a clean Unraid install and restore succeed.
### Phase 5 — Archives and 3D
- ZIP/ZIP64/7z providers per Section 10.3; durable zip/unzip jobs; STL/3MF providers with Web Worker parsing and geometry limits.
- Test malformed archives, solid-7z cost handling, bombs, huge entry counts, oversized models, real 300 GB ZIP64.
### Phase 6 — Sharing and OnlyOffice
- Public links (expiry, passwords, limits, revocation, abuse protection).
- OnlyOffice per Section 11.1: scoped sessions, revision-bearing document keys, JWT callbacks, conflict-copy saves, GC pinning, templates.
### Phase 7 — Calendar
- Schema, recurrence via maintained library, ICS import/export, hashed-token subscription feeds, PWA calendar views, reminders/notifications where supported.
### Phase 8 — Desktop sync
- Shared Go sync engine on the proven change-feed contract; Wails desktop packaging; local SQLite state; filesystem watching; selective sync; resumable transfers; bandwidth limits; autostart; conflict copies.
- Windows/macOS/Linux naming and multi-device conflict validation.
- Wails is desktop-only. Native Android remains indefinitely deferred; the PWA covers mobile use.
## 16. Fixed Assumptions and Decision Log Seeds
- Single owner; multiple sessions/devices/tokens. No migration of v1 metadata or URLs; content ingested via the verified-copy importer.
- Drive exclusively owns managed objects after import; external writes into managed storage unsupported; hardlinks into managed storage prohibited.
- Latest content is canonical; 7-day replacement window; 30-day trash.
- PostgreSQL, config, previews on the exclusive btrfs cache; objects and recovery data on the array share via shfs.
- WebDAV not planned but not forbidden (ADR). No plugin system. No CalDAV.
- Initial ADRs to record: ADR-001 Postgres-on-btrfs CoW (Section 6.3), ADR-002 opaque object IDs + same-directory promotion (6.2), ADR-003 crash posture: commits protected, transfers expendable (I11), ADR-004 recurrence library selection, ADR-005 WebDAV stance, ADR-006 agent-immutable invariant tests.