Files
tech-log-backend/docs/superpowers/plans/2026-07-25-ci-control-plane-recovery.md
T

63 KiB

CI Control Plane Recovery 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: Recover the repository's authoritative hidden control plane, make Gradle and Docker builds hermetic, and establish fail-closed release gates on the Gitea 1.27.0 host.

Architecture: Recovery is a hard gate: authoritative hidden assets are inventoried and restored before any policy is reconstructed or modified. A repository preflight then guards Gradle, security baselines, canonical .github/workflows, root-context Docker builds, and a single Gitea quality fan-in complemented by the vulnerability status.

Tech Stack: Java 21, Spring Boot 4.0.0, Gradle 9 wrapper, Python 3 stdlib harness, Docker/Compose, Gitea 1.27.0 Actions, Gitea runner, Renovate


Spec: docs/superpowers/specs/2026-07-25-ci-control-plane-recovery-design.md

Upstream architecture: docs/superpowers/specs/2026-07-20-harness-policy-engine-design.md

Working policy: commits are human-only. Agentic workers do not stage, commit, amend, or push. Each task ends with a reviewable working-tree checkpoint instead of a commit step.

Packet gate: this plan starts without a resolved task packet because .harness is absent. Tasks 1, 2A, and 2B are bootstrap recovery/reconstruction only. After the human-selected path establishes and validates the harness, write the controller-approved overlay and resolved packet to the recovery evidence directory. Do not start Task 3 until deterministic re-resolution and the recorded packet/rule checksums pass.

File Responsibility Map

Mode A — restore byte-for-byte before editing

  • .harness/ — project manifest, module registry, policies, validators, schemas, tests, canonical agents, generators, and task resolver
  • .agents/ — clean-architecture rules, Superpowers plugin, rendered Antigravity agents/hooks
  • .claude/ — Claude hooks and rendered agents
  • .codex/ — Codex rendered agents and validation guidance
  • .github/ — canonical workflows, scripts, gate matrix, CODEOWNERS, and vulnerability policy
  • .tool-versions — repository Java toolchain pin
  • .trivyignore.yaml — structured suppression contract
  • .gitattributes — repository text/binary normalization contract

Mode B — reconstruct under recorded provenance

  • .harness/ — reconstruct from the approved 2026-07-20 harness design and implementation plan
  • .agents/ — regenerate from reconstructed canonical harness sources
  • .claude/ — regenerate and validate Claude hooks/agents
  • .codex/ — regenerate and validate Codex agents
  • .harness/recovery-provenance.json — declare controlled-reconstruction, source documents, failed baseline revision, and the rule that reconstructed assets are not restored originals
  • .github/, .tool-versions, .trivyignore.yaml, .gitattributes — reconstruct in Tasks 4 and 6 from the approved current design and tracked build contracts

Create after Mode A or Mode B establishes the harness

  • .harness/project/control-plane.yaml — physical required-path and canonical-workflow manifest
  • .harness/validators/validate_control_plane.py — fail-fast physical control-plane validator
  • .harness/tests/test_control_plane.py — missing-path and workflow-shadow mutation tests
  • .dockerignore — repository-root Docker context exclusions
  • docs/security/public-paths-snapshot.txt — committed deny-by-default public-path baseline

Modify after Mode A or Mode B establishes the harness

  • src/build.gradle:274-280,754-819 — wire read-only public-path verification into check and separate approved baseline generation
  • src/Dockerfile:30-54 — build from repository root while retaining /build/src
  • src/Dockerfile.sample:42-63 — mirror the production builder layout
  • docker-compose.yml:28-35 — use repository-root context and src/Dockerfile
  • .github/workflows/ci-quality-gates.yml — add control-plane preflight and stable release-gate
  • .github/workflows/build-release-supply-chain.yml — consume the same preflight and root context
  • .github/workflows/dependency-vulnerability.yml — publish the complementary blocking status
  • .github/ci-gate-matrix.yml — record exact workflow/job/task ownership
  • .github/scripts/verify-gate-matrix.sh — validate the restored job graph
  • .github/scripts/verify-reproducible-build.sh — invoke Docker/Gradle with corrected paths
  • renovate.json:3-34 — correct the dependency model description and pause automerge
  • README.md:31-107 — correct quick start, Gitea CI, workflow, and Docker references
  • src/README.md:39-181 — correct registry SSOT, lock, snapshot, and Gitea gate guidance

Explicitly forbidden

  • .gitea/workflows/ — would shadow .github/workflows under Gitea's default WORKFLOW_DIRS
  • src/.harness/ — would duplicate the root registry and violate the harness SSOT
  • repository files containing Gitea API or runner registration tokens

Task 1: Preserve Evidence and Record the Human Recovery Mode

Files:

  • Read: AGENTS.md

  • Read: CLAUDE.md

  • Read: docs/superpowers/specs/2026-07-20-harness-policy-engine-design.md

  • Read: docs/superpowers/plans/2026-07-20-harness-policy-engine.md

  • Read: docs/superpowers/specs/2026-07-25-ci-control-plane-recovery-design.md

  • External recovery root: /tmp/ca-control-plane-recovery/authoritative-root

  • External evidence directory: /tmp/ca-control-plane-recovery/evidence

  • Step 1: Capture the repository baseline outside the worktree

Run:

mkdir -p /tmp/ca-control-plane-recovery/evidence
git rev-parse HEAD | tee /tmp/ca-control-plane-recovery/evidence/failed-head.txt
git status --short --branch | tee /tmp/ca-control-plane-recovery/evidence/failed-status.txt
git ls-tree -r --name-only HEAD \
  | tee /tmp/ca-control-plane-recovery/evidence/failed-tree.txt

Expected: failed-head.txt contains one 40-character revision; status contains no tracked changes other than the plan executor's intentional working-tree state; the tree contains no root .harness, .agents, .claude, .codex, or .github path.

  • Step 2: Inspect the preferred authoritative export

Human action: when available, copy or mount the original working tree/archive that produced the 2026-07-20 harness policy implementation at:

/tmp/ca-control-plane-recovery/authoritative-root

Inspect it without changing the repository:

for ci_recovery_path in \
  .harness \
  .agents \
  .claude \
  .codex \
  .github \
  .tool-versions \
  .trivyignore.yaml \
  .gitattributes
do
  if test -e "/tmp/ca-control-plane-recovery/authoritative-root/${ci_recovery_path}"
  then
    echo "PRESENT ${ci_recovery_path}"
  else
    echo "MISSING ${ci_recovery_path}"
  fi
done \
  | tee /tmp/ca-control-plane-recovery/evidence/authoritative-inventory.txt

Expected: the inventory records all eight paths as PRESENT for Mode A. Any MISSING result makes Mode A incomplete but does not preclude the human from choosing Mode B.

  • Step 3: Record exactly one human-selected mode

For a complete authoritative export, the human runs:

printf '%s\n' \
  'mode=A-authoritative-restore' \
  'decision=human-approved' \
  'claim=byte-for-byte-restore-after-hash-verification' \
  > /tmp/ca-control-plane-recovery/evidence/recovery-mode.txt

When the original export is unavailable or incomplete, the human runs:

printf '%s\n' \
  'mode=B-controlled-reconstruction' \
  'decision=human-approved' \
  'claim=reconstructed-not-restored' \
  'sources=2026-07-20-harness-spec,2026-07-20-harness-plan,2026-07-25-ci-recovery-design,tracked-build-contracts' \
  > /tmp/ca-control-plane-recovery/evidence/recovery-mode.txt

Verify:

