Files
tech-log-backend/docs/superpowers/plans/2026-07-28-fileserver-durable-recovery.md
T

67 lines
3.8 KiB
Markdown

# Fileserver Durable Recovery Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this
> plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. Repository policy is
> human-only, so no step stages or commits changes.
**Goal:** Make the local publication provider restart-safe for completed and sealed operations
without re-running the row producer.
**Architecture:** Keep the application port unchanged. The adapter owns a private operation journal
under `.ca-fileserver/operations`, writes records through forced temp files and atomic rename, and
uses a deterministic request fingerprint. A retry restores a verified terminal receipt or resumes a
sealed staged artifact; disagreement is a conflict or indeterminate outcome, never an overwrite.
**Tech Stack:** Java 21 NIO, JUnit 5, AssertJ, existing Gradle quality gates.
---
### Task 1: Define deterministic journal records and request fingerprints
**Files:**
- Create: `src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/LocalPublicationJournalRecord.java`
- Create: `src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/LocalPublicationJournalCodec.java`
- Create: `src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FilePublishRequestFingerprint.java`
- Test: `src/adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/LocalPublicationJournalTest.java`
- [x] Write a failing test proving stable request fingerprints and different fingerprints for
source/schema changes.
- [x] Write a failing test proving journal round-trip and rejection of corrupt/newer records.
- [x] Run
`./gradlew :adapter:outbound:fileserver:test --tests '*LocalPublicationJournalTest' --console=plain`
and confirm the missing types fail compilation.
- [x] Implement a bounded flat JSON codec with schema version, state, fingerprint, locator token,
checksum/counts and receipt snapshot fields. It must reject duplicate/unknown keys and never
serialize absolute paths or row data.
- [x] Run the focused test and confirm GREEN.
### Task 2: Add forced atomic journal persistence and recovery
**Files:**
- Create: `src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/LocalPublicationJournal.java`
- Modify: `src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/LocalFilePublicationAdapter.java`
- Test: `src/adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/LocalFilePublicationRecoveryTest.java`
- [x] Write a failing test where a completed operation is retried with a producer that throws; the
original receipt must be returned and the producer must remain uncalled.
- [x] Write a failing test that reconstructs a new adapter over a sealed journal plus staged bytes
and resumes publication without calling the producer.
- [x] Write a failing test proving the same operation ID with a different request is a conflict and
a digest mismatch is indeterminate.
- [x] Run the recovery test and confirm RED.
- [x] Persist `WRITING`, `SEALED`, and `PUBLISHED` records with temp + force + atomic move. Verify
the target size and SHA-256 before terminal reconstruction.
- [x] Run all Fileserver tests and confirm GREEN.
### Task 3: Report the exact readiness boundary
**Files:**
- Modify: `src/adapter/outbound/fileserver/README.md`
- Modify: `src/adapter/outbound/fileserver/CLAUDE.md`
- Modify: `docs/superpowers/specs/2026-07-26-fileserver-production-capability-design.md`
- [x] Mark single-node local restart recovery as implemented.
- [x] Keep multi-node fencing, bounded background reaper, SFTP, NFS and HA evidence explicitly
unimplemented.
- [x] Run `./gradlew :adapter:outbound:fileserver:check --console=plain`.