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

4.0 KiB

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.