grep -Eq '^mode=(A-authoritative-restore|B-controlled-reconstruction)$' \
  /tmp/ca-control-plane-recovery/evidence/recovery-mode.txt
grep -q '^decision=human-approved$' \
  /tmp/ca-control-plane-recovery/evidence/recovery-mode.txt

Expected: both commands exit 0. Stop until the human has selected one mode. Never infer the mode from which files happen to be available.

  • Step 4: Validate and hash Mode A when selected

Run only when recovery-mode.txt contains mode=A-authoritative-restore:

grep -q '^mode=A-authoritative-restore$' \
  /tmp/ca-control-plane-recovery/evidence/recovery-mode.txt
for ci_recovery_path in \
  .harness \
  .agents \
  .claude \
  .codex \
  .github \
  .tool-versions \
  .trivyignore.yaml \
  .gitattributes
do
  test -e "/tmp/ca-control-plane-recovery/authoritative-root/${ci_recovery_path}" \
    || {
      echo "RECOVERY BLOCKED: missing authoritative ${ci_recovery_path}" >&2
      exit 1
    }
done

cd /tmp/ca-control-plane-recovery/authoritative-root
find \
  .harness \
  .agents \
  .claude \
  .codex \
  .github \
  .tool-versions \
  .trivyignore.yaml \
  .gitattributes \
  -type f -print0 \
  | sort -z \
  | xargs -0 sha256sum \
  > /tmp/ca-control-plane-recovery/evidence/authoritative-sha256.txt
test -s /tmp/ca-control-plane-recovery/evidence/authoritative-sha256.txt

Expected: exit 0 and a non-empty, stably sorted hash inventory. If validation fails, stop Mode A and ask the human to repair the export or explicitly replace the recorded decision with Mode B.

  • Step 5: Record Mode B's reconstruction boundary when selected

Run only when recovery-mode.txt contains mode=B-controlled-reconstruction:

grep -q '^mode=B-controlled-reconstruction$' \
  /tmp/ca-control-plane-recovery/evidence/recovery-mode.txt
sha256sum \
  docs/superpowers/specs/2026-07-20-harness-policy-engine-design.md \
  docs/superpowers/plans/2026-07-20-harness-policy-engine.md \
  docs/superpowers/specs/2026-07-25-ci-control-plane-recovery-design.md \
  docs/superpowers/plans/2026-07-25-ci-control-plane-recovery.md \
  > /tmp/ca-control-plane-recovery/evidence/reconstruction-source-sha256.txt
test -s /tmp/ca-control-plane-recovery/evidence/reconstruction-source-sha256.txt

Expected: exit 0. These hashes identify the approved prose inputs; they are not presented as hashes of the missing original assets.

  • Step 6: Review checkpoint

Review:

cat /tmp/ca-control-plane-recovery/evidence/recovery-mode.txt
find /tmp/ca-control-plane-recovery/evidence -maxdepth 1 -type f -print | sort

Expected: one human-approved mode and its matching evidence files exist. No hidden repository file has been restored or reconstructed in this task.

Task 2A: Restore and Validate the Authoritative Hidden Control Plane

Files:

  • Restore: .harness/
  • Restore: .agents/
  • Restore: .claude/
  • Restore: .codex/
  • Restore: .github/
  • Restore: .tool-versions
  • Restore: .trivyignore.yaml
  • Restore: .gitattributes

Entry condition: run this task only when /tmp/ca-control-plane-recovery/evidence/recovery-mode.txt contains mode=A-authoritative-restore. Mode B skips Task 2A and executes Task 2B.

  • Step 1: Assert that recovery will not overwrite an existing path

Run from the repository root:

for ci_recovery_path in \
  .harness \
  .agents \
  .claude \
  .codex \
  .github \
  .tool-versions \
  .trivyignore.yaml \
  .gitattributes
do
  test ! -e "${ci_recovery_path}" \
    || {
      echo "RECOVERY BLOCKED: destination already exists: ${ci_recovery_path}" >&2
      exit 1
    }
done

Expected: exit 0. If another worker restored a path, stop and compare it to the authoritative hash inventory instead of overwriting it.

  • Step 2: Restore the exact asset set

Run:

for ci_recovery_path in \
  .harness \
  .agents \
  .claude \
  .codex \
  .github \
  .tool-versions \
  .trivyignore.yaml \
  .gitattributes
do
  cp -a \
    "/tmp/ca-control-plane-recovery/authoritative-root/${ci_recovery_path}" \
    "${ci_recovery_path}"
done

Expected: all eight paths exist in the working tree.

  • Step 3: Prove byte-for-byte parity with the recovery source

Run:

for ci_recovery_path in \
  .harness \
  .agents \
  .claude \
  .codex \
  .github \
  .tool-versions \
  .trivyignore.yaml \
  .gitattributes
do
  diff -qr \
    "/tmp/ca-control-plane-recovery/authoritative-root/${ci_recovery_path}" \
    "${ci_recovery_path}"
done

Expected: exit 0 with no output.

  • Step 4: Run the recovered harness's own tests before modifying it

Run:

python3 -m unittest discover -s .harness/tests -p 'test_*.py'
python3 .harness/validators/validate_policy_parity.py
python3 .harness/generators/render_agents.py --check

Expected: all commands exit 0. If a recovered command name differs, stop and report the recovered manifest and CLI help to the controller; do not silently substitute a guessed command.

  • Step 5: Confirm Gradle can now discover the registered projects

Run:

./src/gradlew -p src projects --no-daemon --console=plain
./src/gradlew -p src verifyCleanArchitectureDependencies --no-daemon --console=plain

Expected: both commands exit 0, and the project report contains all 19 registered leaf modules.

  • Step 6: Stop for task-packet resolution

Using the exact overlay schema exposed by the recovered resolver and its tests, write the controller-approved CI/deployment overlay to:

/tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-overlay.json

Then run the actual resolver and persist its output:

test -s /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-overlay.json
python3 .harness/validators/resolve_task.py \
  /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-overlay.json \
  > /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-packet.json
python3 -m json.tool \
  /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-packet.json \
  >/dev/null
sha256sum \
  /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-packet.json \
  .harness/core/risk-policy.yaml \
  .harness/core/evidence-policy.yaml \
  .harness/core/review-policy.yaml \
  .harness/core/report-policy.yaml \
  > /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-packet-and-rules.sha256
sha256sum --check \
  /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-packet-and-rules.sha256

Expected: every command exits 0; the overlay, resolved packet, packet hash, and all four governing rule hashes are retained at explicit evidence paths. The controller confirms the packet's high-risk CI/deployment classification before Task 3.

  • Step 7: Review checkpoint

Run:

git status --short
git diff --stat

Expected: only the byte-for-byte recovered assets and the two approved design/plan documents are present; no production Java file has changed.

Task 2B: Reconstruct the Harness Under New Provenance

Files:

  • Create: .harness/
  • Create: .agents/
  • Create: .claude/
  • Create: .codex/
  • Create: .harness/recovery-provenance.json
  • Reference: docs/superpowers/specs/2026-07-20-harness-policy-engine-design.md
  • Execute: docs/superpowers/plans/2026-07-20-harness-policy-engine.md:Task 1-5

Entry condition: run this task only when /tmp/ca-control-plane-recovery/evidence/recovery-mode.txt contains mode=B-controlled-reconstruction. Mode A skips Task 2B.

  • Step 1: Assert the controlled-reconstruction decision

Run:

grep -q '^mode=B-controlled-reconstruction$' \
  /tmp/ca-control-plane-recovery/evidence/recovery-mode.txt
grep -q '^claim=reconstructed-not-restored$' \
  /tmp/ca-control-plane-recovery/evidence/recovery-mode.txt
