feat: redis, fileserver, httpclient 런타임 시점 구현 추가

This commit is contained in:
donghyeon-ka
2026-07-28 14:26:54 +09:00
parent 7363b2aa1e
commit b3add0162d
257 changed files with 30430 additions and 1357 deletions
@@ -1,3 +1,7 @@
> **SUPERSEDED — HISTORICAL PROVENANCE ONLY (2026-07-25):** The user-approved harness-free
> Mode B amendment supersedes this plan. Retain the body as historical provenance; it is not
> executable instruction.
# Harness Policy Engine Implementation Plan
> **Spec:** `docs/superpowers/specs/2026-07-20-harness-policy-engine-design.md`
@@ -0,0 +1,144 @@
# Application Outbox Failure Reporting Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development
> (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use
> checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make `application-core` framework/logging-free while preserving one safe structured ERROR
after each confirmed outbox FAILED/DEAD transition.
**Architecture:** The application owns a narrow typed reporting port and safe report value.
Messaging renders the report through SLF4J, and bootstrap only injects it. Transition state remains
authoritative; reporter failures are non-authoritative and contained.
**Tech Stack:** Java 21 records, JUnit Jupiter, AssertJ, Spring Boot 4 configuration, SLF4J 2 fluent
logging, Logback capture tests, ArchUnit, Gradle Groovy DSL, dependency locking.
---
### Task 1: Safe Application Report Contract
**Files:**
- Create: `src/application-core/src/test/java/dev/caskeleton/application/outbox/OutboxRelayFailureReportTest.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/outbox/OutboxRelayFailureReport.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/outbox/OutboxRelayFailureReportPort.java`
- [ ] Write factory, invariant, and reflection-whitelist tests for the exact eight record components.
- [ ] Run `./gradlew :application-core:test --tests '*OutboxRelayFailureReportTest' --console=plain`
and record the expected missing-type RED.
- [ ] Implement the immutable record, exact invariants, factories, and functional port.
- [ ] Re-run the focused value test and record GREEN.
### Task 2: Relay Reporting Behavior
**Files:**
- Modify: `src/application-core/src/test/java/dev/caskeleton/application/outbox/PublishPendingOutboxEventsUseCaseTest.java`
- Modify: `src/application-core/src/main/java/dev/caskeleton/application/outbox/PublishPendingOutboxEventsUseCase.java`
- Modify direct test constructor sites under
`src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/`
- [ ] Add recording/throwing reporters and tests for exact FAILED/DEAD reports, all no-report paths,
transition failure propagation, and reporter-failure continuation.
- [ ] Run the relay test and record constructor/behavior RED.
- [ ] Inject the reporter after the publish port, remove SLF4J, report only after successful
transition, and contain reporter `RuntimeException`.
- [ ] Update test-only direct constructors with explicit lambdas and re-run relay tests GREEN.
### Task 3: Structured Messaging Adapter and Publish-Adapter Deduplication
**Files:**
- Create:
`src/adapter/outbound/messaging/src/test/java/dev/caskeleton/adapter/outbound/messaging/outbox/Slf4jOutboxRelayFailureReportAdapterTest.java`
- Create:
`src/adapter/outbound/messaging/src/main/java/dev/caskeleton/adapter/outbound/messaging/outbox/Slf4jOutboxRelayFailureReportAdapter.java`
- Modify:
`src/adapter/outbound/messaging/src/test/java/dev/caskeleton/adapter/outbound/messaging/outbox/OutboxMessagePublishAdapterTest.java`
- Modify:
`src/adapter/outbound/messaging/src/main/java/dev/caskeleton/adapter/outbound/messaging/outbox/OutboxMessagePublishAdapter.java`
- [ ] Write Logback capture tests for exact ERROR count, fixed fields, throwable, retry-only time,
unsafe-data absence, internal logging failure containment, and the adapter contract that
`report(null)` never throws.
- [ ] Run
`./gradlew :adapter:outbound:messaging:test --tests '*Slf4jOutboxRelayFailureReportAdapterTest' --console=plain`
and record missing-type RED.
- [ ] Implement the SLF4J 2 fluent adapter and re-run GREEN.
- [ ] Replace outbox publish WARN expectations with no-log and propagation expectations; run RED.
- [ ] Remove `FailOpenDependencyLogger` from the outbox adapter and re-run its tests GREEN, leaving
`OutboundMessagePublisher` unchanged.
### Task 4: Unconditional Reporter Wiring
**Files:**
- Modify: `src/adapter/outbound/messaging/src/main/java/dev/caskeleton/adapter/outbound/messaging/MessagingConfig.java`
- Modify: `src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxConfig.java`
- Modify: `src/app-bootstrap/src/test/java/dev/caskeleton/adapter/outbound/OptionalAdapterBeanGatingTest.java`
- Modify: `src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxConfigTest.java`
- [ ] Add disabled and active context assertions for exactly one structured reporter bean.
- [ ] Run `OptionalAdapterBeanGatingTest` and record RED.
- [ ] Add the unconditional messaging reporter bean, use `disabled` for blank broker, update outbox
publish adapter construction, and inject the port through bootstrap.
- [ ] Re-run the gating and outbox configuration tests GREEN.
### Task 5: Application Dependency Purity
**Files:**
- Modify: `src/build.gradle`
- Modify: `src/application-core/build.gradle`
- Mechanically regenerate only: `src/application-core/gradle.lockfile`
- [ ] Add `verifyApplicationCoreDependencyPurity`, wire it into `:application-core:check`, and run it
against the current starter declaration to record RED.
- [ ] Give `application-core` only JUnit Jupiter and AssertJ test dependencies while retaining the
shared Boot test dependencies for every other leaf.
- [ ] Remove the application Spring Boot starter and re-run the purity task GREEN.
- [ ] Run
`./gradlew :application-core:resolveAndLockAll --write-locks --console=plain`; confirm no other
lockfile changes.
- [ ] Run application lock verification, tests, and compile/test runtime dependency reports.
### Task 6: Non-Vacuous Diagnostic Architecture Rule
**Files:**
- Modify:
`src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java`
- Create:
`src/app-bootstrap/src/test/java/dev/caskeleton/application/architecture/violations/ApplicationDiagnosticFrameworkViolation.java`
- Modify:
`src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchitectureViolationFixtureTest.java`
- [ ] Add the violation fixture inside the exact `dev.caskeleton.application..` rule scope and its
mutation assertion; run it before the rule to record RED.
- [ ] Add `APPLICATION_HAS_NO_DIAGNOSTIC_FRAMEWORK`, scoped exactly to
`dev.caskeleton.application..`, for SLF4J, JUL, Logback, Log4j, and Micrometer.
- [ ] Run the mutation test and production `CleanArchitectureTest` GREEN.
### Task 7: Documentation and Verification
**Files:**
- Modify: `src/application-core/CLAUDE.md`
- Modify: `src/application-core/README.md`
- Modify: `src/adapter/outbound/messaging/CLAUDE.md`
- Modify: `src/adapter/outbound/messaging/README.md`
- Modify relevant wiring guidance in `src/app-bootstrap/README.md`
- [ ] Document the framework-free application contract, typed report semantics, messaging ownership,
duplicate-log rule, and bootstrap wiring-only role.
- [ ] Run focused application, messaging, gating, architecture mutation, production architecture,
and available outbox integration tests.
- [ ] Run `verifyCleanArchitectureDependencies`, dependency evidence reports, and `check`.
- [ ] Run required safety greps, `git diff --check`, and `git status --short`; report any skip or
remaining risk.
- [ ] Hand the exact LLM Wiki capture responsibility and evidence back to the top-level controller;
do not write the vault from this dispatched scope.
No step authorizes staging, committing, amending, pushing, public-path changes, CI changes, module
registry changes, or `.harness` changes.
@@ -0,0 +1,99 @@
# Harness-Free Mode B Amendment Implementation Plan
> **For agentic workers:** Execute this plan task-by-task with
> `superpowers:executing-plans`; use `superpowers:test-driven-development` for the build behavior
> change and `superpowers:verification-before-completion` before reporting results.
**Goal:** Restore Gradle bootstrap and Clean Architecture dependency enforcement without recreating
the absent development harness.
**Architecture:** One strict JSON registry under `src/config/architecture/` owns all 19 leaf
identities, paths, and allowed production project edges. Gradle settings validate and include the
registry fail-closed; the root dependency verification task reads the same file and checks actual
production project dependencies against it.
**Tech Stack:** Gradle Groovy DSL, Groovy `JsonSlurper`, strict JSON, Java 21.
**Working policy:** Human-only git handling. Do not stage, commit, amend, push, or create a PR.
---
### Task 1: Capture the broken bootstrap
**Files:**
- Read: `src/settings.gradle`
- [x] Run `cd src && ./gradlew help --console=plain`.
- [x] Confirm exit 1 is caused by the missing `.harness/project/modules.yaml`, not dependency
resolution or an unrelated build failure.
### Task 2: Add the Gradle-owned registry
**Files:**
- Create: `src/config/architecture/modules.json`
- Read: each of the 19 leaf-module `build.gradle` files
- [x] Record exactly 19 unique module IDs, Gradle paths, and repository-relative source paths.
- [x] Set `allowed_dependencies` from each leaf's current `api`, `implementation`, `compileOnly`,
and `runtimeOnly` project dependencies.
- [x] Exclude test/fixture configurations from production policy and keep `sample-portfolio` a
fixture consumer that no production leaf may depend on.
- [x] Parse the file with Python's strict JSON parser and compare its edges with the checked-in
leaf build declarations.
### Task 3: Restore Gradle bootstrap and dependency enforcement
**Files:**
- Modify: `src/settings.gradle`
- Modify: `src/build.gradle`
- [x] Make settings load only `config/architecture/modules.json`.
- [x] Fail closed on a missing registry, wrong root/module/field types, empty values, duplicate
identities or paths, unsafe path shapes, unknown/self dependencies, count drift, or missing
source directories.
- [x] Include every registered Gradle path and map it to its repository-root-relative source
directory.
- [x] Make `verifyCleanArchitectureDependencies` read the same registry without a second module
list.
- [x] Preserve all-leaf coverage and forbidden-edge checks, explicitly reject a production edge
to `sample-portfolio`, and replace stale error wording with actionable registry guidance.
### Task 4: Align active repository guidance
**Files:**
- Modify: `AGENTS.md`
- Modify: `CLAUDE.md`
- Modify: `README.md`
- Modify: `src/README.md`
- Modify: all 19 nearest leaf-module `CLAUDE.md` files that name the old registry
- Annotate as superseded: the 2026-07-20 harness design and plan
- [x] Point active topology and allowed-edge guidance to
`src/config/architecture/modules.json`.
- [x] State that focused commands are derived from the owning Gradle path rather than a task
packet.
- [x] Keep all eight local HARD-STOP meanings, architecture boundaries, human-only git policy,
verification discipline, and LLM Wiki capture requirements.
- [x] Make the earlier harness documents explicit historical provenance rather than active
reconstruction instructions.
### Task 5: Verify from a fresh Gradle invocation
**Files:**
- Verify: all changed files
- [ ] Run `cd src && ./gradlew help --console=plain`.
- [ ] Run `cd src && ./gradlew projects --console=plain`.
- [ ] Run `cd src && ./gradlew verifyCleanArchitectureDependencies --console=plain`.
- [ ] Run a deterministic strict-JSON script proving exactly 19 unique IDs/Gradle paths and
existing source directories.
- [ ] Run a deterministic comparison between registry edges and leaf production project
dependencies.
- [ ] Run `git diff --check` and `git status --short`.
- [ ] Report exact exits, any unavailable checks, LLM Wiki capture outcome, and remaining risks
without claiming the broader Phase A/refactor is complete.
@@ -0,0 +1,117 @@
# Harness-Free Quality and Security CI Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:executing-plans` to implement this
> plan task-by-task, `superpowers:test-driven-development` for executable drift controls, and
> `superpowers:verification-before-completion` before reporting. Git remains human-only: do not
> stage, commit, amend, or push.
**Goal:** Reconstruct a harness-free, repository-internal quality and dependency-security CI
control plane that is truthful to the current Gradle build and `main` branch.
**Architecture:** Canonical workflows live only under `.github/workflows`. A small YAML gate matrix
maps current controls to real Gradle tasks/plugins/tests and workflow jobs, while a portable Bash
verifier rejects drift; vulnerability policy is enforced by a platform-neutral Trivy filesystem
job with guarded GitHub-only complements.
**Tech Stack:** GitHub Actions-compatible YAML, Bash, Gradle 9 Groovy DSL, Java/Temurin 21, Trivy,
jq, lychee.
---
### Task 1: Capture missing-control RED
**Files:**
- Verify absent: `.trivyignore.yaml`
- Verify absent: `.github/ci-gate-matrix.yml`
- Verify absent: `.github/scripts/verify-gate-matrix.sh`
- [ ] Run `cd src && ./gradlew verifyTrivyignore --console=plain`.
- [ ] Confirm the failure names the missing repository-root `.trivyignore.yaml`.
- [ ] Confirm the matrix, verifier, and canonical workflows are absent.
### Task 2: Add repository baselines
**Files:**
- Create: `.tool-versions`
- Create: `.gitattributes`
- Create: `.trivyignore.yaml`
- [ ] Pin `java temurin-21.0.11+10`, matching candidate evidence and the local Gradle launcher JDK.
- [ ] Normalize source, YAML, Markdown, Gradle, and shell text to LF; keep `gradlew.bat` CRLF and
mark common binary formats `-text`.
- [ ] Add the four structured empty Trivy sections with suppression governance comments.
- [ ] Run `cd src && ./gradlew verifyTrivyignore --console=plain` and expect zero suppressions
validated.
### Task 3: Add quality governance and drift verification
**Files:**
- Create: `.github/CODEOWNERS`
- Create: `.github/pull_request_template.md`
- Create: `.github/ci-gate-matrix.yml`
- Create: `.github/scripts/verify-gate-matrix.sh`
- Create: `.github/workflows/ci-quality-gates.yml`
- Create: `.github/workflows/link-check.yml`
- [ ] Record only current Gradle/task/test/job mechanisms in the matrix.
- [ ] Implement repository-root-safe matrix parsing with schema, uniqueness, task/plugin/test, and
workflow-job checks.
- [x] Before Java/Gradle, fail unless `docs/security/public-paths-snapshot.txt` is committed and
non-empty; do not let the Gradle task create a first-run CI baseline.
- [ ] Have a human track and commit the canonical snapshot; agents do not stage or commit, and CI's
`git ls-files` precondition rejects an untracked worktree file.
- [ ] Add required `quality-gates`, `sample-off`, and `gate-matrix-lint` jobs plus the advisory
quarantine job.
- [ ] Make `release-gate` depend exactly on the three required jobs and fail unless all succeeded.
- [ ] Add path-scoped link checking for PR and `main` push.
- [ ] Pin every workflow `uses:` reference to a verified full commit SHA and retain its immutable
release label in an inline comment.
- [ ] Run Bash syntax and gate-matrix checks.
### Task 4: Add dependency-vulnerability controls
**Files:**
- Create: `.github/dependency-review-config.yml`
- Create: `.github/dependency-vulnerability-policy.md`
- Create: `.github/scripts/install-jq.sh`
- Create: `.github/workflows/dependency-vulnerability.yml`
- [ ] Configure PR dependency review to block new High/Critical runtime vulnerabilities and
forbidden strong/network-copyleft licenses without posting PR summary comments.
- [ ] Document High/Critical blocking, Medium/Low advisory, KEV fail-closed handling, suppression
review, GitHub/Gitea differences, egress, and mirror requirements.
- [ ] Install checksum-pinned jq and version-pinned Trivy under `${RUNNER_TEMP}`, adding them through
`${GITHUB_PATH}` without privileged writes.
- [ ] Guard GitHub-only review/submission and keep `trivy-fs` platform-neutral on all required
triggers.
- [ ] Pass `--ignorefile .trivyignore.yaml` to every Trivy invocation.
- [ ] Reject KEV catalogs with blank metadata, non-positive/non-integral or mismatched counts,
empty vulnerability arrays, invalid CVE identifiers, or duplicate identifiers before
intersection.
- [ ] Reject malformed or empty Trivy JSON before extracting candidate vulnerability identifiers.
### Task 5: Verify the reconstructed slice
**Files:**
- Verify: all files created by this plan
- [ ] Parse strict policy/matrix YAML with an available parser and document GitHub `on` parser
limitations if applicable.
- [ ] Prove only `main` is an active branch trigger and no active `master` remains.
- [ ] Prove every Trivy scan consumes the root ignore file.
- [ ] Prove the release fan-in is exact and excludes quarantine.
- [x] Prove the missing/empty/untracked snapshot precondition exits non-zero; the canonical
`/api/healthcheck` snapshot now exists in the worktree but still requires a human commit.
- [ ] Exercise the KEV predicate with empty/malformed/count/CVE/duplicate failures and a valid
synthetic catalog.
- [ ] Exercise the Trivy JSON predicate with malformed Results/Vulnerabilities/IDs and a realistic
valid Results array.
- [ ] Prove no harness call or `.gitea/workflows` shadow was introduced.
- [ ] Run `git diff --check` and `git status --short`.
- [ ] Capture the work in the required LLM Wiki branch note, including evidence and external
blockers, without claiming server Actions or full Phase A completion.
@@ -0,0 +1,103 @@
# Harness-Free Module and Gradle Hygiene Implementation Plan
**Goal:** Apply the approved 19-leaf dependency and boundary cleanup without `.harness`.
**Spec:** `docs/superpowers/specs/2026-07-25-module-gradle-hygiene-harness-free-design.md`
**Policy:** TDD for behavior/boundary changes; focused proof before dependency removal; human-only
Git operations.
## Task 1: Lock Phase B and characterize the Phase C baseline
- [ ] Confirm the Phase B focused tests, dependency-purity gate, spec review, and quality review
are green.
- [ ] Record the current 19-leaf registry and affected lockfiles.
- [ ] Run the existing OpenAPI runtime tests before changing springdoc.
## Task 2: Isolate pure-core tests
- [ ] Change the root test convention so `domain-core`, `application-core`, and
`shared-contract` receive only JUnit Jupiter, AssertJ, and the platform launcher.
- [ ] Run the three core test suites and dependency reports.
- [ ] Regenerate only their affected locks and prove no Spring coordinate remains on their test
runtime classpaths.
## Task 3: Prune core/inbound declarations and align Boot 4
- [ ] Before editing, run and record each affected leaf's `compileJava`, `compileTestJava`, `test`,
runtime dependency report, and relevant dependency insight.
- [ ] Remove the approved unused project edges from application and inbound leaves.
- [ ] Upgrade springdoc to `3.0.0`.
- [ ] Remove unused GraphQL/WebSocket Jackson 2 declarations and unused gRPC direct declarations.
- [ ] Characterize `jackson-databind-nullable` with dependency insight and focused
present/null/undefined Jackson 3 tests; exclude its Jackson 2 transitive dependency only if the
tests and real-server OpenAPI contract remain green.
- [ ] Run each affected leaf test plus the two real-server `/v3/api-docs` tests.
- [ ] Update the OpenAPI snapshot only if the generated public contract is semantically unchanged.
## Task 4: Prune outbound declarations
- [ ] Before editing, run and record each affected leaf's `compileJava`, `compileTestJava`, `test`,
runtime dependency report, and relevant dependency insight.
- [ ] Apply the approved support/cache/httpclient/identifier/messaging/notification project-edge
removals.
- [ ] Remove Groovy/Spock only from leaves with no Groovy tests.
- [ ] Narrow fileserver/objectstorage from the broad Boot starter to autoconfigure plus SLF4J API.
- [ ] Remove the JPA domain edge and remove explicit Flyway core only if focused compile/test proves
it is redundant.
- [ ] Run affected compile/tests before and after each dependency group.
## Task 5: Enforce configuration-processor parity
- [ ] Add a failing verification fixture or temporary mutation proving the exact
`@ConfigurationProperties(` parity check detects missing and extra processors.
- [ ] Register `verifyConfigurationPropertiesProcessor` from the JSON registry and wire it into
leaf `check`.
- [ ] Add processors to settings-owning leaves and remove the unused GraphQL processor.
- [ ] Run the new gate and affected settings tests.
## Task 6: Remove the Mongo example domain
- [ ] Add tests for disabled mode, enable-flag binding, and enabled infrastructure with a mock
`MongoClient`.
- [ ] Delete all production/test `Example*` types and remove the fixed example bean/repository
scanning.
- [ ] Remove obsolete project and Testcontainers dependencies.
- [ ] Run the Mongo tests and an `rg` assertion that production contains no `Example*`.
## Task 7: Invert sample correlation access
- [ ] Add framework-free `CorrelationIdPort` contract tests/fakes.
- [ ] Add and test the inbound web MDC implementation.
- [ ] Change the two sample application collaborators to use the port while retaining event-id
fallback behavior.
- [ ] Add an architecture assertion that sample application source has no SLF4J dependency.
- [ ] Run application, web, sample outbox/poster, and architecture focused tests.
## Task 8: Clean generated state and composition documentation
- [ ] Delete tracked `src/sample-portfolio/.jqwik-database` and ignore future files.
- [ ] Correct app-bootstrap “every module” wording and document default versus opt-in runtime
composition.
- [ ] Preserve the existing default runtime dependency set.
## Task 9: Locks, full verification, and review
- [ ] Regenerate strict lockfiles only with each affected leaf's
`:leaf-path:resolveAndLockAll --write-locks`; do not run the root all-leaf writer.
- [ ] Run all commands in the design verification section.
- [ ] Run `git diff --check` and inspect the complete unstaged/untracked status.
- [ ] Request spec and code-quality review; fix all actionable findings.
- [ ] Update the mandated LLM Wiki raw branch note and derived raw notes, or record the exact
missing-vault blocker.
## Final review hardening
- [x] Pin the Springdoc 3 `ApiError.details` widening with a real-server RED test.
- [x] Add a web-owned OpenAPI customizer, import it in both real-server test applications, and
restore the committed `type: object` snapshot without adding Swagger to `shared-contract`.
- [x] Reproduce starter-driven Mongo activation through an actual `@EnableAutoConfiguration`
context in both default and explicit-false modes.
- [x] Register a module-level Boot 4 `AutoConfigurationImportFilter` that blocks Mongo
auto-configuration until the module enable flag is true.
- [x] Re-run affected formatting, locks, focused tests, and all design verification commands.
@@ -0,0 +1,66 @@
# 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`.
@@ -0,0 +1,236 @@
# Fileserver Production Capability Foundation Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Replace the list-materializing CSV demo boundary with the Phase 1 framework-free publication contract and a bounded, staged local CSV R1 provider without claiming crash-safe R2 guarantees.
**Architecture:** `application-core` owns typed publication requests, rows, cells, producer/sink callbacks, opaque references, and receipts. `adapter:outbound:fileserver` owns CSV encoding, spreadsheet-formula mitigation, staging, digest/count limits, and local atomic publication. The legacy `FileExportPort` remains temporarily for compatibility and is explicitly documented as deprecated R0/R1 behavior.
**Tech Stack:** Java 21, JUnit 5, AssertJ, Spring Boot configuration properties, JDK NIO filesystem and SHA-256.
---
### Task 1: Add the framework-free publication contract
**Files:**
- Create: `src/application-core/src/main/java/dev/caskeleton/application/filepublication/FilePublicationPort.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/filepublication/FilePublishRequest.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/filepublication/FilePublishOperationId.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/filepublication/FileDestinationId.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/filepublication/LogicalFileName.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/filepublication/SourceRevision.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/filepublication/ExportSchema.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/filepublication/TabularCell.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/filepublication/TabularRow.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/filepublication/TabularRowProducer.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/filepublication/TabularRowSink.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/filepublication/FilePublishReceipt.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/filepublication/PublishedFileReference.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/filepublication/FileVersion.java`
- Test: `src/application-core/src/test/java/dev/caskeleton/application/filepublication/FilePublicationContractTest.java`
- [ ] **Step 1: Write the failing contract test**
```java
@Test
void requestRejectsPathLikeLogicalNamesAndSchemaRejectsDuplicateColumns() {
assertThatThrownBy(() -> new LogicalFileName("../report.csv"))
.isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(
() ->
new ExportSchema(
"worklog-v1",
1,
List.of(
new ExportSchema.Column(
"id", ExportSchema.CellType.INTEGER, false,
ExportSchema.FormulaPolicy.REJECT, 64),
new ExportSchema.Column(
"id", ExportSchema.CellType.TEXT, false,
ExportSchema.FormulaPolicy.MITIGATE, 128))))
.isInstanceOf(IllegalArgumentException.class);
}
```
- [ ] **Step 2: Verify RED**
Run: `cd src && ./gradlew :application-core:test --tests '*FilePublicationContractTest' --console=plain`
Expected: compilation failure because the `filepublication` contract does not exist.
- [ ] **Step 3: Implement immutable validated values**
The contract must expose this shape and no `Path`, `File`, stream, Spring, or provider type:
```java
public interface FilePublicationPort {
FilePublishReceipt publish(FilePublishRequest request, TabularRowProducer producer);
}
@FunctionalInterface
public interface TabularRowProducer {
void produce(TabularRowSink sink);
}
public interface TabularRowSink {
void write(TabularRow row);
void checkpoint();
}
```
`TabularCell` is a sealed interface with nested records for text, integer, decimal, boolean, date,
instant, and null. `ExportSchema` owns ordered columns, cell type, nullability, formula policy, and
per-cell byte bounds. Records reject null/blank IDs, path separators in `LogicalFileName`, duplicate
column names, empty schemas, and non-positive limits.
- [ ] **Step 4: Verify GREEN**
Run: `cd src && ./gradlew :application-core:test --tests '*FilePublicationContractTest' --console=plain`
Expected: PASS.
### Task 2: Add streaming CSV encoding and staged local publication
**Files:**
- Create: `src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/StreamingCsvEncoder.java`
- Create: `src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/LocalFilePublicationAdapter.java`
- Create: `src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/LocalFilePublicationPolicy.java`
- Test: `src/adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/LocalFilePublicationAdapterTest.java`
- [ ] **Step 1: Write the failing streaming publication tests**
```java
@Test
void publishesRowsThroughTheSinkAndReturnsAnOpaqueReceipt() {
AtomicInteger calls = new AtomicInteger();
FilePublishReceipt receipt =
adapter.publish(
request(),
sink -> {
calls.incrementAndGet();
sink.write(new TabularRow(List.of(new IntegerCell(1), new TextCell("=cmd"))));
});
assertThat(calls).hasValue(1);
assertThat(receipt.reference().value()).doesNotContain(tempDir.toString());
assertThat(Files.readString(publishedFile(receipt), UTF_8)).contains("1,'=cmd");
}
@Test
void abortsBeforeFinalPublicationWhenTheByteLimitIsExceeded() {
assertThatThrownBy(
() -> adapter.publish(request(), sink -> sink.write(oversizedRow())))
.isInstanceOf(FilePublicationException.class);
assertThat(finalArtifacts()).isEmpty();
}
```
- [ ] **Step 2: Verify RED**
Run: `cd src && ./gradlew :adapter:outbound:fileserver:test --tests '*LocalFilePublicationAdapterTest' --console=plain`
Expected: compilation failure because the staged provider does not exist.
- [ ] **Step 3: Implement the minimum staged provider**
`LocalFilePublicationPolicy` validates a fixed destination ID, base directory, maximum rows,
maximum encoded bytes, and the only initial format profile `csv-rfc4180-v1`.
`LocalFilePublicationAdapter` must:
```text
validate request/schema before producer invocation
create a private .staging directory
exclusive-create an operation-scoped .part file
write header and each row directly through StreamingCsvEncoder
enforce schema/cell/row/byte limits at each sink call
prefix dangerous spreadsheet text with a single quote when policy is MITIGATE
compute SHA-256 and counts while writing
flush and FileChannel.force(true)
move staging to the final operation-scoped file with ATOMIC_MOVE
delete staging on pre-publish failure
return an opaque reference and never an absolute path
```
The first release is labelled local R1. Existing final artifacts cause a typed conflict; durable
operation journals, crash reconciliation, replace semantics, and SFTP/NFS remain unimplemented and
must not be advertised.
- [ ] **Step 4: Verify GREEN**
Run: `cd src && ./gradlew :adapter:outbound:fileserver:test --tests '*LocalFilePublicationAdapterTest' --console=plain`
Expected: PASS.
### Task 3: Add opt-in R1 composition and truthful documentation
**Files:**
- Modify: `src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportProperties.java`
- Modify: `src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java`
- Create: `src/adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/FilePublicationConfigTest.java`
- Modify: `src/adapter/outbound/fileserver/README.md`
- Modify: `src/adapter/outbound/fileserver/CLAUDE.md`
- [ ] **Step 1: Write the failing composition test**
```java
@Test
void disabledConfigurationCreatesNoPublicationPort() {
contextRunner
.withUserConfiguration(FileExportConfig.class)
.run(context -> assertThat(context).doesNotHaveBean(FilePublicationPort.class));
}
@Test
void enabledConfigurationCreatesExactlyOneLocalR1PublicationPort() {
contextRunner
.withUserConfiguration(FileExportConfig.class)
.withPropertyValues(
"ca-skeleton.fileserver.enabled=true",
"ca-skeleton.fileserver.destination-id=local-export",
"ca-skeleton.fileserver.base-directory=" + tempDir)
.run(context -> assertThat(context).hasSingleBean(FilePublicationPort.class));
}
```
- [ ] **Step 2: Verify RED**
Run: `cd src && ./gradlew :adapter:outbound:fileserver:test --tests '*FilePublicationConfigTest' --console=plain`
Expected: FAIL because the new port is not composed.
- [ ] **Step 3: Wire only the local R1 provider**
Add validated destination ID, row limit, byte limit, and format-profile settings. Contribute
`FilePublicationPort` only when explicitly enabled. Keep `FileExportPort` as a deprecated compatibility
bean and document that it materializes caller rows and is not R2 evidence.
- [ ] **Step 4: Verify module and architecture gates**
Run:
```bash
cd src
./gradlew :application-core:test :adapter:outbound:fileserver:check --console=plain
./gradlew verifyCleanArchitectureDependencies --console=plain
./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest' --console=plain
```
Expected: all commands PASS.
### Task 4: Record the unfinished R2 boundary
**Files:**
- Modify: `docs/superpowers/specs/2026-07-26-fileserver-production-capability-design.md`
- [ ] **Step 1: Update implementation status without weakening completion criteria**
Record Phase 01/local R1 foundation as implemented. Keep Phase 2 durable journal/reconciliation,
Phase 3 operations, Phase 4 SFTP, Phase 5 NFS/HA/bootstrap, and Phase 6 optional operations marked
unimplemented. The document must still say that local R1 is not Fileserver R2.
- [ ] **Step 2: Verify documentation structure**
Run: `rg -n 'R1|R2|구현 상태|미구현' docs/superpowers/specs/2026-07-26-fileserver-production-capability-design.md`
Expected: explicit R1 implementation and remaining R2 gaps are both present.
@@ -0,0 +1,79 @@
# HTTP Client Production Capability Foundation Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development
> (recommended) or superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Establish the framework-free call-budget and typed operation/target foundation, then close
two proven safety defects in the legacy JDK provider without claiming Apache HC5, hard total
deadline, egress security, or R2 readiness.
**Architecture:** `application-core` owns only a monotonic `CallBudget`. Product forks continue to
own feature-specific semantic ports. `adapter:outbound:httpclient` owns destination/operation IDs,
immutable operation descriptors, relative target construction, status/retry/body semantics, and
legacy provider fixes. The generic `OutboundHttpClient` remains a migration facade.
**Scope boundary:** This applies Phase 0 and a bounded Phase 1 foundation. Canonical binding
composition, exact readiness tuple registry, Apache HC5 pool, active cancellation, TLS/DNS/proxy,
auth, codec, and real-network qualification remain unimplemented.
---
### Task 1: Add a framework-free monotonic call budget
**Files:**
- Create: `src/application-core/src/main/java/dev/caskeleton/application/outbound/CallBudget.java`
- Test: `src/application-core/src/test/java/dev/caskeleton/application/outbound/CallBudgetTest.java`
- [ ] Write RED tests for expiry, remaining time, finite bounds, and parent/child intersection.
- [ ] Implement without Spring, wall-clock timestamps, scheduler, or HTTP types.
- [ ] Verify GREEN.
### Task 2: Add typed operation catalog and safe target construction
**Files:**
- Modify: `src/adapter/outbound/httpclient/build.gradle`
- Create: `src/adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/operation/HttpDestinationId.java`
- Create: `src/adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/operation/HttpOperationId.java`
- Create: `src/adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/operation/HttpOperationDescriptor.java`
- Create: `src/adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/operation/HttpOperationCatalog.java`
- Create: `src/adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/target/FixedHttpDestination.java`
- Create: `src/adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/target/HttpTargetBuilder.java`
- Test: `src/adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/operation/HttpOperationCatalogTest.java`
- Test: `src/adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/target/HttpTargetBuilderTest.java`
- [ ] Write RED tests for ID/uniqueness/cross-field operation invariants.
- [ ] Write RED tests rejecting absolute, scheme-relative, traversal, user-info, query/fragment, and
multi-segment variables.
- [ ] Implement closed immutable descriptors and one-pass path-segment encoding.
- [ ] Verify GREEN.
### Task 3: Correct characterized legacy provider safety defects
**Files:**
- Modify: `src/adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/OutboundHttpClient.java`
- Modify: `src/adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/OutboundHttpRestClientFactory.java`
- Test: `src/adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/OutboundHttpClientSafetyRegressionTest.java`
- [ ] Reproduce streaming 5xx body delivery and logical-call-only circuit-breaker counting.
- [ ] Make streaming validate status before exposing the body and discard error bodies.
- [ ] Put circuit breaker around each physical attempt and retry around the attempt loop.
- [ ] Set JDK redirects to `NEVER` explicitly and validate legacy base URI/relative request targets.
- [ ] Verify focused regressions and the full legacy test suite.
### Task 4: Record exact readiness and verify
**Files:**
- Modify: `src/adapter/outbound/httpclient/README.md`
- Modify: `src/adapter/outbound/httpclient/CLAUDE.md`
- Modify: `docs/superpowers/specs/2026-07-27-httpclient-production-capability-design.md`
- [ ] Mark the implemented foundation and fixed legacy defects.
- [ ] Keep total deadline/cancellation, canonical zero-binding composition, Apache pool, fixed
egress, TLS/auth, bounded decoded streaming, and R2 cards unimplemented.
- [ ] Run:
```bash
cd src
./gradlew :application-core:check :adapter:outbound:httpclient:check --console=plain
./gradlew verifyCleanArchitectureDependencies --console=plain
```
@@ -0,0 +1,59 @@
# HTTP Client Total Deadline 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:** Enforce `CallBudget` across the legacy HTTP logical call, including retry wait and blocking
I/O, and cancel the executing task when the absolute monotonic deadline wins.
**Architecture:** Preserve the current migration facade but inject a bounded executor owned by each
client. Every call intersects the caller budget with the configured maximum, passes the same
absolute deadline to retry policy, waits through `Future.get(remaining)`, and cancels on timeout or
shutdown. This is R1 cancellation evidence, not Apache pool or hard-wire-cancellation R2 evidence.
**Tech Stack:** Java 21 virtual-thread executor, Spring RestClient/JDK HttpClient, Resilience4j,
JUnit loopback HTTP server.
---
### Task 1: Add deadline execution and explicit timeout vocabulary
**Files:**
- Create: `src/adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/OutboundCallDeadlineExceededException.java`
- Create: `src/adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/OutboundCallExecutor.java`
- Test: `src/adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/OutboundCallExecutorTest.java`
- [x] Write failing tests proving an expired budget does not start work, a running task is
interrupted on expiry, and completion wins before the deadline.
- [x] Confirm RED.
- [x] Implement absolute monotonic remaining-time calculation, `Future.get`, cancellation and
exact exception mapping.
- [x] Confirm GREEN.
### Task 2: Connect the budget to buffered and streaming calls
**Files:**
- Modify: `src/adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/OutboundHttpClient.java`
- Modify: `src/adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/OutboundRetryPolicy.java`
- Test: `src/adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/OutboundHttpClientDeadlineTest.java`
- [x] Write a failing loopback test where response delay exceeds the budget and confirm bounded
return; record that JDK-provider server-side hard close is not proven by this lane.
- [x] Write a failing test proving a shorter caller budget wins and retry cannot start after expiry.
- [x] Confirm RED.
- [x] Add overloads accepting `CallBudget`; existing methods create a configured maximum budget.
Intersect budgets once and use the same deadline for retry and blocking execution.
- [x] Confirm GREEN and run the complete HTTP leaf tests.
### Task 3: Record provider limits and verify
**Files:**
- Modify: `src/adapter/outbound/httpclient/README.md`
- Modify: `src/adapter/outbound/httpclient/CLAUDE.md`
- Modify: `docs/superpowers/specs/2026-07-27-httpclient-production-capability-design.md`
- [x] Record active logical-call deadline/cancellation as implemented.
- [x] Keep explicit pool lease, Apache exact provider, DNS rebinding, TLS/auth/proxy and R2 hard
cancellation evidence unimplemented.
- [x] Run the HTTP leaf check and architecture/public-path gates.
@@ -0,0 +1,93 @@
# Redis Production Capability Foundation Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development
> (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use
> checkbox syntax for tracking.
**Goal:** Replace the adapter-only cache seam with a framework-free semantic cache contract, safe
physical key construction, and a versioned typed atomic-program foundation without claiming that a
real Redis runtime or any R2 capability is complete.
**Architecture:** `application-core` owns provider-neutral cache outcomes and mutation intent.
`adapter:outbound:cache-redis` owns physical key construction, digesting, Lua resources, program
descriptors, and typed primitive facades. Existing legacy routing remains compatible while migration
is incremental. No Redis SDK, raw command, raw key, or Lua concept crosses into core.
**Scope boundary:** This batch implements Phase 0 and selected Phase 1 foundations. Spring Data
Redis/Lettuce runtime, codec/envelope, real-service integration, topology, distributed rate limit,
idempotency, lease, session, and R2/R3 evidence remain separate implementation phases.
---
### Task 1: Add the provider-neutral cache contract
**Files:**
- Create: `src/application-core/src/main/java/dev/caskeleton/application/cache/CacheRegionPort.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/cache/CacheLookup.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/cache/CacheRecordMetadata.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/cache/CacheRecordIntent.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/cache/CacheRecordOutcome.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/cache/CacheInvalidationOutcome.java`
- Create: `src/application-core/src/main/java/dev/caskeleton/application/cache/AuthoritativeAbsence.java`
- Test: `src/application-core/src/test/java/dev/caskeleton/application/cache/CacheRegionContractTest.java`
- [ ] Write a failing test for hit/negative/miss/unavailable distinctions and immutable metadata.
- [ ] Verify RED with `./gradlew :application-core:test --tests '*CacheRegionContractTest'`.
- [ ] Implement only framework-free values and ports.
- [ ] Verify GREEN.
### Task 2: Add canonical Redis physical keys
**Files:**
- Modify: `src/adapter/outbound/cache-redis/build.gradle`
- Create: `src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/key/RedisKeyNamespace.java`
- Create: `src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/key/RedisKeyBuilder.java`
- Create: `src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/key/RedisKeyDigest.java`
- Test: `src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/key/RedisKeyBuilderTest.java`
- [ ] Write a failing test proving namespace isolation, one stable hash tag, bounded key bytes, and
absence of raw sensitive resource identifiers.
- [ ] Verify RED.
- [ ] Implement SHA-256 for opaque IDs and HMAC-SHA-256 for sensitive scopes using defensive secret
copies and length-prefixed component encoding.
- [ ] Verify GREEN.
### Task 3: Add a typed, versioned atomic-program catalog
**Files:**
- Create: `src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/program/RedisProgramId.java`
- Create: `src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/program/RedisProgramDescriptor.java`
- Create: `src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/program/RedisProgramCatalog.java`
- Create: `src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/program/RedisProgramExecutor.java`
- Create: `src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/program/RedisAtomicPrimitives.java`
- Create: `src/adapter/outbound/cache-redis/src/main/resources/redis/scripts/compare-and-delete-v1.lua`
- Create: `src/adapter/outbound/cache-redis/src/main/resources/redis/scripts/compare-and-expire-v1.lua`
- Create: `src/adapter/outbound/cache-redis/src/main/resources/redis/scripts/set-if-absent-with-ttl-v1.lua`
- Test: `src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/program/RedisProgramCatalogTest.java`
- Test: `src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/program/RedisAtomicPrimitivesTest.java`
- [ ] Write failing catalog and facade tests.
- [ ] Verify RED.
- [ ] Implement exact resource digest, key/argument bounds, typed status mapping, and no generic
application-facing execution surface.
- [ ] Verify GREEN.
### Task 4: Record exact readiness and verify
**Files:**
- Modify: `src/adapter/outbound/cache-redis/README.md`
- Modify: `src/adapter/outbound/cache-redis/CLAUDE.md`
- Modify: `docs/superpowers/specs/2026-07-26-redis-production-capability-design.md`
- [ ] Mark only contract/key/program foundation as implemented and all real runtime/capability
promotion as unimplemented.
- [ ] Run:
```bash
cd src
./gradlew :application-core:check :adapter:outbound:cache-redis:check --console=plain
./gradlew verifyCleanArchitectureDependencies --console=plain
```
- [ ] Do not claim Redis cache R1/R2 until a real standalone service lane and codec/runtime evidence
exist.
@@ -0,0 +1,64 @@
# Redis Runtime And Semantic Cache 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:** Replace the SDK-less Redis seam with an opt-in managed Lettuce runtime, a real Lua
executor, and a bounded semantic string-cache implementation.
**Architecture:** A package-private runtime owns `RedisClient`, connection and synchronous binary
commands. The Lua executor uses the compiled catalog checksum and `EVALSHA`, falling back to `EVAL`
only for `NOSCRIPT`. A versioned binary envelope distinguishes positive, negative and incompatible
entries behind `CacheRegionPort<String,String>`.
**Tech Stack:** Java 21, Lettuce Core managed by Spring Boot 4 BOM, Spring Boot configuration
properties, JUnit 5, optional Docker-backed Redis qualification.
---
### Task 1: Add the managed runtime and typed program execution
**Files:**
- Modify: `src/adapter/outbound/cache-redis/build.gradle`
- Create: `src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/RedisRuntimeSettings.java`
- Create: `src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/LettuceRedisRuntime.java`
- Modify: `src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/RedisCacheAdapterConfig.java`
- Create: `src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/LettuceRedisRuntimeTest.java`
- [x] Write failing tests for URI/timeout validation, lifecycle close, binary get/set/delete and
`EVALSHA -> NOSCRIPT -> EVAL`.
- [x] Confirm RED before adding the Lettuce production dependency.
- [x] Add `io.lettuce:lettuce-core` using the Boot BOM and update the affected dependency locks.
- [x] Implement a package-private runtime with finite command/shutdown timeouts, bounded reconnect
behavior, and no connection side effects while disabled or in external-client mode.
- [x] Verify focused tests GREEN.
### Task 2: Implement the semantic cache region
**Files:**
- Create: `src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/RedisCacheRegionPolicy.java`
- Create: `src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/RedisCacheEnvelopeCodec.java`
- Create: `src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/RedisStringCacheRegion.java`
- Test: `src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/RedisStringCacheRegionTest.java`
- [x] Write failing tests for hit, negative hit, miss, incompatible schema, positive/negative TTL,
invalidation and provider failure certainty.
- [x] Confirm RED.
- [x] Implement a bounded versioned binary envelope and HMAC-derived physical keys. Support UPSERT;
return `NOT_RECORDED_PROVIDER_POLICY` for opaque revision ordering the provider cannot prove.
- [x] Confirm GREEN and run the complete Redis leaf test suite.
### Task 3: Qualify and document without false promotion
**Files:**
- Modify: `src/adapter/outbound/cache-redis/README.md`
- Modify: `src/adapter/outbound/cache-redis/CLAUDE.md`
- Modify: `docs/superpowers/specs/2026-07-26-redis-production-capability-design.md`
- Modify: runtime configuration and env-key registry only for settings actually introduced.
- [x] If a local Redis image is available, run an explicit real-service program/cache test; never
silently skip it.
- [x] Mark standalone runtime/cache as R1 unless real service, restart, ACL/TLS and fault evidence
required by the readiness card all pass.
- [x] Run the leaf check, dependency lock check, env-key gate and architecture gate.