- backend/internal/web/router.go: staticDir string -> staticFS fs.FS; /healthz uses HealthzHandler(); /readyz registered with ReadyzHandler(pinger); embedded FS served via fs.Sub()
- backend/cmd/web/main.go: import assets "backend"; db.RunMigrations(ctx, pool, assets.Migrations) before router; web.NewRouter now receives assets.Static
- All *_test.go NewRouter call sites updated from "./static" to os.DirFS("./static"); "os" import added where missing
- TestHealthz_OK now calls HealthzHandler() with no args (liveness, no db field)
- TestHealthz_Down deleted (new HealthzHandler has no failure mode)
- TestReadyz_OK added: ReadyzHandler(stubPinger{err: nil}) -> 200 + db:ok
- TestReadyz_Down added: ReadyzHandler(stubPinger{err: ...}) -> 503 + degraded
ROADMAP SC-3 (list-failed-jobs CLI) and SC-4 (web binary enqueue) struck
through as deferred per CONTEXT.md decisions D-03 and D-06. VERIFICATION.md
status updated to complete (3/3 active success criteria).
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Closes SC-3 (WORK-04) with a list-failed-jobs subcommand in cmd/worker and
SC-4 (WORK-01) by wiring a river insert-only client into cmd/web with a
/debug/enqueue-test handler that proves the web→worker enqueue path.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
All WORK-01 through WORK-04 requirements verified live against Postgres + MinIO.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- rivermigrate at startup (idempotent, before client construction)
- S3 store init from env vars (S3_ENDPOINT/S3_BUCKET/S3_ACCESS_KEY/S3_SECRET_KEY/S3_REGION/S3_USE_PATH_STYLE)
- signal.NotifyContext created AFTER all startup I/O (PATTERNS.md critical ordering)
- HeartbeatWorker + OrphanCleanupWorker registered via river.AddWorker
- river.Client with slog.Default() Logger, SlogErrorHandler, MaxWorkers:10
- HeartbeatArgs periodic every 1 min (RunOnStart:true), OrphanCleanupArgs every 1 hr
- StopAndCancel(10s timeout) on shutdown; pool.Close after StopAndCancel
- HeartbeatArgs + HeartbeatWorker (logs slog.Info on each tick)
- OrphanCleanupArgs + OrphanCleanupWorker (S3 delete then DB delete loop)
- NewOrphanCleanupWorker constructor with pool + FileStorer injection
- SlogErrorHandler implementing river.ErrorHandler (HandleError + HandlePanic)
- fileQuerier interface for test injection without real DB
- Unit tests: 7 tests pass (pure mock-based, no DB required)
- go build ./... exits 0
Three plans: Wave 1 adds river dependency + internal/jobs package with unit
tests; Wave 2 wires cmd/worker/main.go with full river runtime + justfile
target + README; Wave 3 is human-verify checkpoint.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Set nyquist_compliant: true and wave_0_complete: true in frontmatter
- Update Per-Task Verification Map: FILE-01..06 integration tests marked ⚠️
(clean skip without TEST_DATABASE_URL); new unit tests marked ✅ green
- Check all Wave 0 requirements as complete
- Fill in Validation Sign-Off checklist
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Gap fill: three no-infrastructure unit tests that run without TEST_DATABASE_URL or S3_ENDPOINT:
- backend/templates/files_helpers_test.go — formatBytes boundary cases (B/KB/MB/GB)
- backend/internal/files/store_unit_test.go — byteCountReader accumulation, io.ErrUnexpectedEOF
guard for small files, and MultiReader body reconstruction after 512-byte sniff
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
15s was too short for 25MB uploads on slow connections (~256KB/s takes
~100s). Both timeouts are raised to 120s to accommodate MAX_UPLOAD_SIZE_MB
at worst-case bandwidth with headroom.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Expand stubbedFileStorer with deletedKey tracking and deleteErr injection field
- Implement TestFileDownload (FILE-04): 302 redirect to presigned URL
- Implement TestFileDownload_NonOwner: non-owner gets 404
- Implement TestFileDelete (FILE-05): HTMX delete, S3+DB both deleted
- Implement TestFileDelete_S3Failure: S3 error does not abort DB delete, 200 returned
- Implement TestFileOwnership (FILE-06): non-owner gets 404 on all three routes