for ci_reconstruction_path in .harness .agents .claude .codex
do
  test ! -e "${ci_reconstruction_path}" \
    || {
      echo "RECONSTRUCTION BLOCKED: destination already exists: ${ci_reconstruction_path}" >&2
      exit 1
    }
done

Expected: exit 0.

  • Step 2: Reconstruct the 2026-07-20 harness design

Use superpowers:executing-plans or superpowers:subagent-driven-development to execute Tasks 1-5 of:

docs/superpowers/plans/2026-07-20-harness-policy-engine.md

Apply its registry, resolver, import-gate, verdict/evidence, platform-rendering, and risk/profile outputs exactly to:

.harness
.agents
.claude
.codex

Expected: all 19 leaf modules are present in .harness/project/modules.yaml; generated platform agents carry new source hashes; every platform retains human-only commit policy. Do not copy content from an unrelated plugin cache or label these files as restored.

  • Step 3: Add explicit reconstructed provenance

Create .harness/recovery-provenance.json with:

{
  "mode": "controlled-reconstruction",
  "claim": "reconstructed-not-restored",
  "decision_date": "2026-07-25",
  "failed_baseline_revision": "821fe00c323b5335980f271c7ee47b92ac2168f2",
  "source_documents": [
    "docs/superpowers/specs/2026-07-20-harness-policy-engine-design.md",
    "docs/superpowers/plans/2026-07-20-harness-policy-engine.md",
    "docs/superpowers/specs/2026-07-25-ci-control-plane-recovery-design.md",
    "docs/superpowers/plans/2026-07-25-ci-control-plane-recovery.md"
  ]
}
  • Step 4: Run schema, renderer, parity, and mutation evidence

Run:

python3 -m unittest discover -s .harness/tests -p 'test_*.py'
python3 .harness/validators/validate_modules.py
python3 .harness/validators/validate_policy_parity.py
python3 .harness/generators/render_agents.py --check

Expected: all commands exit 0, including the registry-driven import mutation suite for every registered production leaf.

  • Step 5: Prove Gradle consumes the reconstructed registry

Run:

./src/gradlew -p src projects --no-daemon --console=plain
./src/gradlew -p src verifyCleanArchitectureDependencies --no-daemon --console=plain

Expected: both commands exit 0 and the project report contains all 19 leaves.

  • Step 6: Hash reconstructed outputs as new artifacts

Run:

find \
  .harness \
  .agents \
  .claude \
  .codex \
  -type f -print0 \
  | sort -z \
  | xargs -0 sha256sum \
  > /tmp/ca-control-plane-recovery/evidence/reconstructed-sha256.txt
test -s /tmp/ca-control-plane-recovery/evidence/reconstructed-sha256.txt

Expected: exit 0. The evidence filename and provenance both say reconstructed; no report calls these hashes a match to the lost original.

  • Step 7: Stop for task-packet resolution

Using the exact overlay schema exposed by the reconstructed resolver and its tests, write the controller-approved CI/deployment overlay to:

/tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-overlay.json

Then run:

test -s /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-overlay.json
python3 .harness/validators/resolve_task.py \
  /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-overlay.json \
  > /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-packet.json
python3 -m json.tool \
  /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-packet.json \
  >/dev/null
sha256sum \
  /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-packet.json \
  .harness/core/risk-policy.yaml \
  .harness/core/evidence-policy.yaml \
  .harness/core/review-policy.yaml \
  .harness/core/report-policy.yaml \
  > /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-packet-and-rules.sha256
sha256sum --check \
  /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-packet-and-rules.sha256

Expected: every command exits 0; the reconstructed authority produces a valid high-risk packet and explicit packet/rule checksum evidence before Task 3.

  • Step 8: Review checkpoint

Run:

git status --short
git diff --stat
git diff --check

Expected: new harness/platform assets and explicit reconstruction provenance are reviewable; no production Java file changed.

Task 3: Add a Fail-Fast Physical Control-Plane Validator

Blocking entry assertion: before editing any Task 3 file, verify the recorded packet and rules and prove that the recovered resolver is deterministic for the same overlay:

test -s /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-overlay.json
test -s /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-packet.json
test -s /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-packet-and-rules.sha256
sha256sum --check \
  /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-packet-and-rules.sha256
python3 .harness/validators/resolve_task.py \
  /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-overlay.json \
  > /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-packet.recheck.json
cmp \
  /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-packet.json \
  /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-packet.recheck.json

Expected: all commands exit 0. Any missing checksum, changed rule, or non-deterministic packet returns control to Task 2A or 2B; Task 3 does not proceed.

Files:

  • Create: .harness/project/control-plane.yaml

  • Create: .harness/validators/validate_control_plane.py

  • Create: .harness/tests/test_control_plane.py

  • Step 1: Write the failing validator tests

Add a stdlib unittest suite with this public interface and fixture:

import json
import sys
import tempfile
import unittest
from pathlib import Path

HARNESS_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(HARNESS_ROOT))

from validators.validate_control_plane import validate_control_plane


class ControlPlaneValidationTest(unittest.TestCase):
    def setUp(self) -> None:
        self.temporary_directory = tempfile.TemporaryDirectory()
        self.repository = Path(self.temporary_directory.name)
        policy = {
            "schema_version": 1,
            "canonical_workflow_directory": ".github/workflows",
            "forbidden_workflow_shadow": ".gitea/workflows",
            "required_directories": [".github/workflows", ".harness"],
            "required_files": [".github/CODEOWNERS", ".tool-versions"],
        }
        policy_path = self.repository / ".harness/project/control-plane.yaml"
        policy_path.parent.mkdir(parents=True)
        policy_path.write_text(json.dumps(policy), encoding="utf-8")
        (self.repository / ".github/workflows").mkdir(parents=True)
        (self.repository / ".github/CODEOWNERS").write_text("* @owners\n", encoding="utf-8")
        (self.repository / ".tool-versions").write_text("java temurin-21\n", encoding="utf-8")

    def tearDown(self) -> None:
        self.temporary_directory.cleanup()

    def test_complete_control_plane_has_no_violations(self) -> None:
        self.assertEqual([], validate_control_plane(self.repository))

    def test_reports_every_missing_required_path(self) -> None:
        (self.repository / ".tool-versions").unlink()
        (self.repository / ".github/CODEOWNERS").unlink()
        self.assertEqual(
            [
                "missing required file: .github/CODEOWNERS",
                "missing required file: .tool-versions",
            ],
            validate_control_plane(self.repository),
        )

    def test_rejects_gitea_workflow_shadow(self) -> None:
        (self.repository / ".gitea/workflows").mkdir(parents=True)
        self.assertEqual(
            ["forbidden workflow shadow exists: .gitea/workflows"],
            validate_control_plane(self.repository),
        )


if __name__ == "__main__":
    unittest.main()
  • Step 2: Run the tests and observe the missing implementation

Run:

python3 .harness/tests/test_control_plane.py -v

Expected: non-zero exit because validate_control_plane or its manifest does not exist.

  • Step 3: Add the physical manifest

Create .harness/project/control-plane.yaml with JSON syntax:

