feat: redis, fileserver, httpclient 런타임 시점 구현 추가
This commit is contained in:
@@ -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`
|
||||
|
||||
+144
@@ -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 0–1/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.
|
||||
Reference in New Issue
Block a user