chore: record pre-existing uncommitted repository state
Snapshot of the in-flight state that already existed, identically, in both this worktree and the main checkout before this session began: the initial HTTP Client platform implementation (previously untracked), the redis-lab removal, and the JPA / object-storage / notification integration work. Kept separate from this session's HTTP Client review response, which lands in the following commit, so the two bodies of work stay reviewable apart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1a3b560678
commit
5f10b791d3
@@ -0,0 +1,160 @@
|
||||
# Release Hygiene Refactoring Design
|
||||
|
||||
**Date:** 2026-08-01
|
||||
**Status:** approved by the user's instruction to apply the preceding review
|
||||
**Scope:** release-blocking architecture test, Gradle wrapper supply-chain integrity, Docker build configuration inputs, SpotBugs analysis completeness, and the observed Gradle 10 deprecation
|
||||
|
||||
## Context
|
||||
|
||||
The repository-wide review found that the 19-leaf Clean Architecture dependency model is healthy,
|
||||
but the release surface is not green:
|
||||
|
||||
- `:app-bootstrap:sampleOffTest` fails because a whole-composition Object Storage ArchUnit rule is
|
||||
evaluated on the intentionally sample-free classpath with `allowEmptyShould(false)`.
|
||||
- the two Dockerfiles run Gradle before copying configuration-time registry inputs, while the root
|
||||
build also requires a Git checkout during configuration even though the Docker context excludes
|
||||
`.git`;
|
||||
- `gradle-wrapper.properties` selects Gradle 9.0.0 while the checked-in wrapper JAR is from another
|
||||
official Gradle release, and the distribution checksum is absent;
|
||||
- clean SpotBugs analysis reports missing Spring Session, Micrometer Context Propagation, and
|
||||
protobuf classes;
|
||||
- a root task calls `Task.project` during execution, which is deprecated and scheduled to fail in
|
||||
Gradle 10.
|
||||
|
||||
This design deliberately closes those release-hygiene defects before changing idempotency, outbox,
|
||||
security, or sample data behavior. Each later subsystem gets a separate design and plan so that a
|
||||
reviewer can accept or revert it independently.
|
||||
|
||||
## Considered Approaches
|
||||
|
||||
### Approach A: weaken the existing global gates
|
||||
|
||||
Set ArchUnit rules to allow empty matches, ignore SpotBugs missing-class messages, and make Docker
|
||||
configuration registries optional. This is the smallest diff, but it makes the architecture and
|
||||
static-analysis gates less trustworthy. Rejected.
|
||||
|
||||
### Approach B: patch each symptom in place
|
||||
|
||||
Condition the ArchUnit rule on a sample flag, copy only the two currently missing registry files,
|
||||
and add the three currently missing SpotBugs JARs manually. This would pass today's cases but would
|
||||
recur whenever another leaf, registry, source set, or dependency is added. Rejected because it
|
||||
duplicates ownership knowledge.
|
||||
|
||||
### Approach C: align ownership and derive inputs from the owning model
|
||||
|
||||
Move the leaf-specific architecture rule to the Object Storage leaf, keep root tests responsible
|
||||
for cross-leaf registration, treat `config/**` as a declared Docker configuration input, move Git
|
||||
evidence checks to the evidence task execution phase, align the wrapper artifacts to one version,
|
||||
and derive SpotBugs auxiliary inputs from each analyzed source set's runtime classpath. Selected.
|
||||
|
||||
## Architecture Test Ownership
|
||||
|
||||
`adapter-outbound-objectstorage` owns rules about the public types of its production adapter methods.
|
||||
The rule moves out of `app-bootstrap` and runs in the Object Storage module's normal test suite.
|
||||
It remains strict: the Object Storage module must contain matching production classes and the rule
|
||||
must not globally allow an empty `should` clause.
|
||||
|
||||
`app-bootstrap` continues to own cross-module rules. Its sample-off suite verifies that production
|
||||
composition works without `sample-portfolio`; it does not require sample-only leaves to be present.
|
||||
The existing module registry and dependency verification remain the SSOT for leaf coverage.
|
||||
|
||||
## Gradle Wrapper Integrity
|
||||
|
||||
Gradle 9.0.0 remains the selected version for this refactoring. The wrapper scripts, properties, and
|
||||
JAR are regenerated from Gradle 9.0.0 in a trusted environment. The official 9.0.0 binary
|
||||
distribution SHA-256 is recorded as:
|
||||
|
||||
```text
|
||||
8fad3d78296ca518113f3d29016617c7f9367dc005f932bd9d93bf45ba46072b
|
||||
```
|
||||
|
||||
The wrapper properties are one exact ordered eight-line byte contract, preventing Java Properties
|
||||
duplicate-key, separator, escape, and continuation semantics from overriding the reviewed values.
|
||||
The complete six-file workflow path set and every workflow's SHA-256 are embedded as a reviewed
|
||||
byte lock in the verifier. This is the primary completeness boundary: YAML has aliases, encoded
|
||||
keys, duplicate-key overrides, custom shells, and other equivalent representations that a partial
|
||||
Bash parser cannot safely model. Any workflow addition, removal, rename, symlink replacement, or
|
||||
byte change fails until the complete workflow diff is intentionally reviewed and the sorted lock
|
||||
is refreshed in the same change.
|
||||
|
||||
The restricted block-style workflow grammar remains defense in depth and supplies actionable
|
||||
diagnostics for ordinary drift. Every Gradle-running job uses an unconditional validation step
|
||||
with a stable ID and the action pinned by commit SHA. Checkout and validation precede every Gradle
|
||||
invocation, not only the first; a cleanup/sanitizer step that intentionally uses `always()` also
|
||||
requires the validation step's successful outcome. This is consistent with the repository's
|
||||
existing pinned `actions/setup-java` policy and prevents wrapper failure from being bypassed by
|
||||
step conditions.
|
||||
|
||||
## Docker Configuration Contract
|
||||
|
||||
Both Docker build dependency-cache stages preserve the repository layout with `WORKDIR /build/src`
|
||||
and copy the complete `config/**` tree before invoking Gradle. The parent `/build` is therefore the
|
||||
repository root expected by registry `source_path: src/**` entries. This is intentional: Gradle
|
||||
configuration registries and their repository-relative path base are build inputs, while the
|
||||
registry's exact internal file list may evolve.
|
||||
|
||||
Git revision validation no longer runs unconditionally while the build script is being configured.
|
||||
A root-owned resolver is invoked once from each root evidence action or leaf evidence test's
|
||||
root-suite completion action; eager scalar evidence properties are removed. Only evidence-producing
|
||||
tasks resolve the checkout revision during their execution. Docker builds provide
|
||||
`-PgitRevision=<40 lowercase hex>` and do not copy `.git` into the image context.
|
||||
|
||||
The boot JAR path is obtained from Gradle's archive output contract rather than selecting the first
|
||||
filesystem match. The final images retain the existing digest-pinned base image, non-root user,
|
||||
read-only root filesystem, and JRE-only runtime.
|
||||
|
||||
## SpotBugs and Gradle 10 Compatibility
|
||||
|
||||
Every SpotBugs task analyzes a named source set and receives that source set's runtime classpath as
|
||||
its auxiliary analysis classpath, excluding its own compiled output. Custom test source sets are
|
||||
covered by the same rule. No production dependency scope is widened merely to silence SpotBugs.
|
||||
|
||||
Missing-analysis-class output is treated as a gate failure. The clean gate must produce zero
|
||||
`classes needed for analysis were missing` messages.
|
||||
|
||||
The observed Gradle 10 deprecation is removed by capturing the application-core project during
|
||||
configuration instead of calling `Task.project` from the task action. The dependency-purity gate
|
||||
still traverses that project's configurations during execution, so it explicitly opts out of the
|
||||
configuration cache rather than claiming serializable declared inputs it does not have.
|
||||
|
||||
## Error Handling and Failure Semantics
|
||||
|
||||
- sample-off fails only for a real production composition or architecture violation;
|
||||
- an empty Object Storage rule in its owning module is a test failure;
|
||||
- a wrapper JAR or distribution checksum mismatch fails before Gradle build logic executes in CI;
|
||||
- missing Docker configuration input fails with a named build-contract test rather than an opaque
|
||||
settings error;
|
||||
- invalid or absent `gitRevision` fails only an evidence task that requires it;
|
||||
- SpotBugs missing classes fail static analysis instead of producing a successful partial report.
|
||||
|
||||
## Verification Design
|
||||
|
||||
The implementation follows red-green-refactor. Each behavior has a regression test or executable
|
||||
contract that fails before the production/configuration change:
|
||||
|
||||
1. reproduce `sampleOffTest` failure, then add an owner-module architecture test and remove the
|
||||
misplaced global rule;
|
||||
2. add wrapper property and workflow contract assertions before regenerating the wrapper;
|
||||
3. extend Docker contract tests so a cache-stage Gradle configuration fixture requires `config/**`
|
||||
and accepts an attested `gitRevision` without `.git`;
|
||||
4. add Gradle build-contract coverage for source-set-derived SpotBugs auxiliary classpaths, the
|
||||
removed execution-time `Task.project` access, and the explicit configuration-cache opt-out;
|
||||
5. run focused gates, then the clean repository-wide gate and gate-matrix script.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- no dependency version upgrade beyond aligning the wrapper to the already selected Gradle 9.0.0;
|
||||
- no business/domain behavior changes;
|
||||
- no idempotency, outbox, Poster publication, security, DTO, or database migration changes;
|
||||
- no broad extraction of the 3,768-line root build script in this phase;
|
||||
- no agent-created branch, stage, commit, amend, or push.
|
||||
|
||||
## Decision Summary
|
||||
|
||||
- Object Storage-specific ArchUnit rules live with Object Storage.
|
||||
- Root architecture rules remain strict and cross-module only.
|
||||
- Gradle stays at 9.0.0 and gains exact wrapper/distribution validation.
|
||||
- Docker copies `config/**`; Git evidence is execution-scoped and supplied by `gitRevision`.
|
||||
- SpotBugs uses source-set runtime classpaths and fails on missing analysis classes.
|
||||
- The dependency-purity task avoids execution-time `Task.project` access and truthfully declares
|
||||
its configuration-cache incompatibility while it still inspects project configurations.
|
||||
@@ -0,0 +1,74 @@
|
||||
# Client-Safe Error Boundary Design
|
||||
|
||||
**Date:** 2026-08-02
|
||||
**Status:** approved by the user's instruction to apply the detailed P1/P2 review sequentially
|
||||
**Scope:** HTTP error envelopes in `adapter:inbound:web` and the `sample-portfolio` domain advice
|
||||
|
||||
## Context
|
||||
|
||||
Several handlers pass `Exception#getMessage()`, rejected request values, or a raw request URL into
|
||||
the public error envelope. Those values are not a stable API contract and can contain identifiers,
|
||||
tokens, uploaded values, configuration details, or internal diagnostics. Persistence and outbound
|
||||
dependency failures already use fixed client-safe messages; the rest of the HTTP boundary must
|
||||
follow the same rule.
|
||||
|
||||
## Decision
|
||||
|
||||
The inbound adapter owns a message allowlist keyed by stable error code. Handlers may expose only:
|
||||
|
||||
- stable `code`, `category`, HTTP status, and `retryable` from `ApiErrorCode`;
|
||||
- fixed, code-specific client messages;
|
||||
- bounded structural details such as field name, validation reason code, expected Java type,
|
||||
supported HTTP methods, or supported media types.
|
||||
|
||||
They must not expose exception messages, rejected values, raw request URLs, adapter/configuration
|
||||
diagnostics, opaque cursors, authentication diagnostics, resource identifiers, or duplicate domain
|
||||
values. Bean Validation interpolated/default messages are also discarded because custom templates
|
||||
can include the validated value. Validation details contain only normalized server-owned property
|
||||
names plus allowlisted reason codes and fixed messages; collection/map keys and indices are removed.
|
||||
|
||||
`ClientSafeErrorMessages` is extended for skeleton-wide operational codes. The sample keeps its
|
||||
domain wording in a separate package-private `PortfolioClientSafeErrorMessages`, preserving the
|
||||
rule that production modules do not know sample business concepts.
|
||||
|
||||
## Public Messages
|
||||
|
||||
Representative mappings are fixed as follows:
|
||||
|
||||
- `MAPPING_FAILED` → `Request data could not be mapped`;
|
||||
- `BAD_PARAMETER` → `Request parameter is invalid`;
|
||||
- `INVALID_TOKEN` → `Authentication token is invalid`;
|
||||
- `UNAUTHENTICATED` → `Authentication is required`;
|
||||
- authorization denials → `Access is denied`;
|
||||
- `PRECONDITION_FAILED` → `Resource state changed; refresh and retry`;
|
||||
- page/cursor failures → generic corrective text, with safe field/reason details retained;
|
||||
- `ADAPTER_DISABLED` and internal classifications → `Internal server error`;
|
||||
- domain not-found/conflict/invariant codes → fixed noun-level text with no ID/title value.
|
||||
|
||||
Transport overrides use fixed wording and retain only safe protocol metadata. For example, 405
|
||||
still emits `Allow`, while both controller-route (`NoHandlerFoundException`) and static-resource
|
||||
(`NoResourceFoundException`) 404s use the same envelope without echoing the request URL.
|
||||
|
||||
## Testing
|
||||
|
||||
Tests inject conspicuous secret sentinels into exception messages, rejected values, URLs, tokens,
|
||||
IDs, and duplicate titles. Every resulting response must preserve its status/code/category while
|
||||
excluding the sentinel from both `error.message` and `error.details`.
|
||||
|
||||
Validation tests additionally place sentinels in interpolated/default messages and iterable
|
||||
keys/indices. A real MockMvc resource-resolution request verifies the Spring 7
|
||||
`NoResourceFoundException` path rather than calling the advice method directly.
|
||||
|
||||
The focused module suites remain the primary verification:
|
||||
|
||||
- `:adapter:inbound:web:test` for operational and transport handlers;
|
||||
- `:sample-portfolio:test` for domain advice and sample wire behavior;
|
||||
- `verifyCleanArchitectureDependencies` for dependency direction.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- no change to error codes, categories, statuses, or retryability;
|
||||
- no suppression of server-side logs or tracing in this batch;
|
||||
- no application/domain dependency on HTTP response types;
|
||||
- no generic exception-message sanitizer based on regexes or truncation;
|
||||
- no staging, commit, amend, or push by an agent.
|
||||
@@ -0,0 +1,118 @@
|
||||
# Conditional Inbound Transport Boundary Design
|
||||
|
||||
**Date:** 2026-08-02
|
||||
**Status:** approved by the user's instruction to apply the detailed P1/P2 review sequentially
|
||||
**Scope:** the opt-in GraphQL, gRPC, and WebSocket leaf modules and their release evidence
|
||||
|
||||
## Context
|
||||
|
||||
The three leaves are registered and tested independently, but neither `app-bootstrap` nor
|
||||
`sample-portfolio` has a production dependency on them. That omission is intentional: adding a
|
||||
classpath edge today would activate GraphQL, start a plaintext/reflection-enabled gRPC server by
|
||||
default, and unconditionally expose a wildcard-origin STOMP broker that serializes arbitrary domain
|
||||
events. The leaf documentation nevertheless describes sample contributions that do not exist, and
|
||||
the ordinary root `check` can become `NO-SOURCE` without a transport-specific positive-count and
|
||||
zero-skip qualification gate.
|
||||
|
||||
P1 therefore makes opt-in status executable and makes accidental activation fail closed. It does
|
||||
not add these leaves to the default runtime or claim the P2 production baselines.
|
||||
|
||||
## Runtime Membership SSOT
|
||||
|
||||
Every entry in `config/architecture/modules.json` gains an exact `runtime_memberships` array whose
|
||||
values are limited to the two composition roots: `app-bootstrap` and `sample-portfolio`.
|
||||
|
||||
- A composition root includes itself in its membership.
|
||||
- Direct production `api`/`implementation`/`compileOnly`/`runtimeOnly` project dependencies must
|
||||
equal the registry members for that root, excluding the root itself.
|
||||
- An empty array means the leaf is built and architecture-checked but absent from both shipped
|
||||
runtime graphs. GraphQL, gRPC, WebSocket, and Mongo remain in this state.
|
||||
- Test fixtures and custom qualification configurations do not change production membership.
|
||||
|
||||
Settings validation is fail-closed for missing, duplicate, or unknown membership names. A Gradle
|
||||
verification task compares the registry to both composition roots and is part of `check`.
|
||||
|
||||
## Explicit Qualification Composition
|
||||
|
||||
`app-bootstrap` owns a `conditionalTransportTest` source set whose classpath explicitly includes
|
||||
the three opt-in leaves. It proves that the opt-in artifacts resolve together while the registry
|
||||
still declares them absent from both default runtime graphs. It is evidence composition, not a new
|
||||
production dependency edge.
|
||||
|
||||
The root registers exact qualification `Test` tasks for GraphQL, gRPC, and WebSocket. Each task:
|
||||
|
||||
- names required test classes rather than broad discovery;
|
||||
- fails on no match or no discovery;
|
||||
- always reruns in UTC;
|
||||
- fails if the root suite reports any skipped test.
|
||||
|
||||
An aggregate `conditionalTransportQualification` task depends on the composition contract and all
|
||||
three exact lanes. CI invokes it explicitly from the existing release-blocking quality job, and the
|
||||
gate matrix records the task.
|
||||
|
||||
## gRPC P1 Boundary
|
||||
|
||||
gRPC activation becomes explicit and local-only until a later TLS/mTLS design exists:
|
||||
|
||||
- `enabled=false` and `reflectionEnabled=false` are defaults; missing properties create no runner,
|
||||
health manager, reflection service, or listener.
|
||||
- The current insecure credential mode requires an explicit local-development override and a
|
||||
loopback bind address. Non-loopback insecure bind fails startup.
|
||||
- Feature services require a caller-supplied authentication policy/interceptor. Missing or invalid
|
||||
metadata returns stable `UNAUTHENTICATED`; valid metadata reaches the service.
|
||||
- Health remains a local lifecycle probe; reflection is a separate explicit flag.
|
||||
- The error interceptor wraps `ServerCall.close`, so handler throws, listener throws, ordinary
|
||||
`responseObserver.onError`, and raw `StatusRuntimeException` all pass the same sanitizer.
|
||||
Recognized `ApiErrorCarrier` causes produce stable code/category trailers; unrecognized status
|
||||
descriptions become fixed `INTERNAL_ERROR` with no raw diagnostic.
|
||||
|
||||
A real ephemeral Netty unary service verifies authentication, reflection-off, all error paths, and
|
||||
sentinel redaction. TLS/mTLS, external bind, deadlines, streaming, and protobuf compatibility are
|
||||
P2 and remain unclaimed.
|
||||
|
||||
## GraphQL P1 Boundary
|
||||
|
||||
GraphQL remains classpath-selected: its absence from the default runtime is the disable mechanism,
|
||||
and the qualification classpath is the explicit opt-in mechanism. The wire lane starts a real
|
||||
random-port MVC server and crosses HTTP JSON, Spring Security, and CORS.
|
||||
|
||||
It verifies unauthenticated rejection, authenticated health success, allowed/disallowed origins,
|
||||
GraphiQL disabled, production-style introspection disabled, stable carrier errors, unknown errors,
|
||||
and absence of distinct secret sentinels from the complete response body. The existing resolver is
|
||||
changed only if a failing wire contract proves unsafe behavior.
|
||||
|
||||
Feature schema/resolvers, field authorization, depth/cost, persisted queries, DataLoader, schema
|
||||
compatibility, and subscriptions remain P2.
|
||||
|
||||
## WebSocket P1 Boundary
|
||||
|
||||
WebSocket gains `ca-skeleton.websocket.enabled=false`; both configuration and broadcaster are
|
||||
conditional. Enabled settings reject wildcard/blank origins and invalid endpoint/destination
|
||||
shapes.
|
||||
|
||||
The inbound channel requires an authenticated handshake principal, permits subscription only to
|
||||
the configured server topic, permits authenticated application sends under `/app/**`, and rejects
|
||||
client sends to `/topic/**`. A custom STOMP error handler emits only a fixed client-safe code.
|
||||
|
||||
The broadcaster no longer serializes arbitrary `@DomainEvent` objects. It consults an explicit
|
||||
projection allowlist; an event without exactly one projection is not sent. Projection output is a
|
||||
bounded primitive map, not the domain object graph.
|
||||
|
||||
A real random-port WebSocket/STOMP lane verifies disabled absence, origin/auth/connect/subscribe,
|
||||
server push, broker-send rejection, error redaction, and no projection/no broadcast. The simple
|
||||
broker remains local/R1 only; broker relay, cross-node durability, replay, backpressure, and a
|
||||
domain-specific versioned projection catalog remain P2.
|
||||
|
||||
## Documentation Truthfulness
|
||||
|
||||
Leaf READMEs and CLAUDE files describe only code that exists. Sample GraphQL schemas, gRPC services,
|
||||
and WebSocket publishers are future adoption examples, not current runtime features. Each document
|
||||
states the activation switch, exact P1 evidence, and unimplemented P2 limits.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- adding any of the three leaves to a shipped default runtime;
|
||||
- adding a production project dependency edge outside the registry;
|
||||
- claiming production readiness from local loopback/simple-broker tests;
|
||||
- implementing sample feature APIs or domain payloads;
|
||||
- staging, committing, amending, or pushing changes.
|
||||
@@ -0,0 +1,79 @@
|
||||
# P2 Verification Governance Refactoring Design
|
||||
|
||||
## Goal
|
||||
|
||||
Remove the remaining fail-open verification paths without changing production behavior or adding
|
||||
unadopted runtime capabilities. P2 strengthens qualification tasks, tracked contract resources,
|
||||
CI parser evidence, JSON Schema conformance, registry ownership, and bounded documentation debt.
|
||||
|
||||
## Scope and sequence
|
||||
|
||||
1. Move strict qualification `Test` registration to each owner leaf through one shared convention.
|
||||
2. Resolve tracked repository contract resources from an explicit repository root and fail when
|
||||
tracked files or directories are absent.
|
||||
3. Exercise the real gate-matrix shell validator through isolated mutation fixtures.
|
||||
4. Validate every Redis program manifest with the committed Draft 2020-12 schema.
|
||||
5. Make the tracked registry set explicit, resolve every `required_test` identifier, and govern
|
||||
temporary runbook stubs with owners and expiry dates.
|
||||
6. Apply bounded P2 cleanup: module-doc link coverage, migration-neutral gate labels, and
|
||||
deterministic outbound HTTP timeout tests.
|
||||
|
||||
Each item is independently reviewable. A later item may reuse infrastructure from an earlier item,
|
||||
but no batch may weaken an existing check while waiting for a subsequent batch.
|
||||
|
||||
## Qualification convention
|
||||
|
||||
The owner project applies `gradle/strict-qualification-test.gradle` and registers its own exact
|
||||
qualification tasks. The root project only aggregates absolute task paths and validates resulting
|
||||
JUnit XML.
|
||||
|
||||
Every strict qualification task must:
|
||||
|
||||
- name at least one required FQCN;
|
||||
- depend on compilation and fail before test execution when any required class file is absent;
|
||||
- use exact JUnit filters with no-match and no-discovery failures enabled;
|
||||
- force fresh execution in UTC and emit JUnit XML;
|
||||
- reject skipped tests and require a positive, failure-free XML count.
|
||||
|
||||
This applies to conditional transports, Messaging evidence lanes, object-storage release lanes,
|
||||
the Poster migration lane, and the app-bootstrap conditional-composition proof. Ordinary optional
|
||||
or quarantine tests are deliberately excluded.
|
||||
|
||||
## Repository contract resources
|
||||
|
||||
`app-bootstrap` injects `ca.repository.root` into contract tests. A package-private resolver
|
||||
normalizes the root, rejects traversal, and exposes `requireTrackedFile` and
|
||||
`requireTrackedDirectory`. Missing tracked resources are assertion failures, never assumptions.
|
||||
Assumptions remain valid only for truly optional external infrastructure.
|
||||
|
||||
## CI parser evidence
|
||||
|
||||
The gate-matrix validator accepts an optional repository-root argument. Contract tests construct a
|
||||
minimal temporary repository fixture and invoke the actual shell script. Mutations for deceptive
|
||||
step names, execution-suppressing flags, missing or duplicated gates, and unregistered tasks must
|
||||
produce non-zero exits with stable diagnostics. Java must not contain a second parser.
|
||||
|
||||
## Schema and registry governance
|
||||
|
||||
- Redis manifests are validated by a Draft 2020-12 implementation in addition to existing catalog
|
||||
cross-checks.
|
||||
- A registry catalog has an exact one-to-one relationship with tracked `docs/registries/*.yaml`.
|
||||
- Stable `required_test` IDs resolve through a tracked catalog to a single owner Gradle path and
|
||||
source test/method. Unknown, duplicate, and dangling mappings fail.
|
||||
- Temporary runbook stubs are listed in tracked debt data with owner, issue, start, and sunset.
|
||||
Missing or expired debt entries fail.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- No GraphQL feature schema, cost/depth policy, gRPC TLS/streaming, WebSocket relay, or other
|
||||
production capability is introduced.
|
||||
- No lockfile consolidation, version-catalog migration, JVM test-suite migration, or broad module
|
||||
boundary change is included.
|
||||
- Root Gradle capability extraction and a typed settings/build registry model remain separate
|
||||
refactors unless their benefit can be proven without expanding this verification change.
|
||||
|
||||
## Verification
|
||||
|
||||
Each batch starts with a focused failing contract and finishes with its owner `check`. Final
|
||||
verification runs root `test`, `check`, architecture/dependency/runtime membership gates, CI shell
|
||||
validators, dependency locks, public-path/env gates, and `git diff --check`.
|
||||
@@ -0,0 +1,79 @@
|
||||
# Redis Session HTTP Boundary Design
|
||||
|
||||
**Date:** 2026-08-02
|
||||
**Status:** approved by the user's instruction to apply the reviewed P1/P2 work sequentially
|
||||
**Scope:** composition of inbound browser-session security with the outbound versioned Redis session repository
|
||||
|
||||
## Context
|
||||
|
||||
Inbound-web unit contracts prove CSRF, fixation, hardened cookie settings, and primitive security
|
||||
snapshot behavior with `MockHttpSession`/in-memory repositories. Cache-redis contracts prove the
|
||||
versioned session repository and Lua semantics against Redis. No test currently crosses the actual
|
||||
Spring Session filter, production SecurityFilterChain, real Redis, and a second application context.
|
||||
|
||||
Putting this test in inbound-web would require a forbidden dependency on the outbound Redis leaf.
|
||||
The composition root already depends on both leaves and owns the `redisCompositionTest` source set,
|
||||
so app-bootstrap is the correct boundary owner.
|
||||
|
||||
## Decision
|
||||
|
||||
Add a tagged `redis-session-http` integration contract under app-bootstrap's existing
|
||||
`redisCompositionTest` source set. Ordinary `redisCompositionTest` excludes the tag. A new explicit
|
||||
`redisSessionHttpIntegrationTest` task includes only that tag, fails on no discovery or any skip,
|
||||
always reruns, pins UTC, and passes the checked-in Redis image registry path.
|
||||
|
||||
The task is deliberately not attached to ordinary local `check`, because it requires Docker. It is
|
||||
added to the existing release-blocking `redis-standalone` CI job, which is the Docker-capable Redis
|
||||
lane. Docker availability and container startup are attempted directly; no condition, assumption,
|
||||
or environment flag may convert absence into a skip.
|
||||
|
||||
The test loads `redis.approved.image` from `src/gradle/redis-test-images.properties` and rejects an
|
||||
unpinned reference. It creates an ephemeral CA/server certificate and a named, least-privilege ACL
|
||||
user, then connects with TLS, full hostname verification, and explicit CA trust. A
|
||||
runtime-generated Redis password and 32-byte HMAC are supplied through caller-owned versioned
|
||||
material; no secret value is checked in, passed on the Redis command line, or logged. Missing
|
||||
Docker or OpenSSL is a hard failure, not a skip.
|
||||
|
||||
The custom source set needs the Spring Session API at compile time. App-bootstrap therefore adds
|
||||
`spring-session-core` only to `redisCompositionTestImplementation`; the existing version is reused
|
||||
and the lockfile records the new custom compile configuration without changing a dependency
|
||||
version.
|
||||
|
||||
## HTTP/Session Contract
|
||||
|
||||
1. A state-changing request without CSRF is 403.
|
||||
2. Accessing the CSRF endpoint emits the configured Secure, non-HttpOnly CSRF cookie.
|
||||
3. Login with matching cookie/header creates only the bounded primitive authentication snapshot.
|
||||
4. The session cookie is host-only, Secure, HttpOnly, SameSite=Lax, path `/`, and session-scoped.
|
||||
5. After the first web context closes, a second independent context restores `/whoami` from the
|
||||
same cookie through real Redis.
|
||||
6. Logout force-revokes/tombstones the session; the old cookie is unauthenticated and a previously
|
||||
loaded stale session object cannot save over the tombstone.
|
||||
7. If Redis becomes unavailable during session lookup, the request fails closed before the
|
||||
protected controller and the surfaced exception graph contains only the repository's fixed
|
||||
availability message, not endpoint/password/session material.
|
||||
|
||||
The RED run exposed two production composition gaps which are part of this boundary:
|
||||
|
||||
- the primitive security-context repository must wrap the response and persist before response
|
||||
commit, otherwise a successful response can commit before the first session is created;
|
||||
- the API security chain disables Spring Security's request cache, otherwise an unauthenticated
|
||||
request stores a `DefaultSavedRequest` framework graph that the primitive session codec correctly
|
||||
rejects.
|
||||
|
||||
## Architecture
|
||||
|
||||
- Inbound-web remains provider-neutral and has no outbound dependency.
|
||||
- Cache-redis keeps Redis keys, Lua, codec, HMAC, and tombstone policy private.
|
||||
- App-bootstrap assembles both adapters only for a cross-module composition contract.
|
||||
- No production dependency edge or dependency version changes; only a custom-test compile
|
||||
configuration is added to the existing lock entry.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Redis Sentinel/Cluster sessions (production activation explicitly rejects them today);
|
||||
- browser-engine proof of SameSite behavior;
|
||||
- credential/certificate rotation qualification (the fixture still uses mandatory TLS, full
|
||||
hostname verification, explicit trust, and a named ACL user);
|
||||
- attaching Docker work to ordinary `check`;
|
||||
- staging, commit, amend, or push by an agent.
|
||||
@@ -0,0 +1,84 @@
|
||||
# Verification Purity Refactoring Design
|
||||
|
||||
**Date:** 2026-08-02
|
||||
**Status:** approved by the user's instruction to apply the P1/P2 review sequentially
|
||||
**Scope:** stale traceable JAR verification/cleanup and public-path snapshot verification/update
|
||||
|
||||
## Context
|
||||
|
||||
Two root Gradle verification paths currently mutate files while they are expected to be safe gates:
|
||||
|
||||
- every `Jar` task deletes stale traceable archives in `doFirst`, and
|
||||
`verifyNoStaleTraceableJars` depends on `cleanStaleTraceableJars`;
|
||||
- `verifyPublicPathSnapshot` creates a missing snapshot and updates drift when
|
||||
`-PapprovePublicPathChange` is supplied.
|
||||
|
||||
That makes `check` capable of hiding the state it is meant to detect. This batch restores the
|
||||
standard contract: verification observes and fails, while explicitly named maintenance tasks own
|
||||
writes.
|
||||
|
||||
## Considered Approaches
|
||||
|
||||
### Keep the root build logic in place and inspect source text in tests
|
||||
|
||||
This is the smallest diff, but a source assertion cannot prove task side effects. Rejected.
|
||||
|
||||
### Invoke the entire repository build from a copied checkout
|
||||
|
||||
This tests the actual root build but requires copying all 19 leaves and resolving every root plugin
|
||||
for two small contracts. It is slow and couples the tests to unrelated configuration. Rejected.
|
||||
|
||||
### Extract only the two task concerns into applied Gradle scripts and exercise them with TestKit
|
||||
|
||||
Selected. The production root applies the same scripts that an isolated functional fixture uses.
|
||||
The fixture observes exit status and filesystem state, so it proves behavior rather than source
|
||||
shape. This is a bounded extraction required for testability, not the broad P2 root-build rewrite.
|
||||
|
||||
## Archive Hygiene Contract
|
||||
|
||||
`gradle/archive-hygiene.gradle` owns stale traceable archive discovery and the two root tasks:
|
||||
|
||||
- `verifyNoStaleTraceableJars` reports every stale archive and fails without deleting anything;
|
||||
- `cleanStaleTraceableJars` deletes only names matching the traceable archive pattern for a known
|
||||
`Jar` task and never deletes the current archive;
|
||||
- normal `jar`/`bootJar` execution never performs cleanup.
|
||||
|
||||
The existing traceable version naming and manifest metadata remain unchanged.
|
||||
|
||||
## Public-Path Snapshot Contract
|
||||
|
||||
`gradle/public-path-snapshot.gradle` owns canonicalization and two root tasks:
|
||||
|
||||
- `verifyPublicPathSnapshot` fails when the env file or committed snapshot is missing, when content
|
||||
drifts, or when the update-only approval property is passed to the verifier. It never creates
|
||||
directories or writes files;
|
||||
- `updatePublicPathSnapshot` requires `-PapprovePublicPathChange` and writes the canonical snapshot.
|
||||
|
||||
A clean-worktree requirement is intentionally not used: the normal update workflow necessarily has
|
||||
an intentional `.env` change. Explicit task naming, the approval property, and the resulting diff
|
||||
are the review boundary.
|
||||
|
||||
The canonical header names `updatePublicPathSnapshot`, so documentation and the committed snapshot
|
||||
do not instruct users to mutate through a verification task.
|
||||
|
||||
## Testing
|
||||
|
||||
`BuildVerificationPurityContractTest` runs from an isolated `functionalTest` source set using Gradle
|
||||
TestKit against temporary projects that apply the production scripts directly. Keeping TestKit off
|
||||
the ordinary `testRuntimeClasspath` prevents Gradle's SLF4J provider from replacing Logback during
|
||||
Spring tests. It proves:
|
||||
|
||||
1. a normal `jar` leaves a matching stale archive untouched;
|
||||
2. verification fails and preserves the stale archive;
|
||||
3. explicit cleanup deletes the stale archive but preserves the current archive;
|
||||
4. missing/drifted public-path snapshots cause read-only failure;
|
||||
5. the verifier rejects the update approval property;
|
||||
6. only the explicit updater with approval creates or changes the snapshot.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- no change to archive naming, versions, manifests, production dependency versions, or project edges;
|
||||
- only the new isolated functional-test configurations are added to `app-bootstrap/gradle.lockfile`;
|
||||
- no public-path allow-list value change;
|
||||
- no broad root Gradle convention-plugin migration;
|
||||
- no staging, commit, amend, or push by an agent.
|
||||
@@ -0,0 +1,448 @@
|
||||
# Warning-Zero Build Refactoring Design
|
||||
|
||||
**Date:** 2026-08-02
|
||||
**Status:** Approved design, pending written-spec review
|
||||
**Scope:** Java compilation, Error Prone, Checkstyle, SpotBugs, test JVM diagnostics, expected-negative
|
||||
shell-contract output, and intentional legacy/architecture-test compatibility seams.
|
||||
|
||||
## Goal
|
||||
|
||||
Make the standard repository build both functionally green and warning-clean. A successful build
|
||||
must no longer conceal compiler warnings, test-source SpotBugs findings, ignored Checkstyle
|
||||
findings, deprecated third-party API calls, or expected-negative subprocess diagnostics that look
|
||||
like real failures.
|
||||
|
||||
The final local proof is a fresh `./gradlew clean build --warning-mode=all --no-daemon
|
||||
--console=plain` with:
|
||||
|
||||
- exit code zero;
|
||||
- zero compiler/Error Prone warnings;
|
||||
- zero Checkstyle and SpotBugs findings in every executed source set;
|
||||
- zero `SpotBugs ended with exit code 1` messages;
|
||||
- zero OpenJDK CDS warnings from test JVMs;
|
||||
- no successful Redis lab contract printing its expected-negative child diagnostics;
|
||||
- only the five currently intentional optional-adapter/TestKit skips, with no qualification lane
|
||||
silently skipped.
|
||||
|
||||
## Baseline Evidence
|
||||
|
||||
The fresh pre-change command completed successfully in 20 minutes 26 seconds with 283 of 283 tasks
|
||||
executed. Success did not mean warning-clean:
|
||||
|
||||
- 123 compiler warning diagnostics across 19 warning rules (122 distinct file-line/rule
|
||||
coordinates because one line emits two separate removal diagnostics);
|
||||
- one test-source SpotBugs `DMI_RANDOM_USED_ONLY_ONCE` finding;
|
||||
- ten OpenJDK CDS warning lines from Mockito-using test JVMs;
|
||||
- 82 `redis-lab:` expected-negative stderr lines;
|
||||
- five intentional skipped tests;
|
||||
- no test failure, compiler error, Checkstyle finding, SpotBugs analysis error, or missing analysis
|
||||
class.
|
||||
|
||||
The Gradle Problems report is an informational index over compiler diagnostics, not a separate
|
||||
defect. It must become empty as a consequence of removing the underlying warnings; it must not be
|
||||
hidden.
|
||||
|
||||
### Warning inventory traceability
|
||||
|
||||
| Rule | Diagnostic instances | Required resolution |
|
||||
| --- | ---: | --- |
|
||||
| `removal` | 46 | Exact legacy lifecycle/suppression policy in section 4 |
|
||||
| `MissingOverride` | 16 | Add annotations to the implementing test fakes in section 2 |
|
||||
| `StringCaseLocaleUsage` | 10 | `Locale.ROOT` behavior fixes and test cleanup in sections 1–2 |
|
||||
| `SameNameButDifferent` | 9 | Qualify the two Redis nested enum types in section 2 |
|
||||
| `DefaultCharset` | 9 | Explicit UTF-8 test data in sections 1–2 |
|
||||
| `ArrayRecordComponent` | 7 | Exact record policies and copy regressions in section 2 |
|
||||
| `CanonicalDuration` | 5 | `Duration.ofDays(3)` in section 2 |
|
||||
| `StringSplitter` | 4 | ETag scanner plus three grammar-specific test fixes in sections 1–2 |
|
||||
| `EmptyCatch` | 4 | Cleanup failure propagation in section 1 |
|
||||
| `StringConcatToTextBlock` | 2 | Byte-identical text blocks in section 2 |
|
||||
| `InvalidBlockTag` | 2 | Inline-code annotation names in section 2 |
|
||||
| `BigDecimalLiteralDouble` | 2 | Method-only intentional-fixture suppressions in section 5 |
|
||||
| `TypeParameterUnusedInFormals` | 1 | Spring Session method-only suppression in section 2 |
|
||||
| `ThreadLocalUsage` | 1 | Instance-isolation regression and field-only suppression in section 2 |
|
||||
| `ReferenceEquality` | 1 | Redis catalog identity regression and constructor-only suppression in section 2 |
|
||||
| `MissingSummary` | 1 | Public Javadoc summary in section 2 |
|
||||
| `JavaTimeDefaultTimeZone` | 1 | Fixed date/explicit zone in section 1 |
|
||||
| `FutureReturnValueIgnored` | 1 | Observe the future in section 1 |
|
||||
| `BooleanLiteral` | 1 | Literal assertion cleanup in section 2 |
|
||||
|
||||
This table accounts for all 123 Error Prone/compiler-warning diagnostics. The separate
|
||||
`-Xlint:deprecation,unchecked` inventory is covered by the third-party migrations and exact legacy
|
||||
seam policy below; it is not allowed to disappear through a source-set suppression.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Do not remove the legacy poster-image endpoint, `StoredObjectResponse`, raw-key compatibility
|
||||
data, or legacy object-storage adapters during warning cleanup.
|
||||
- Do not switch the sample runtime from legacy to publication mode without the separately required
|
||||
API, data-adoption, dual-read, and external-consumer approvals.
|
||||
- Do not apply module-wide or task-wide suppression for `removal`, `deprecation`, `unchecked`, or
|
||||
Error Prone rules.
|
||||
- Do not weaken architecture rules or change deliberately forbidden bytecode merely to silence a
|
||||
fixture warning.
|
||||
- Do not make quarantine tests blocking; their separate sunset and reporting policy remains
|
||||
unchanged.
|
||||
|
||||
## Design Principles
|
||||
|
||||
1. Fix behavior defects at their source before applying any suppression.
|
||||
2. Use suppression only where a framework signature, identity invariant, intentional violation
|
||||
fixture, or approved compatibility seam makes the warning inapplicable.
|
||||
3. Scope every suppression to the smallest class, method, field, constructor, or fixture that
|
||||
explains it, with a nearby rationale.
|
||||
4. Replace deprecated third-party APIs with their typed current equivalents and verify behavior,
|
||||
not only compilation.
|
||||
5. Capture expected-negative diagnostics and assert them exactly; never discard stderr globally.
|
||||
6. Add blocking gates only after the current warning inventory is clean.
|
||||
|
||||
## Component Design
|
||||
|
||||
### 1. Real behavior defects
|
||||
|
||||
#### Locale-independent identifiers
|
||||
|
||||
Use `Locale.ROOT` for security roles, notification configuration keys, repository ACL names, and
|
||||
test comparisons. Add Turkish-default-locale regressions that restore the original default locale
|
||||
in `finally`:
|
||||
|
||||
- `JwtToAuthenticatedPrincipalConverter`: `admin` must always become `ROLE_ADMIN`;
|
||||
- `RoutingNotifier`: diagnostic keys for `EMAIL` must remain `app.notification.routes.email...`;
|
||||
- `RepoStatsAclMapper`: `IDEA/Repo` must normalize to `idea/repo`.
|
||||
|
||||
This is a correctness fix: the current code can generate dotless/dotted Turkish-I variants in
|
||||
authorization and operational identifiers.
|
||||
|
||||
#### Quote-aware ETag list parsing
|
||||
|
||||
Do not replace `String.split(",")` with another delimiter-only splitter. A comma is legal inside a
|
||||
quoted opaque entity tag. `ETags` will use a small scanner that:
|
||||
|
||||
- splits only on commas outside a quoted string;
|
||||
- preserves weak-tag prefixes and the existing trimming behavior;
|
||||
- treats malformed/unclosed quotes as non-matching input rather than guessing a token;
|
||||
- preserves wildcard and ordinary multi-value behavior.
|
||||
|
||||
Regressions cover a single comma-bearing tag, a mixed list containing a weak comma-bearing tag,
|
||||
ordinary lists, wildcard, stale values, blank input, and malformed quoting.
|
||||
|
||||
#### Asynchronous and cleanup failures
|
||||
|
||||
- `AsyncGracefulShutdownBehaviorTest` retains the returned `Future<?>` and observes `get()` so a
|
||||
background assertion or exception cannot disappear.
|
||||
- Outbox test cleanup methods propagate or wrap resource-destruction failures with the original
|
||||
cause instead of using empty catches.
|
||||
- Tests use fixed dates, UTF-8, and explicit locale rather than host defaults.
|
||||
|
||||
### 2. Production warning cleanup with preserved invariants
|
||||
|
||||
#### Redis primitive ownership
|
||||
|
||||
`RedisPrimitiveInvocation` intentionally requires descriptor object identity. Value equality would
|
||||
admit a descriptor created by another catalog and weaken the closed-catalog invariant. Keep the
|
||||
reference comparison, add an exact constructor-level `ReferenceEquality` suppression, and add a
|
||||
regression proving value-equal but non-identical cross-catalog descriptors are rejected.
|
||||
|
||||
Qualify both nested `ExpectedKind` types with their enclosing record names rather than renaming the
|
||||
types. This removes `SameNameButDifferent` without changing bytecode or package-local consumers.
|
||||
|
||||
#### Framework-owned generic signature
|
||||
|
||||
`RedisVersionedSession.<T>getAttribute(String)` must retain Spring Session's inherited signature.
|
||||
Apply a method-only `TypeParameterUnusedInFormals` suppression with the interface-contract reason.
|
||||
|
||||
#### Instance-owned retry context
|
||||
|
||||
`OutboundRetryPolicy` keeps its instance `ThreadLocal`. Making it static would leak call context
|
||||
between policy instances on the same thread. Add a field-only `ThreadLocalUsage` suppression and a
|
||||
regression proving policy A's context is invisible to policy B and is cleared by `endCall()`.
|
||||
|
||||
#### Array-bearing records
|
||||
|
||||
- `NotificationCiphertext` retains its public array components because it already clones inputs and
|
||||
accessors, implements content-based equality/hash code, and redacts `toString`. Add focused
|
||||
defensive-copy/equality/redaction tests and an exact record-level suppression.
|
||||
- The four internal session command/outcome records in `VersionedRedisSessionStore` remain internal
|
||||
transport envelopes. Preserve defensive copies, document that generated record equality is not
|
||||
their contract, add constructor/accessor copy tests, and suppress `ArrayRecordComponent` on each
|
||||
exact record.
|
||||
- The private test fake in `RedisVersionedSessionRepositoryTest` receives the same exact nested-type
|
||||
treatment; no public type is changed.
|
||||
|
||||
#### Mechanical behavior-neutral fixes
|
||||
|
||||
- Express 72 hours as `Duration.ofDays(3)` in application/bootstrap/sample settings and matching
|
||||
tests.
|
||||
- Add the missing public Javadoc summary in `TracingSampleRateResolver`, and render annotation names
|
||||
such as `@WebMvcTest` as inline `{@code ...}` rather than accidental block tags.
|
||||
- Add missing `@Override` annotations in sample test fakes.
|
||||
- Replace readability-only string concatenations with text blocks where the literal bytes remain
|
||||
identical.
|
||||
- Replace Boolean wrapper comparisons with boolean literals.
|
||||
- For the three test-only delimiter warnings, preserve each existing grammar explicitly: retain CSV
|
||||
empty-token filtering with a limit-bearing split or scanner, scan mapping-path segments without
|
||||
changing leading/trailing-empty behavior, and parse the single HTTP byte-range hyphen with an
|
||||
asserted `indexOf` boundary. These are not allowed to inherit the ETag scanner because their
|
||||
grammars differ.
|
||||
|
||||
### 3. Third-party API migration
|
||||
|
||||
#### Jackson 3
|
||||
|
||||
In `LocalJsonSchemaRegistry`, replace deprecated `JsonNode.isTextual()`/`textValue()` with
|
||||
`isString()`/`stringValue()`. Existing type guards remain, and JSON schema identity/reference/value
|
||||
tests prove identical acceptance and rejection behavior.
|
||||
|
||||
In `DeterministicEnvelopeWriter`, replace the deprecated convenience call with
|
||||
`jsonFactory.createGenerator(ObjectWriteContext.empty(), output, JsonEncoding.UTF8)`, the
|
||||
non-deprecated Jackson 3.0.2 overload. Preserve canonical byte output; the existing deterministic
|
||||
envelope golden tests are the behavior gate.
|
||||
|
||||
#### Lettuce
|
||||
|
||||
Convert both finite canonical scores to `BigDecimal`, build one inclusive
|
||||
`Range<? extends Number>` for each invocation, and call the typed `zcount(key, range)` and
|
||||
`zrangebyscoreWithScores(key, range, Limit.create(offset, count))` overloads. Preserve inclusive
|
||||
bounds, offset, count, and exact reply mapping. A dynamic-proxy regression verifies both typed
|
||||
overloads are selected; sorted-set primitive contract tests verify results.
|
||||
|
||||
#### AWS SDK retry
|
||||
|
||||
Replace old `RetryPolicy` and core `EqualJitterBackoffStrategy` with `StandardRetryStrategy`, the
|
||||
retries API half-jitter exponential backoff, `maxAttempts`, and
|
||||
`ClientOverrideConfiguration.Builder.retryStrategy`. Tests assert maximum attempts and normal versus
|
||||
throttling backoff configuration. The focused object-storage check must cover provider assembly;
|
||||
compile-only success is insufficient.
|
||||
|
||||
#### Testcontainers Toxiproxy
|
||||
|
||||
Use the Testcontainers 2 toxiproxy package and a typed `ToxiproxyClient`/`Proxy` with an explicit
|
||||
exposed proxy port. Fault tests must still prove cut and restore behavior against MinIO. Dependency
|
||||
and lock changes stay inside the object-storage leaf.
|
||||
|
||||
#### Remaining JDK/generic deprecations
|
||||
|
||||
- Replace deprecated `new URL(String)` test construction with `URI.create(...).toURL()`.
|
||||
- Replace the varargs `thenReturn(firstFuture, secondFuture)` stub in
|
||||
`S3ConditionalObjectControlStoreTest` with two chained single-value `thenReturn(...)` calls, so
|
||||
Mockito does not create the unchecked generic `CompletableFuture<PutObjectResponse>[]` array.
|
||||
- Resolve every `-Xlint:deprecation,unchecked` location individually; do not suppress the source
|
||||
set.
|
||||
|
||||
### 4. Legacy object-storage compatibility seam
|
||||
|
||||
The canonical object-storage ports and sample publication path already exist. The legacy runtime is
|
||||
still selected in local/test configuration and cannot be deleted solely to silence warnings.
|
||||
|
||||
Keep `@Deprecated(forRemoval = true)` on the genuinely replaced whole-byte contracts:
|
||||
|
||||
- `ObjectStoragePort`;
|
||||
- `StoredObject`;
|
||||
- `ObjectStorageSettings`.
|
||||
|
||||
Apply `removal` suppression only to exact compatibility owners:
|
||||
|
||||
- `ObjectStoragePort` for its legacy receipt return type;
|
||||
- `FilesystemObjectStorageAdapter` and `S3ObjectStorageAdapter`;
|
||||
- `UploadPosterImageUseCase`;
|
||||
- the legacy bean method in `PosterImageApiConfig`;
|
||||
- `LegacyPosterImageController`;
|
||||
- `PosterWebMapper.toStoredObjectResponse`;
|
||||
- named legacy characterization test classes and single legacy-receipt test methods.
|
||||
|
||||
The six `application.storage.migration` types and `AdoptLegacyPosterImageUseCase` are the mechanism
|
||||
used to complete data adoption and currently have no replacement. Change their lifecycle marker
|
||||
from `@Deprecated(forRemoval = true)` to plain `@Deprecated`; use exact `deprecation` suppression
|
||||
only inside adoption implementation/configuration. Keep the application-core architecture contract
|
||||
requiring `forRemoval=true` only for `ObjectStoragePort` and `StoredObject`. Keep the adapter-owned
|
||||
`ObjectStorageSettings` marker and add its lifecycle assertion in the object-storage leaf.
|
||||
|
||||
This keeps migration debt visible without falsely claiming that the migration mechanism itself is
|
||||
ready for removal.
|
||||
|
||||
### 5. Test/static-analysis/output cleanup
|
||||
|
||||
#### SpotBugs
|
||||
|
||||
Reuse one static `SecureRandom` in `RedisPrimitiveRuntimeServiceTest` rather than constructing a
|
||||
one-shot generator. After all test reports are clean, make every ordinary and custom test-source
|
||||
SpotBugs task included by `check` blocking. SpotBugs analysis errors and missing classes remain
|
||||
separately fail-closed.
|
||||
|
||||
#### Intentional architecture fixtures
|
||||
|
||||
Keep prohibited `BigDecimal(double/float)` constructor bytecode and apply method-only
|
||||
`BigDecimalLiteralDouble` suppressions. Fix unrelated warnings in allowed fixtures normally. A
|
||||
suppression must never replace the forbidden operation the ArchUnit test is supposed to detect.
|
||||
|
||||
#### Redis lab expected failures
|
||||
|
||||
Change `assert_fails` to capture stdout/stderr per case, assert a non-zero exit and the exact expected
|
||||
diagnostic, reject extra lines, and print the capture only when the assertion fails. Do not redirect
|
||||
to `/dev/null` and do not silence the Gradle `Exec` task globally.
|
||||
|
||||
#### Mockito/CDS
|
||||
|
||||
Provide `mockito-core` to test JVMs as an explicit startup `-javaagent` through a relocatable Gradle
|
||||
argument provider. This removes reliance on Java 21+ runtime self-attachment. Add test-JVM-only
|
||||
`-Xshare:off` because Mockito's bootstrap append otherwise prints the harmless CDS warning. No
|
||||
production JVM argument changes.
|
||||
|
||||
#### Skips
|
||||
|
||||
Retain exactly these five intentional app-bootstrap contract skips:
|
||||
|
||||
- `emailNotificationAdapterRunsOnlyWhenConfigured()`;
|
||||
- `slackNotificationAdapterRunsOnlyWhenConfigured()`;
|
||||
- `redisCacheAdapterRunsOnlyWhenEnabled()`;
|
||||
- `messagingBrokerAdapterRunsOnlyWhenConfigured()`;
|
||||
- `DisabledOptionalAdapterFixture.wouldFailIfItEverRan()`.
|
||||
|
||||
Qualification tasks continue to require positive discovery, at least one executed test, zero skips,
|
||||
and fresh XML, so this policy cannot turn a selected qualification lane green without execution.
|
||||
Any additional skip, or any of these five moving outside its named optional-adapter contract, fails
|
||||
the inventory check.
|
||||
|
||||
### 6. Warning-zero enforcement
|
||||
|
||||
After all existing warnings are removed:
|
||||
|
||||
- configure every leaf `JavaCompile` task with `-Werror`, `-Xlint:deprecation`, and
|
||||
`-Xlint:unchecked` in the root build policy;
|
||||
- retain Error Prone on the same compile tasks so its warnings are promoted by `-Werror`;
|
||||
- remove the root `checkstyleTest`/`spotbugsTest` warning-only policy and the app-bootstrap
|
||||
`sampleOffTest`, `functionalTest`, and `conditionalTransportTest` Checkstyle/SpotBugs
|
||||
`ignoreFailures` overrides, making every such task included by `check` blocking;
|
||||
- retain exact suppression comments as the only approved exception mechanism;
|
||||
- run Gradle with `--warning-mode=fail` in the warning-clean CI lane so Gradle API deprecations also
|
||||
fail rather than print.
|
||||
|
||||
`quarantineTest` remains non-blocking by design. Protected AWS/Docker qualifications remain separate
|
||||
environment evidence and are not converted into local unit tests.
|
||||
|
||||
## File Ownership and Expected Change Groups
|
||||
|
||||
### Root build policy
|
||||
|
||||
- `src/build.gradle`
|
||||
- `src/gradle/test-jvm-agents.gradle`, defining the relocatable Mockito `-javaagent` argument
|
||||
provider and test-only `-Xshare:off` policy, applied once by the root build
|
||||
- `.github/workflows/ci-quality-gates.yml`, adding `--warning-mode=fail` to the blocking
|
||||
`quality-gates` Gradle invocation
|
||||
|
||||
### Production leaves
|
||||
|
||||
- `src/application-core`
|
||||
- `src/adapter/inbound/web`
|
||||
- `src/adapter/outbound/cache-redis`
|
||||
- `src/adapter/outbound/fileserver`
|
||||
- `src/adapter/outbound/httpclient`
|
||||
- `src/adapter/outbound/identifier`
|
||||
- `src/adapter/outbound/messaging`
|
||||
- `src/adapter/outbound/notification`
|
||||
- `src/adapter/outbound/objectstorage`
|
||||
- `src/adapter/outbound/persistence-jpa`
|
||||
- `src/app-bootstrap`
|
||||
- `src/sample-portfolio`
|
||||
- `src/shared-contract`
|
||||
|
||||
Every focused command is derived from the owning leaf's `gradle_path` in
|
||||
`src/config/architecture/modules.json`; no production dependency edge changes are permitted unless
|
||||
the registry is deliberately updated and its architecture verifier passes.
|
||||
|
||||
### Tests and shell contract
|
||||
|
||||
- owning leaf tests adjacent to every behavior change
|
||||
- exact architecture violation fixtures under app-bootstrap test sources
|
||||
- `infra/redis-lab/test/redis-lab-contract.sh`
|
||||
|
||||
## Implementation Sequence
|
||||
|
||||
1. Add failing behavioral regressions for locale, ETag parsing, async exception observation,
|
||||
cleanup propagation, Redis descriptor identity, and retry-context isolation.
|
||||
2. Implement those behavior fixes and run owner-focused tests.
|
||||
3. Remove behavior-neutral compiler/Error Prone warnings per leaf, using only exact justified
|
||||
suppressions.
|
||||
4. Migrate Jackson, Lettuce, AWS SDK, Testcontainers, URL, and generic stubs; run their focused
|
||||
behavior/qualification tests.
|
||||
5. Correct legacy lifecycle markers and exact compatibility suppressions; run application-core,
|
||||
object-storage, sample, and architecture contracts.
|
||||
6. Clean test-only warnings, SpotBugs, Mockito/CDS, and Redis-lab output.
|
||||
7. Enable blocking compiler, Checkstyle, SpotBugs, and Gradle warning gates.
|
||||
8. Run focused checks, architecture validators, dependency locks, full tests, full check, and the
|
||||
fresh warning-clean build.
|
||||
9. Update the LLM Wiki branch note and the warning-debt error note with resolved evidence or exact
|
||||
remaining environmental blockers.
|
||||
|
||||
## Verification Strategy
|
||||
|
||||
### Focused verification
|
||||
|
||||
- Each behavior change follows RED → GREEN with the owning leaf test.
|
||||
- Static-only warning fixes use the exact `compileJava`, `compileTestJava`, Checkstyle, or SpotBugs
|
||||
task as the failing/passing executable contract.
|
||||
- Third-party API migrations run behavior tests that exercise request mapping, retry/backoff,
|
||||
sorted-set bounds, schema parsing, or network-fault cut/restore semantics.
|
||||
- Legacy suppressions are checked by architecture tests that reject old imports outside the named
|
||||
compatibility surface.
|
||||
|
||||
### Repository verification
|
||||
|
||||
Run from `src/`:
|
||||
|
||||
```bash
|
||||
./gradlew test --no-daemon --console=plain
|
||||
./gradlew check --no-daemon --console=plain
|
||||
./gradlew build --warning-mode=fail --no-daemon --console=plain
|
||||
./gradlew clean build --warning-mode=all --no-daemon --console=plain
|
||||
./gradlew verifyCleanArchitectureDependencies verifyRuntimeModuleMembership \
|
||||
verifyDependencyLocks verifyPublicPathSnapshot verifyEnvKeys \
|
||||
--no-daemon --console=plain
|
||||
```
|
||||
|
||||
Also verify the real gate matrix, wrapper contract, shell syntax, warning-report XML, skipped-test
|
||||
inventory, and `git diff --check`.
|
||||
|
||||
## Failure Handling
|
||||
|
||||
- If a suggested warning fix changes a public signature or weakens an identity/security invariant,
|
||||
retain the behavior and use an exact documented suppression backed by a regression.
|
||||
- If three attempted fixes in one warning family fail or expose cross-module coupling, stop that
|
||||
family and revisit the design instead of stacking suppressions.
|
||||
- If the AWS retry or Toxiproxy migration cannot reproduce old behavior, report that qualification
|
||||
as blocked; do not claim warning-zero by suppressing the deprecation.
|
||||
- If a warning originates only in generated code, prove the generated source owner and configure
|
||||
that exact generated boundary; do not disable warnings for handwritten sources.
|
||||
|
||||
## Risks and Mitigations
|
||||
|
||||
- **ETag grammar regression:** use quote-aware focused tests before replacing the parser.
|
||||
- **Authorization drift:** test role normalization under Turkish locale.
|
||||
- **Redis catalog weakening:** retain identity comparison and test cross-catalog rejection.
|
||||
- **AWS retry semantic drift:** assert maximum attempts and backoff classes/policies, then run the
|
||||
object-storage provider tests.
|
||||
- **Legacy data stranding:** preserve legacy activation and characterization until the separate
|
||||
data/API migration gates are approved.
|
||||
- **Hidden diagnostics:** capture-and-assert expected stderr; never discard it.
|
||||
- **Suppression creep:** exact annotations plus architecture/import checks prevent module-wide
|
||||
exemptions.
|
||||
- **Build duration:** use owner-focused RED/GREEN loops and reserve full clean builds for integration
|
||||
checkpoints and final proof.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
The work is complete only when:
|
||||
|
||||
1. All behavior regressions and focused owner checks pass.
|
||||
2. Every compiler task passes with `-Werror`, deprecation lint, unchecked lint, and Error Prone.
|
||||
3. Every ordinary/custom Checkstyle and SpotBugs task included by `check` is blocking and clean.
|
||||
4. Legacy warnings are limited to no output because exact compatibility code is explicitly and
|
||||
locally justified; no module/task-wide suppression exists.
|
||||
5. The Redis lab successful contract prints only its success summary and unexpected child
|
||||
diagnostics still fail the test with captured evidence.
|
||||
6. Test JVMs print no CDS/self-attachment warning.
|
||||
7. Full test, check, build, dependency, architecture, runtime-membership, env, public-path, wrapper,
|
||||
gate-matrix, shell, and diff validators pass.
|
||||
8. The final fresh clean-build log contains no `warning:`, deprecated/unchecked `Note:`, SpotBugs
|
||||
non-zero message, OpenJDK warning, or leaked expected-negative Redis diagnostic.
|
||||
9. LLM Wiki capture records commands, results, resolved warning counts, suppressions, and any
|
||||
environment-only qualification not executed locally.
|
||||
@@ -0,0 +1,72 @@
|
||||
# Web Security Boundary Design
|
||||
|
||||
**Date:** 2026-08-02
|
||||
**Status:** approved by the user's instruction to apply the reviewed P1/P2 work sequentially
|
||||
**Scope:** JWT/OIDC/JWKS and CORS behavior at the `adapter:inbound:web` Spring Security filter boundary
|
||||
|
||||
## Context
|
||||
|
||||
The module has unit contracts for JWT validators, exception classification, envelope writers, and
|
||||
CORS settings. It does not yet prove that a real bearer request crosses issuer discovery, JWKS
|
||||
retrieval, signature/claim validation, principal conversion, `SecurityFilterChain`, and the public
|
||||
error envelope. CORS configuration is likewise untested at the filter boundary, where preflight
|
||||
ordering relative to authentication is the important behavior.
|
||||
|
||||
These are release-boundary checks and must not silently skip because an external IdP, environment
|
||||
variable, or optional flag is absent.
|
||||
|
||||
## Decision
|
||||
|
||||
Add a dedicated `webSecurityBoundaryTest` task that reuses the ordinary test output/classpath and
|
||||
runs only JUnit tests tagged `security-boundary`. Ordinary `test` excludes that tag so each contract
|
||||
runs once. The dedicated task:
|
||||
|
||||
- fails when no tests are discovered;
|
||||
- disables up-to-date reuse;
|
||||
- fails the root suite when any test reports `SKIPPED`;
|
||||
- is required by the inbound-web `check` task;
|
||||
- uses UTC and no environment-dependent conditions or assumptions.
|
||||
|
||||
JWT tests use a JDK loopback `HttpServer` bound to `127.0.0.1` on an ephemeral port. It serves the
|
||||
minimum OIDC discovery document and JWKS response. Tests generate ephemeral RSA keys and compact
|
||||
RS256 JWTs with the already-resolved Nimbus dependency; no new library or external network is
|
||||
allowed. Each failure case uses a fresh server and Spring context to prevent decoder/JWK cache
|
||||
cross-contamination.
|
||||
|
||||
CORS tests build the production `SecurityConfig` and real `springSecurityFilterChain` with direct
|
||||
configuration properties. They issue real preflight and actual-origin MockMvc requests. A test JWT
|
||||
decoder bean is allowed here because CORS ordering—not token decoding—is the owned boundary.
|
||||
|
||||
## JWT/JWKS Contract
|
||||
|
||||
- application context startup performs zero discovery/JWKS calls (lazy decoder);
|
||||
- a correctly signed token reaches a protected controller and exposes the expected
|
||||
`AuthenticatedPrincipal` subject/roles;
|
||||
- expiry beyond the configured 60-second skew, issuer mismatch, audience mismatch, wrong
|
||||
signature, and unknown `kid` produce their exact stable 401 error codes and bounded
|
||||
`WWW-Authenticate`/`Retry-After` headers;
|
||||
- deterministic JWKS 503 produces `AUTH_JWKS_UNAVAILABLE`, HTTP 503, and `Retry-After: 30`;
|
||||
- after that first-request 503, the same lazy decoder/context retries initialization and succeeds
|
||||
once the JWKS endpoint recovers;
|
||||
- discovery metadata that is fetched successfully but is internally inconsistent produces the
|
||||
fixed 500 `INTERNAL_AUTH_MISCONFIGURATION` envelope rather than a raw initialization exception;
|
||||
- responses never contain the bearer token, issuer URL, `kid`, JWK material, or internal decoder
|
||||
diagnostics.
|
||||
|
||||
## CORS Contract
|
||||
|
||||
- an approved credentialed preflight to an authenticated endpoint succeeds before bearer
|
||||
authentication and emits exact origin/credentials/method/header/max-age policy;
|
||||
- an unapproved origin receives 403 without allow-origin or allow-credentials reflection;
|
||||
- disabled CORS emits no CORS response headers;
|
||||
- wildcard origin without credentials returns `*` and no credentials header;
|
||||
- an approved actual-origin request receives matching CORS and bounded `Vary` headers;
|
||||
- wildcard plus credentials remains a settings startup failure (already covered by settings tests).
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- external IdP/TLS/rotation rehearsal;
|
||||
- browser-engine SameSite behavior;
|
||||
- Redis-backed session continuity (the next P1 batch);
|
||||
- new test libraries, Docker, or changes to production dependency direction;
|
||||
- staging, commit, amend, or push by an agent.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user