{
  "schema_version": 1,
  "canonical_workflow_directory": ".github/workflows",
  "forbidden_workflow_shadow": ".gitea/workflows",
  "required_directories": [
    ".agents",
    ".claude",
    ".codex",
    ".github/workflows",
    ".harness"
  ],
  "required_files": [
    ".dockerignore",
    ".gitattributes",
    ".tool-versions",
    ".trivyignore.yaml",
    ".agents/plugins/ca-superpowers/rules/clean-architecture.md",
    ".github/CODEOWNERS",
    ".github/ci-gate-matrix.yml",
    ".github/dependency-vulnerability-policy.md",
    ".github/scripts/verify-gate-matrix.sh",
    ".github/scripts/verify-reproducible-build.sh",
    ".github/workflows/build-release-supply-chain.yml",
    ".github/workflows/ci-quality-gates.yml",
    ".github/workflows/dependency-vulnerability.yml",
    ".harness/manifest.yaml",
    ".harness/project/modules.yaml",
    "docs/security/public-paths-snapshot.txt",
    "src/gradle/wrapper/gradle-wrapper.jar",
    "src/gradle/wrapper/gradle-wrapper.properties",
    "src/gradlew"
  ]
}
  • Step 4: Implement deterministic validation

Implement:

#!/usr/bin/env python3
import json
import sys
from pathlib import Path


def validate_control_plane(repository_root: Path) -> list[str]:
    policy_path = repository_root / ".harness/project/control-plane.yaml"
    policy = json.loads(policy_path.read_text(encoding="utf-8"))
    violations: list[str] = []

    for relative_path in policy["required_directories"]:
        if not (repository_root / relative_path).is_dir():
            violations.append(f"missing required directory: {relative_path}")

    for relative_path in policy["required_files"]:
        if not (repository_root / relative_path).is_file():
            violations.append(f"missing required file: {relative_path}")

    shadow = policy["forbidden_workflow_shadow"]
    if (repository_root / shadow).exists():
        violations.append(f"forbidden workflow shadow exists: {shadow}")

    return sorted(violations)


def main() -> int:
    repository_root = Path(__file__).resolve().parents[2]
    violations = validate_control_plane(repository_root)
    if violations:
        print("control-plane validation failed:", file=sys.stderr)
        for violation in violations:
            print(f"  - {violation}", file=sys.stderr)
        return 1
    print("control-plane validation passed")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

If the recovered harness has an established CLI/result abstraction, retain the function signature and deterministic messages above while adapting only the entrypoint plumbing to that abstraction.

  • Step 5: Run positive and mutation tests

Run:

python3 .harness/tests/test_control_plane.py -v
python3 .harness/validators/validate_control_plane.py

Expected: unit tests pass. The repository invocation remains non-zero and deterministically lists the contracts not yet created by Tasks 4-6. This is the intended control-plane red state; do not weaken the manifest to make it green.

  • Step 6: Review checkpoint

Run:

git diff --check
python3 -m unittest discover -s .harness/tests -p 'test_*.py'

Expected: no whitespace errors and the full recovered-plus-new harness suite passes.

Task 4: Restore Security Contracts and Make Public Paths Fail Closed

Files:

  • Restore or create: .tool-versions

  • Restore or create: .gitattributes

  • Restore or create: .trivyignore.yaml

  • Create: docs/security/public-paths-snapshot.txt

  • Modify: src/build.gradle:274-280,754-819

  • Modify: src/README.md:115-152

  • Test: .harness/tests/test_control_plane.py

  • Step 1: Establish the three root baseline contracts

For Mode A, verify the three files still match the authoritative export:

diff -q \
  /tmp/ca-control-plane-recovery/authoritative-root/.tool-versions \
  .tool-versions
diff -q \
  /tmp/ca-control-plane-recovery/authoritative-root/.gitattributes \
  .gitattributes
diff -q \
  /tmp/ca-control-plane-recovery/authoritative-root/.trivyignore.yaml \
  .trivyignore.yaml

Expected in Mode A: all commands exit 0.

For Mode B, create .tool-versions with:

java temurin-21.0.11+10.0.LTS

Create .gitattributes with:

* text=auto eol=lf
*.bat text eol=crlf
*.jar binary
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary

Create .trivyignore.yaml with:

vulnerabilities: []
licenses: []
misconfigurations: []
secrets: []

Expected in Mode B: the files are reported as reconstructed in the review evidence and are not described as recovered originals.

  • Step 2: Verify the structured Trivy contract and Java major

Run:

sed -n '1,160p' .trivyignore.yaml
java -version 2>&1 | grep -E 'version "21(\.|")'
./src/gradlew -p src verifyTrivyignore --no-daemon --console=plain

Expected: the file contains all four structured sections and Gradle exits 0. If there are active suppressions, each has the recovered reason and bounded future expiry.

  • Step 3: Add a failing missing-baseline mutation

Extend .harness/tests/test_control_plane.py so deleting docs/security/public-paths-snapshot.txt produces:

["missing required file: docs/security/public-paths-snapshot.txt"]

Run:

python3 .harness/tests/test_control_plane.py -v

Expected: exit 0; the isolated fixture proves that deleting the baseline returns the exact blocking violation.

  • Step 4: Create the reviewed current baseline

Create docs/security/public-paths-snapshot.txt with:

# feature-security-operational-baseline D5 — deny-by-default public path snapshot.
# SSOT: SECURITY_PUBLIC_PATHS (src/.env) -> SecurityConfig permitAll(); anyRequest authenticated.
# Update only with: ./gradlew updatePublicPathSnapshot -PapprovePublicPathChange
/api/healthcheck

Expected: the single non-comment path matches src/.env:121.

  • Step 5: Write the read-only verification behavior before changing Gradle

In an isolated execution worktree, temporarily move the snapshot and run:

./src/gradlew -p src verifyPublicPathSnapshot --no-daemon --console=plain

Expected before the fix: exit 0 and a newly generated file. Record this as the failing characterization because verification should return non-zero when the baseline is absent. Restore the reviewed snapshot before continuing.

  • Step 6: Split verification from approved update

Change verifyPublicPathSnapshot so:

if (!snapshotFile.isFile()) {
    throw new GradleException(
            "verifyPublicPathSnapshot: missing committed baseline ${snapshotFile}")
}

Remove all writes from that task. Register updatePublicPathSnapshot to require -PapprovePublicPathChange, create the parent directory, and write the same canonical content. Without the property it must fail with:

updatePublicPathSnapshot requires -PapprovePublicPathChange
  • Step 7: Wire read-only verification into every leaf check

At src/build.gradle:274-280, add:

dependsOn rootProject.tasks.named('verifyPublicPathSnapshot')

Expected: check verifies but never updates the baseline.

  • Step 8: Verify positive and negative behavior

Run:

./src/gradlew -p src verifyPublicPathSnapshot --no-daemon --console=plain
./src/gradlew -p src updatePublicPathSnapshot --no-daemon --console=plain

Expected: verification exits 0; update exits non-zero with the required approval message.

Then, in the isolated execution worktree, move the snapshot aside and rerun verification.

Expected: non-zero exit with missing committed baseline; no replacement file is created.

  • Step 9: Update the security-gate documentation

Change src/README.md to state that the snapshot is committed, missing state fails closed, and only updatePublicPathSnapshot -PapprovePublicPathChange writes it. Remove the claim that a fresh checkout creates a baseline and passes.

  • Step 10: Review checkpoint

Run:

python3 .harness/tests/test_control_plane.py -v
./src/gradlew -p src verifyTrivyignore verifyPublicPathSnapshot --no-daemon --console=plain

Expected: unit and Gradle commands exit 0. Repository-wide control-plane validation is still expected to report the not-yet-created root .dockerignore and, in Mode B, the not-yet-reconstructed .github contracts; Tasks 5 and 6 close those failures.

Task 5: Move Docker Builds to the Repository-Root Context

