---
phase: 06-background-worker
plan: 03
type: execute
wave: 3
depends_on:
  - 06-02
files_modified: []
autonomous: false
requirements:
  - WORK-01
  - WORK-02
  - WORK-03
  - WORK-04
---

<objective>
Human-verify checkpoint: run `just worker` against a live Postgres + MinIO, observe that the worker starts, runs the heartbeat job within seconds (RunOnStart: true), shuts down cleanly on Ctrl+C, and confirm logs are structured and readable.

Purpose: Closes all four WORK requirements with observable end-to-end proof. Verifies the binary actually works — not just compiles.
Output: Developer confirmation that WORK-01 through WORK-04 are satisfied in a live dev environment.
</objective>

<execution_context>
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/workflows/execute-plan.md
@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/ROADMAP.md
@.planning/phases/06-background-worker/06-CONTEXT.md
@.planning/phases/06-background-worker/06-02-SUMMARY.md
</context>

<tasks>

<task type="checkpoint:human-verify" gate="blocking">
  <what-built>
    cmd/worker binary with:
    - rivermigrate running river schema migrations at startup
    - HeartbeatWorker (every 1 minute, RunOnStart: true — fires immediately)
    - OrphanCleanupWorker (every 1 hour, RunOnStart: false)
    - SlogErrorHandler logging job failures to structured slog
    - Graceful shutdown on SIGINT/SIGTERM (StopAndCancel with 10-second timeout)
    - just worker target with MinIO dev defaults
  </what-built>
  <how-to-verify>
    Prerequisites (run each in a separate terminal or as background):
    1. `cd backend && just db-up` — start Postgres (and MinIO from compose.yaml)
    2. Optionally `just dev` in a second terminal to confirm web binary still works alongside

    Worker verification steps:
    3. Run: `cd backend && just worker`
    4. Within 5 seconds, confirm these log lines appear (look for structured key=value or JSON output):
       - `"river migration applied"` (with version number) OR no migration lines if already applied
       - `"worker ready"` — confirms binary started without errors
       - `"worker heartbeat"` — RunOnStart:true fires the heartbeat immediately on first tick; expect this within ~10 seconds

    5. Wait until you have observed AT LEAST 2 separate `"worker heartbeat"` log lines separated by approximately 60 seconds.
       The first fires on startup (RunOnStart); the second confirms the PeriodicJob scheduler is running correctly and is not just a one-shot.
       Look for: `msg="worker heartbeat" job_id=... attempt=1` appearing twice with ~60 seconds between entries.
       Do not proceed to the next step until both heartbeats have been observed.

    6. Send SIGINT: press Ctrl+C in the worker terminal.
    7. Confirm these log lines appear in order:
       - `"shutting down"`
       - `"shutdown complete"`
       Process must exit cleanly (exit code 0 or from signal, NOT with a panic or error).

    Failed job visibility check (WORK-04 — optional if no failing job is easy to trigger):
    - River logs job errors automatically via SlogErrorHandler. If the orphan cleanup job ran (visible as `"orphan cleanup complete"` with `orphans_found=0`), WORK-04 is satisfied by the error handler being registered and logging correctly.
    - Alternatively: temporarily modify HeartbeatWorker.Work to return fmt.Errorf("test error"), rebuild, run worker, and look for `msg="job error" job_kind=heartbeat attempt=1` in logs. Revert after confirming.

    Full test suite gate:
    8. `cd backend && go test ./... && go build ./...` — must exit 0
  </how-to-verify>
  <resume-signal>
    Do not approve until you have observed at least 2 "worker heartbeat" log lines separated by approximately 60 seconds — this confirms the PeriodicJob scheduler is running correctly, not just the RunOnStart fire.
    Type "approved" once both heartbeats are visible and shutdown was clean.
    Type "issues: [describe]" if any step failed or was unclear.
  </resume-signal>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| Local dev environment | All verification runs against local MinIO and Postgres; no production systems involved |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-06-09 | Information Disclosure | Worker logs in dev mode | accept | Dev mode uses slog text handler (not JSON); no PII in heartbeat/orphan cleanup logs; production uses JSON handler (controlled by ENV=production) |
</threat_model>

<verification>
Manual verification only (this plan IS the verification step). Pass criteria:

1. `"worker ready"` appears in logs within 5 seconds of `just worker`
2. `"worker heartbeat"` appears within 10 seconds (RunOnStart fires first heartbeat immediately)
3. A second `"worker heartbeat"` appears ~60 seconds later (confirms scheduler is running — REQUIRED before approval)
4. Ctrl+C produces `"shutting down"` then `"shutdown complete"` with no panic
5. `cd backend && go test ./... && go build ./...` exits 0
</verification>

<success_criteria>
- Worker starts, connects to Postgres, applies river migrations, and logs "worker ready"
- Heartbeat job fires within seconds of startup (RunOnStart: true)
- Second heartbeat fires ~60 seconds later confirming the scheduler loop is active
- Graceful shutdown works: SIGINT → "shutting down" → "shutdown complete"
- Full test suite green: go test ./... && go build ./... exits 0
- All WORK-01 through WORK-04 requirements satisfied as observable behaviors
</success_criteria>

<output>
After completion, create `.planning/phases/06-background-worker/06-03-SUMMARY.md`
</output>
