3.8 KiB
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 -
Write a failing test proving stable request fingerprints and different fingerprints for source/schema changes.
-
Write a failing test proving journal round-trip and rejection of corrupt/newer records.
-
Run
./gradlew :adapter:outbound:fileserver:test --tests '*LocalPublicationJournalTest' --console=plainand confirm the missing types fail compilation. -
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.
-
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 -
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.
-
Write a failing test that reconstructs a new adapter over a sealed journal plus staged bytes and resumes publication without calling the producer.
-
Write a failing test proving the same operation ID with a different request is a conflict and a digest mismatch is indeterminate.
-
Run the recovery test and confirm RED.
-
Persist
WRITING,SEALED, andPUBLISHEDrecords with temp + force + atomic move. Verify the target size and SHA-256 before terminal reconstruction. -
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 -
Mark single-node local restart recovery as implemented.
-
Keep multi-node fencing, bounded background reaper, SFTP, NFS and HA evidence explicitly unimplemented.
-
Run
./gradlew :adapter:outbound:fileserver:check --console=plain.