Files:

  • Create: .dockerignore

  • Modify: docker-compose.yml:28-35

  • Modify: src/Dockerfile:30-54

  • Modify: src/Dockerfile.sample:42-63

  • Modify in Mode A, create in Task 6 for Mode B: .github/scripts/verify-reproducible-build.sh

  • Modify: README.md

  • Step 1: Reproduce the current context failure

Run:

docker build \
  -f src/Dockerfile \
  src/ \
  --build-arg RELEASE_VERSION=0.0.1 \
  --build-arg BUILD_VERSION=0.0.1+821fe00 \
  --build-arg GIT_SHA=821fe00 \
  --build-arg SOURCE_URL=https://git.learn.hyeonworks.com/donghyeon.kang/clean-architecture-backend-template \
  --tag caskeleton:context-red

Expected before the fix: non-zero exit while settings reports a missing /build/.harness/project/modules.yaml.

  • Step 2: Add the root context exclusion contract

Create .dockerignore with:

.git
.git/**
.gitea
.github
.agents
.claude
.codex
docs
**/.gradle
**/build
**/test-results
**/reports
**/.idea
**/.vscode
**/*.iml
**/.env
**/.env.*
tmp

!.harness/
!.harness/project/
!.harness/project/modules.yaml
!src/
!src/gradlew
!src/gradle/
!src/gradle/wrapper/
!src/gradle/wrapper/gradle-wrapper.jar
!src/gradle/wrapper/gradle-wrapper.properties
!src/settings.gradle
!src/build.gradle
!src/**/build.gradle
!src/**/gradle.lockfile
!src/**/src/

Expected: root governance, Git metadata, build output, and environment files stay out of the context; the module registry and Gradle source inputs remain available.

  • Step 3: Change the production builder layout

In src/Dockerfile, use:

WORKDIR /build/src

COPY --parents \
  .harness/project/modules.yaml \
  src/settings.gradle \
  src/build.gradle \
  src/**/build.gradle \
  src/**/gradle.lockfile \
  /build/
COPY src/gradlew ./
COPY src/gradle/ gradle/

RUN test -n "${RELEASE_VERSION}" \
 && test -n "${GIT_SHA}" \
 && ./gradlew verifyDependencyLocks --no-daemon --quiet \
      -PreleaseVersion="${RELEASE_VERSION}" -PgitRevision="${GIT_SHA}"

COPY src/ /build/src/
RUN ./gradlew :app-bootstrap:bootJar --no-daemon -x test \
      -PreleaseVersion="${RELEASE_VERSION}" -PgitRevision="${GIT_SHA}"

Keep the runtime stage unchanged.

  • Step 4: Mirror the sample builder layout

Apply the same /build/src, registry, wrapper, descriptor, lock, and source copy order to src/Dockerfile.sample; retain :sample-portfolio:bootJar as its target.

  • Step 5: Change Compose to root context

At docker-compose.yml:28-35, use:

build:
  context: .
  dockerfile: src/Dockerfile
  args:
    RELEASE_VERSION: "${RELEASE_VERSION:-0.0.1}"
    BUILD_VERSION: "${BUILD_VERSION:-0.0.1+0000000}"
    GIT_SHA: "${GIT_SHA:-0000000}"
    SOURCE_URL: "${SOURCE_URL:-https://example.invalid/ca-tmpl}"
  • Step 6: Update reproducible-build and README commands

Every production build command must use:

docker build -f src/Dockerfile .

Every sample build command must use:

docker build -f src/Dockerfile.sample .

Expected: no tracked command retains src/ as its Docker context.

  • Step 7: Verify Compose and both images

Run:

docker compose -f docker-compose.yml -f docker-compose.local.yml config --quiet
docker build \
  -f src/Dockerfile \
  . \
  --build-arg RELEASE_VERSION=0.0.1 \
  --build-arg BUILD_VERSION=0.0.1+821fe00 \
  --build-arg GIT_SHA=821fe00 \
  --build-arg SOURCE_URL=https://git.learn.hyeonworks.com/donghyeon.kang/clean-architecture-backend-template \
  --tag caskeleton:context-green
docker build \
  -f src/Dockerfile.sample \
  . \
  --tag caskeleton-sample:context-green

Expected: Compose exits 0; both images build successfully; settings finds /build/.harness/project/modules.yaml; strict dependency-lock verification succeeds.

  • Step 8: Review checkpoint

Run:

rg -n 'docker build .* src/' README.md src/README.md src/Dockerfile src/Dockerfile.sample
rg -n 'context:\\s*src/?' docker-compose*.yml

Expected: both searches return no matches.

Task 6: Enable Gitea Actions and Establish the Canonical Release Gate

Files:

  • Restore or create/modify: .github/workflows/ci-quality-gates.yml

  • Restore or create/modify: .github/workflows/build-release-supply-chain.yml

  • Restore or create/modify: .github/workflows/dependency-vulnerability.yml

  • Restore or create/modify: .github/ci-gate-matrix.yml

  • Restore or create/modify: .github/scripts/verify-gate-matrix.sh

  • Restore or create/modify: .github/scripts/verify-reproducible-build.sh

  • Restore or create/modify: .github/CODEOWNERS

  • Restore or create: .github/dependency-vulnerability-policy.md

  • Verify absent: .gitea/workflows/

  • External: Gitea repository Actions setting and repository-scoped runner

  • Step 1: Establish the canonical .github tree for the selected mode

For Mode A, run:

diff -qr \
  /tmp/ca-control-plane-recovery/authoritative-root/.github \
  .github

Expected in Mode A: exit 0 before intentional workflow edits.

For Mode B, create these directories:

.github
.github/scripts
.github/workflows

Create .github/CODEOWNERS with:

/.agents/ @donghyeon.kang
/.claude/ @donghyeon.kang
/.codex/ @donghyeon.kang
/.github/ @donghyeon.kang
/.harness/ @donghyeon.kang
/.trivyignore.yaml @donghyeon.kang
/docs/security/public-paths-snapshot.txt @donghyeon.kang
/renovate.json @donghyeon.kang

Create .github/dependency-vulnerability-policy.md with:

# Dependency Vulnerability Policy

- HIGH and CRITICAL fixable vulnerabilities block the dependency-vulnerability status.
- Trivy suppressions require `.trivyignore.yaml` reason, future expiry, and CODEOWNERS review.
- Scanner actions are pinned to full reviewed revisions.
- Dependency declarations and strict Gradle lockfiles change together.
- Renovate automerge remains disabled during CI recovery.

Expected in Mode B: the tree is explicitly reconstructed under .harness/recovery-provenance.json; it is not called a restored GitHub control plane.

  • Step 2: Confirm the Gitea baseline

Run:

curl -fsS https://git.learn.hyeonworks.com/api/v1/version \
  | jq -e '.version == "1.27.0"'
curl -fsS \
  https://git.learn.hyeonworks.com/api/v1/repos/donghyeon.kang/clean-architecture-backend-template \
  | jq -e '.has_actions == false'

Expected before enablement: both commands exit 0.

  • Step 3: Enable repository Actions

Human action: open https://git.learn.hyeonworks.com/donghyeon.kang/clean-architecture-backend-template/settings and enable Enable Repository Actions.

Verify:

curl -fsS \
  https://git.learn.hyeonworks.com/api/v1/repos/donghyeon.kang/clean-architecture-backend-template \
  | jq -e '.has_actions == true'

Expected: exit 0.

  • Step 4: Confirm the instance workflow-directory contract

Administrator action: inspect [actions].WORKFLOW_DIRS in the active Gitea configuration and confirm it contains:

.gitea/workflows,.github/workflows

