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

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=plain and 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, and PUBLISHED records 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.