Files
tech-log-backend/docs/superpowers/specs/2026-08-02-verification-purity-refactoring-design.md
T

85 lines
4.0 KiB
Markdown

# 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.