Expected: .github/workflows is eligible and .gitea/workflows is absent from this repository. If the active configuration excludes .github/workflows, stop until the administrator corrects it.

  • Step 5: Register an isolated repository-scoped runner

Obtain the repository registration token from:

https://git.learn.hyeonworks.com/donghyeon.kang/clean-architecture-backend-template/settings/actions/runners

Load it into the runner host's secret environment as GITEA_RUNNER_REGISTRATION_TOKEN, then run on that host:

act_runner --config /etc/act_runner/config.yaml register \
  --no-interactive \
  --instance https://git.learn.hyeonworks.com \
  --token "${GITEA_RUNNER_REGISTRATION_TOKEN}" \
  --name ca-skeleton-repository \
  --labels ubuntu-22.04:docker://gitea/runner-images:ubuntu-22.04
act_runner --config /etc/act_runner/config.yaml daemon

Expected: repository settings show an enabled, idle runner named ca-skeleton-repository with label ubuntu-22.04. The registration token is not written to this repository or printed in CI.

  • Step 6: Verify runner inventory with authorization

Load a read-only repository or administrator API token into GITEA_ACTIONS_AUDIT_TOKEN, then run:

curl -fsS \
  -H "Authorization: token ${GITEA_ACTIONS_AUDIT_TOKEN}" \
  https://git.learn.hyeonworks.com/api/v1/repos/donghyeon.kang/clean-architecture-backend-template/actions/runners \
  | jq -e '(.runners // .) | any(.name == "ca-skeleton-repository" and .status == "online" and .disabled == false)'

Expected: exit 0. Without authorization the endpoint may return 401; that remains expected.

  • Step 7: Preserve .github/workflows as the only workflow directory

Run:

test ! -e .gitea/workflows
find .github/workflows -maxdepth 1 -type f -name '*.yml' -print | sort

Expected: no shadow directory. Mode A lists the recovered workflows; Mode B may list none until Steps 8-10 create them.

  • Step 8: Establish the quality job graph

Make .github/workflows/ci-quality-gates.yml conform to this stable graph:

name: CI Quality Gates

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  control-plane-preflight:
    name: control-plane-preflight
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
      - uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
        with:
          distribution: temurin
          java-version: "21"
          cache: gradle
      - run: python3 -m unittest discover -s .harness/tests -p 'test_*.py'
      - run: python3 .harness/validators/validate_control_plane.py
      - run: python3 .harness/validators/validate_modules.py
      - run: python3 .harness/validators/validate_policy_parity.py
      - run: python3 .harness/generators/render_agents.py --check
      - run: bash .github/scripts/verify-gate-matrix.sh
      - run: ./src/gradlew -p src verifyTrivyignore --no-daemon --console=plain

  gradle-quality:
    name: gradle-quality
    needs: control-plane-preflight
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
      - uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
        with:
          distribution: temurin
          java-version: "21"
          cache: gradle
      - working-directory: src
        run: ./gradlew verifyDependencyLocks verifyCleanArchitectureDependencies verifyPublicPathSnapshot test check --no-daemon --console=plain

  container-build:
    name: container-build
    needs: control-plane-preflight
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
      - run: |
          ci_revision="$(git rev-parse HEAD)"
          docker build -f src/Dockerfile . \
            --build-arg RELEASE_VERSION=0.0.1 \
            --build-arg BUILD_VERSION="0.0.1+${ci_revision:0:12}" \
            --build-arg GIT_SHA="${ci_revision}" \
            --build-arg SOURCE_URL=https://git.learn.hyeonworks.com/donghyeon.kang/clean-architecture-backend-template \
            --tag caskeleton:ci
          docker build -f src/Dockerfile.sample . --tag caskeleton-sample:ci

  quarantine:
    name: quarantine
    needs: control-plane-preflight
    runs-on: ubuntu-22.04
    continue-on-error: true
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
      - uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
        with:
          distribution: temurin
          java-version: "21"
          cache: gradle
      - working-directory: src
        run: ./gradlew quarantineTest --no-daemon --console=plain

  release-gate:
    name: release-gate
    if: always()
    needs:
      - control-plane-preflight
      - gradle-quality
      - container-build
    runs-on: ubuntu-22.04
    steps:
      - name: Require every blocking quality job
        run: |
          test "${{ needs.control-plane-preflight.result }}" = "success"
          test "${{ needs.gradle-quality.result }}" = "success"
          test "${{ needs.container-build.result }}" = "success"

In Mode A, merge stronger recovered static-analysis, reproducibility, and gate-matrix jobs into this graph without renaming their stable statuses. In Mode B, the graph above is the minimum initial quality contract. Every additional blocking job must be added to release-gate.needs and its shell assertions; quarantine remains outside the fan-in.

  • Step 9: Establish release-build and vulnerability workflows

Make .github/workflows/build-release-supply-chain.yml use this Gitea-compatible contract:

name: Build Release Supply Chain

on:
  push:
    tags:
      - "v*"

jobs:
  release-preflight:
    name: release-preflight
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
      - uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
        with:
          distribution: temurin
          java-version: "21"
          cache: gradle
      - run: python3 -m unittest discover -s .harness/tests -p 'test_*.py'
      - run: python3 .harness/validators/validate_control_plane.py
      - run: python3 .harness/validators/validate_modules.py
      - run: python3 .harness/validators/validate_policy_parity.py
      - run: python3 .harness/generators/render_agents.py --check
      - run: bash .github/scripts/verify-gate-matrix.sh
      - run: ./src/gradlew -p src verifyTrivyignore --no-daemon --console=plain

  release-build:
    name: release-build
    needs: release-preflight
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
      - uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
        with:
          distribution: temurin
          java-version: "21"
          cache: gradle
      - run: ./src/gradlew -p src verifyDependencyLocks check --no-daemon --console=plain
      - run: bash .github/scripts/verify-reproducible-build.sh
      - run: |
          release_revision="$(git rev-parse HEAD)"
          release_tag="$(git describe --tags --exact-match)"
          release_version="${release_tag#v}"
          docker build -f src/Dockerfile . \
            --build-arg RELEASE_VERSION="${release_version}" \
            --build-arg BUILD_VERSION="${release_version}+${release_revision:0:12}" \
            --build-arg GIT_SHA="${release_revision}" \
            --build-arg SOURCE_URL=https://git.learn.hyeonworks.com/donghyeon.kang/clean-architecture-backend-template \
            --tag "caskeleton:${release_version}"

Make .github/workflows/dependency-vulnerability.yml publish this separate blocking status:

name: Dependency Vulnerability

on:
  pull_request:
  push:
    branches:
      - main
  schedule:
    - cron: "17 2 * * *"

jobs:
  dependency-vulnerability:
    name: dependency-vulnerability
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
      - uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
        with:
          distribution: temurin
          java-version: "21"
          cache: gradle
      - run: python3 .harness/validators/validate_control_plane.py
      - run: ./src/gradlew -p src verifyTrivyignore --no-daemon --console=plain
      - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
        with:
          scan-type: fs
          scan-ref: .
          trivyignores: .trivyignore.yaml
          format: table
          exit-code: "1"
          ignore-unfixed: true
          severity: HIGH,CRITICAL

Expected: release tags re-run local release gates before building; pull requests and main publish the independent dependency-vulnerability status. All third-party actions are pinned to full reviewed revisions.

  • Step 10: Align gate matrix and scripts

For Mode B, create .github/ci-gate-matrix.yml with:

