Phase 9: Faster Write-Back (CAL-15) — event-driven outbox drain #14

Merged
luckberg merged 26 commits from gsd/phase-09-faster-write-back into main 2026-06-12 21:19:17 -04:00
Showing only changes of commit 555f4c80f8 - Show all commits
@@ -0,0 +1,84 @@
---
phase: 9
slug: faster-write-back
status: draft
nyquist_compliant: false
wave_0_complete: false
created: 2026-06-12
---
# Phase 9 — Validation Strategy
> Per-phase validation contract for feedback sampling during execution.
---
## Test Infrastructure
| Property | Value |
|----------|-------|
| **Framework** | Vitest (existing) |
| **Config file** | `apps/api/vitest.config.ts` |
| **Quick run command** | `cd apps/api && npx vitest run tests/broker/outboxWorker.test.ts` |
| **Full suite command** | `cd apps/api && npm test` (→ `vitest run`) |
| **Estimated runtime** | ~520 seconds (existing broker suite + new signal cases; no live Fastmail) |
---
## Sampling Rate
- **After every task commit:** Run quick command (`vitest run tests/broker/outboxWorker.test.ts`)
- **After every plan wave:** Run full suite (`npm test`) + `npm run typecheck` (`tsc --noEmit` — esbuild/Vitest does not catch type errors; see [[vitest-passes-tsc-fails]])
- **Before `/gsd-verify-work`:** Full suite + typecheck must be green
- **Max feedback latency:** ~20 seconds
---
## Per-Task Verification Map
> Task IDs are assigned by the planner; rows below map each Success Criterion + CAL-15 to its
> concrete automated assertion (Nyquist coverage). The signal-path tests need no fake timers —
> a `setImmediate`/microtask flush is sufficient to observe the synchronous emit → drain wiring
> (per RESEARCH.md). The 15s `setInterval` fallback is exercised with Vitest fake timers as today.
| Item | Wave | Requirement | Threat Ref | Expected Behavior | Test Type | Automated Command | File Exists | Status |
|------|------|-------------|------------|-------------------|-----------|-------------------|-------------|--------|
| SC-1 signal → prompt drain | 1 | CAL-15 | — | Enqueuing a pending row publishes the trigger and `runOutboxDrain` runs without waiting for the 15s interval (CalDAV dispatch stubbed) | unit/integration | `vitest run tests/broker/outboxWorker.test.ts` | ✅ extends existing | ⬜ pending |
| SC-2 fire-and-forget 202 | 1 | CAL-15 | — | Publishing the signal is non-blocking; route enqueue path makes no inline CalDAV call (signal is fire-and-forget) | unit | `vitest run tests/broker/outboxWorker.test.ts` | ✅ extends existing | ⬜ pending |
| SC-3 create-before-delete under move | 1 | CAL-15 | — | Edit-as-move publishes once after the transaction commits; CREATE row precedes DELETE; no signal fires between the two inserts | unit | `vitest run tests/broker/outboxWorker.test.ts` | ✅ extends existing | ⬜ pending |
| SC-4 exactly-once overlap | 1 | CAL-15 | — | Concurrent `scheduleOutboxDrain` calls + 15s fallback overlap produce no duplicate CalDAV PUT for the same uid (`isDraining` guard + `pending AND next_attempt_at <= NOW()` selection unchanged) | unit | `vitest run tests/broker/outboxWorker.test.ts` | ✅ extends existing | ⬜ pending |
| SC-5 / D-05 trailing re-drain | 1 | CAL-15 | — | A signal arriving during an in-flight drain sets `drainRequested` and triggers exactly one follow-up drain after the current one finishes; the 15s `setInterval` fallback still runs and recovers missed rows | unit | `vitest run tests/broker/outboxWorker.test.ts` | ✅ extends existing | ⬜ pending |
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
---
## Wave 0 Requirements
- Existing infrastructure covers all phase requirements. `apps/api/tests/broker/outboxWorker.test.ts`
already provides the DB / write / sync mock scaffold (`isDraining`, drain, edit-as-move ordering).
New cases are added to that file (or a sibling `tests/broker/outboxTrigger.test.ts`); no new
framework, config, or fixtures are installed.
---
## Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|----------|-------------|------------|-------------------|
| — | — | — | — |
*All phase behaviors have automated verification. No operator-stopwatch checkpoint (D-10): the dev-bypass user (id 1) has no CalDAV credential/calendars, so a live event-create 422s in the dev stack ([[dev-data-user1-no-calendars]]); the automated wiring test is the durable, CI-guarded evidence.*
---
## Validation Sign-Off
- [ ] All tasks have `<automated>` verify or Wave 0 dependencies
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
- [ ] Wave 0 covers all MISSING references
- [ ] No watch-mode flags (`vitest run`, not `vitest`)
- [ ] Feedback latency < 20s
- [ ] `nyquist_compliant: true` set in frontmatter
**Approval:** pending