version: 1
blocking:
  - id: control-plane
    workflow: .github/workflows/ci-quality-gates.yml
    job: control-plane-preflight
    commands:
      - python3 -m unittest discover -s .harness/tests -p test_*.py
      - python3 .harness/validators/validate_control_plane.py
      - python3 .harness/validators/validate_modules.py
      - python3 .harness/validators/validate_policy_parity.py
      - python3 .harness/generators/render_agents.py --check
      - bash .github/scripts/verify-gate-matrix.sh
      - ./src/gradlew -p src verifyTrivyignore
    release_gate: true
  - id: gradle-quality
    workflow: .github/workflows/ci-quality-gates.yml
    job: gradle-quality
    command: ./gradlew verifyDependencyLocks verifyCleanArchitectureDependencies verifyPublicPathSnapshot test check
    release_gate: true
  - id: container-build
    workflow: .github/workflows/ci-quality-gates.yml
    job: container-build
    command: docker build -f src/Dockerfile .
    release_gate: true
  - id: dependency-vulnerability
    workflow: .github/workflows/dependency-vulnerability.yml
    job: dependency-vulnerability
    commands:
      - ./src/gradlew -p src verifyTrivyignore
      - aquasecurity/trivy-action with trivyignores=.trivyignore.yaml
    release_gate: protected-branch
non_blocking:
  - id: quarantine
    workflow: .github/workflows/ci-quality-gates.yml
    job: quarantine
    release_gate: false

For Mode A, preserve its recovered schema and add equivalent rows without deleting stronger gates.

Create or update .github/scripts/verify-gate-matrix.sh with these deterministic checks:

#!/usr/bin/env bash
set -euo pipefail

ci_repository_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "${ci_repository_root}"

test ! -e .gitea/workflows
for ci_workflow in \
  .github/workflows/ci-quality-gates.yml \
  .github/workflows/build-release-supply-chain.yml \
  .github/workflows/dependency-vulnerability.yml
do
  test -f "${ci_workflow}"
done

for ci_job in control-plane-preflight gradle-quality container-build release-gate
do
  rg -q "^  ${ci_job}:$" .github/workflows/ci-quality-gates.yml
done

ci_release_gate="$(
  awk '
    /^  release-gate:$/ { in_release_gate = 1; next }
    in_release_gate && /^  [A-Za-z0-9_-]+:$/ { exit }
    in_release_gate { print }
  ' .github/workflows/ci-quality-gates.yml
)"
for ci_dependency in control-plane-preflight gradle-quality container-build
do
  grep -q -- "- ${ci_dependency}" <<< "${ci_release_gate}"
done
if grep -q -- '- quarantine' <<< "${ci_release_gate}"
then
  echo "quarantine must not block release-gate" >&2
  exit 1
fi

for ci_gate in control-plane gradle-quality container-build dependency-vulnerability quarantine
do
  rg -q "id: ${ci_gate}$" .github/ci-gate-matrix.yml
done

For Mode B, create .github/scripts/verify-reproducible-build.sh with:

#!/usr/bin/env bash
set -euo pipefail

ci_repository_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
ci_revision="$(git -C "${ci_repository_root}" rev-parse HEAD)"
cd "${ci_repository_root}/src"

./gradlew clean :app-bootstrap:bootJar \
  --no-daemon --no-build-cache --rerun-tasks \
  -PreleaseVersion=0.0.1 -PgitRevision="${ci_revision}"
ci_first_jar="$(find app-bootstrap/build/libs -maxdepth 1 -type f -name '*.jar' ! -name '*-plain.jar' -print -quit)"
ci_first_hash="$(sha256sum "${ci_first_jar}" | cut -d' ' -f1)"

./gradlew clean :app-bootstrap:bootJar \
  --no-daemon --no-build-cache --rerun-tasks \
  -PreleaseVersion=0.0.1 -PgitRevision="${ci_revision}"
ci_second_jar="$(find app-bootstrap/build/libs -maxdepth 1 -type f -name '*.jar' ! -name '*-plain.jar' -print -quit)"
ci_second_hash="$(sha256sum "${ci_second_jar}" | cut -d' ' -f1)"

test "${ci_first_hash}" = "${ci_second_hash}"
echo "reproducible bootJar sha256=${ci_second_hash}"

Make both scripts executable:

chmod 0755 \
  .github/scripts/verify-gate-matrix.sh \
  .github/scripts/verify-reproducible-build.sh

Run:

bash .github/scripts/verify-gate-matrix.sh
python3 .harness/validators/validate_control_plane.py

Expected: both commands exit 0; this is the first point at which the full physical control-plane validator is green in Mode B.

  • Step 11: Exercise the workflow before protecting the branch

Human action: open a pull request containing only the reviewed recovery changes.

Expected in Gitea Actions:

control-plane-preflight: success
gradle-quality: success
container-build: success
release-gate: success

The dependency vulnerability workflow must also publish its documented blocking success status.

  • Step 12: Configure protected-branch requirements

Human repository-owner action: require the exact successful release-gate status and the exact dependency-vulnerability blocking status on main. Require CODEOWNERS review for the recovered policy, workflow, suppression, and public-path baseline paths.

Expected: a pull request cannot merge when either required status is absent, pending, or failed.

  • Step 13: Seed negative status exercises

Use separate temporary branches to prove:

  1. removing .tool-versions fails control-plane-preflight;
  2. creating .gitea/workflows fails control-plane-preflight;
  3. changing SECURITY_PUBLIC_PATHS without the snapshot fails gradle-quality;
  4. making one lockfile stale fails gradle-quality;
  5. adding a forbidden project edge fails gradle-quality;
  6. breaking the root Docker context fails container-build;
  7. every failure makes release-gate fail.

Expected: branch protection blocks all seven pull requests. Close the exercises without merging.

  • Step 14: Review checkpoint

Run:

python3 .harness/validators/validate_control_plane.py
bash .github/scripts/verify-gate-matrix.sh
git diff --check

Expected: all commands exit 0.

Task 7: Enforce Dependency Locks and Pause Renovate Autonomy

Files:

  • Modify: renovate.json:3-34

  • Modify: .github/workflows/ci-quality-gates.yml

  • Modify: .github/ci-gate-matrix.yml

  • Verify: all 19 src/**/gradle.lockfile files

  • Step 1: Disable every Renovate automerge path

Change the patch/pin/digest rule to:

{
  "description": "Security patch, pin, and digest updates require human review until Gitea required checks and strict lock refresh are proven.",
  "matchUpdateTypes": ["patch", "pin", "digest"],
  "automerge": false
}

Keep minor/major automerge disabled.

  • Step 2: Correct the dependency-model description

Replace the version-catalog claim with:

Renovate is primary over Dependabot for this repository's Gradle build scripts and per-leaf strict lockfiles. The repository does not currently use gradle/libs.versions.toml; direct declarations and all affected lockfiles must change together.

Expected: renovate.json no longer claims a non-existent version catalog.

  • Step 3: Validate JSON and count the lock contracts

Run:

jq -e . renovate.json > /dev/null
test "$(find src -name gradle.lockfile -type f | wc -l)" -eq 19

Expected: exit 0.

  • Step 4: Verify strict locks without writing

Run:

./src/gradlew -p src verifyDependencyLocks --no-daemon --console=plain

Expected: exit 0; no lockfile changes appear in git status.

  • Step 5: Exercise the supported lock refresh in an isolated branch

Check out the first real Renovate security pull-request branch created after Gitea enablement. Verify that its dependency declaration and affected lockfiles are both present, then run:

./src/gradlew -p src resolveAndLockAll --write-locks --no-daemon --console=plain
./src/gradlew -p src verifyDependencyLocks test check --no-daemon --console=plain

Expected: only the declaration and affected lockfiles change; all verification commands exit 0. Close the exercise without merging if it was created solely as a control test.

  • Step 6: Prove stale lock state is blocking

In a separate temporary branch based on the recovery branch before the Renovate update, remove this exact lock entry from src/application-core/gradle.lockfile without changing the declaration:

org.slf4j:slf4j-api:2.0.17=compileClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath

Then run:

./src/gradlew -p src verifyDependencyLocks --no-daemon --console=plain

Expected: non-zero exit naming missing or stale lock state; the Gitea gradle-quality and release-gate statuses fail.

  • Step 7: Keep automerge paused

Review the five re-enable conditions in the design. Record their evidence for the human owner, but leave "automerge": false in this recovery.

  • Step 8: Review checkpoint

Run:

jq -e '.packageRules | all(.automerge == false)' renovate.json
git diff --check

Expected: both commands exit 0.

Task 8: Documentation Parity and Full Verification

Files:

  • Modify: README.md:31-107

  • Modify: src/README.md:39-181

  • Modify: AGENTS.md only if the recovered authority requires a path or command correction

  • Modify: CLAUDE.md only if the recovered authority requires a path or command correction

  • Verify: all files changed by Tasks 1-7

  • Step 1: Correct root onboarding and CI ownership

Update README.md so it states:

  • Gitea 1.27.0 hosts the repository;

  • repository Actions and an online runner are operational prerequisites;

  • .github/workflows is canonical and .gitea/workflows must remain absent;

  • Docker builds use repository-root context;

  • task packet resolution requires the restored .harness;

  • release-gate and the vulnerability status are protected-branch requirements.

  • Step 2: Correct build and security guidance

Update src/README.md so it states:

  • .harness/project/modules.yaml, not a Gradle map, owns module edges;

  • verifyDependencyLocks is read-only and resolveAndLockAll --write-locks is the only refresh;

  • public-path verification never writes;

  • Gitea workflow discovery uses canonical .github/workflows only because no shadow directory exists;

  • CI automerge remains paused.

  • Step 3: Run physical, harness, and documentation validators

Run:

python3 .harness/validators/validate_control_plane.py
python3 -m unittest discover -s .harness/tests -p 'test_*.py'
python3 .harness/validators/validate_modules.py
python3 .harness/validators/validate_policy_parity.py
python3 .harness/generators/render_agents.py --check
bash .github/scripts/verify-gate-matrix.sh
./src/gradlew -p src verifyTrivyignore --no-daemon --console=plain

Expected: all commands exit 0.

  • Step 4: Run the complete Gradle evidence chain

Run:

./src/gradlew -p src projects --no-daemon --console=plain
./src/gradlew -p src verifyDependencyLocks --no-daemon --console=plain
./src/gradlew -p src verifyCleanArchitectureDependencies --no-daemon --console=plain
./src/gradlew -p src :app-bootstrap:test --tests '*CleanArchitectureTest' --no-daemon --console=plain
./src/gradlew -p src verifyPublicPathSnapshot --no-daemon --console=plain
./src/gradlew -p src verifyEnvKeys --no-daemon --console=plain
./src/gradlew -p src test --no-daemon --console=plain
./src/gradlew -p src check --no-daemon --console=plain

Expected: all commands exit 0; project discovery lists all 19 leaves; no task writes policy, snapshot, or lock state.

  • Step 5: Run Docker and reproducibility evidence

Run:

docker compose -f docker-compose.yml -f docker-compose.local.yml config --quiet
bash .github/scripts/verify-reproducible-build.sh
docker build -f src/Dockerfile.sample . --tag caskeleton-sample:final-verification

Expected: Compose syntax passes, reproducible production artifacts have matching hashes, and the sample image builds.

  • Step 6: Verify no shadow, secret, or stale documentation path remains

Run:

test ! -e .gitea/workflows
git grep -n 'docker build .* src/' -- README.md src/README.md src/Dockerfile src/Dockerfile.sample .github \
  && exit 1 || true
git grep -n 'gradle/libs.versions.toml' -- renovate.json \
  && exit 1 || true
git grep -nE '(GITEA_RUNNER_REGISTRATION_TOKEN=|Authorization: token )[A-Za-z0-9_-]{20,}' \
  -- . ':!docs/superpowers/**' \
  && exit 1 || true

Expected: exit 0 and no leaked token value or stale context/catalog claim.

  • Step 7: Perform architecture, specification, and quality review

Review in this order:

  1. recovered hashes and 2026-07-20 harness parity;
  2. current design acceptance criteria;
  3. Gitea workflow semantics and runner isolation;
  4. Gradle/Docker behavior and negative exercises;
  5. documentation and operational safety.

Expected: every blocking finding is fixed and the full relevant verification chain is rerun.

  • Step 8: Finalize the complete Mode B reconstruction inventory

When Mode B was selected, first record the final evidence paths in reconstructed provenance:

python3 - <<'PY'
import json
from pathlib import Path

path = Path(".harness/recovery-provenance.json")
provenance = json.loads(path.read_text(encoding="utf-8"))
provenance["finalized_after"] = "Tasks 3-8 verification"
provenance["final_inventory_evidence"] = (
    "/tmp/ca-control-plane-recovery/evidence/mode-b-final-reconstruction-sha256.txt"
)
provenance["final_inventory_checksum_evidence"] = (
    "/tmp/ca-control-plane-recovery/evidence/mode-b-final-reconstruction-sha256.txt.sha256"
)
path.write_text(
    json.dumps(provenance, indent=2, sort_keys=True) + "\n",
    encoding="utf-8",
)
PY
find \
  .harness \
  .agents \
  .claude \
  .codex \
  .github \
  .tool-versions \
  .trivyignore.yaml \
  .gitattributes \
  .dockerignore \
  docs/security/public-paths-snapshot.txt \
  -type f -print0 \
  | sort -z \
  | xargs -0 sha256sum \
  > /tmp/ca-control-plane-recovery/evidence/mode-b-final-reconstruction-sha256.txt
sha256sum \
  /tmp/ca-control-plane-recovery/evidence/mode-b-final-reconstruction-sha256.txt \
  > /tmp/ca-control-plane-recovery/evidence/mode-b-final-reconstruction-sha256.txt.sha256
for ci_inventory_prefix in .harness/ .agents/ .claude/ .codex/ .github/
do
  grep -Fq "  ${ci_inventory_prefix}" \
    /tmp/ca-control-plane-recovery/evidence/mode-b-final-reconstruction-sha256.txt
done
for ci_inventory_file in \
  .tool-versions \
  .trivyignore.yaml \
  .gitattributes \
  .dockerignore \
  docs/security/public-paths-snapshot.txt
do
  grep -Fq "  ${ci_inventory_file}" \
    /tmp/ca-control-plane-recovery/evidence/mode-b-final-reconstruction-sha256.txt
done
sha256sum --check \
  /tmp/ca-control-plane-recovery/evidence/mode-b-final-reconstruction-sha256.txt.sha256

Expected in Mode B: all commands exit 0; the complete post-change inventory includes every covered path and the final reconstructed provenance file itself. Mode A skips this step and retains its authoritative source/destination inventory plus ordinary review diffs for later changes.

  • Step 9: Prepare the human commit handoff

Run:

git status --short
git diff --stat
git diff --check

Expected: only reviewed recovery and CI-control-plane files are present; there are no agent-created commits. Provide the human owner with the task-packet hash, recovery hash inventory, exact commands and exits, Gitea run links, required-status evidence, failures, and remaining risks.

The documentation-authoring turn that created this plan does not write to the LLM Wiki. During future implementation, the executing controller follows the root AGENTS.md capture policy after all implementation and verification work is complete.