# 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: ```bash 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: ```text /tmp/ca-control-plane-recovery/authoritative-root ``` Inspect it without changing the repository: ```bash 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: ```bash 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: ```bash 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: ```bash 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`: ```bash 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`: ```bash 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: ```bash 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: ```bash 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: ```bash 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: ```bash 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: ```bash 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: ```bash ./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: ```text /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-overlay.json ``` Then run the actual resolver and persist its output: ```bash 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: ```bash 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: ```bash 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: ```text 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: ```text .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: ```json { "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: ```bash 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: ```bash ./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: ```bash 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: ```text /tmp/ca-control-plane-recovery/evidence/ci-control-plane-recovery-overlay.json ``` Then run: ```bash 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: ```bash 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: ```bash 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: ```python 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: ```bash 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: ```json { "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: ```python #!/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: ```bash 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: ```bash 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: ```bash 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: ```text java temurin-21.0.11+10.0.LTS ``` Create `.gitattributes` with: ```gitattributes * text=auto eol=lf *.bat text eol=crlf *.jar binary *.png binary *.jpg binary *.jpeg binary *.gif binary ``` Create `.trivyignore.yaml` with: ```yaml 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: ```bash 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: ```python ["missing required file: docs/security/public-paths-snapshot.txt"] ``` Run: ```bash 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: ```text # 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: ```bash ./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: ```groovy 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: ```text updatePublicPathSnapshot requires -PapprovePublicPathChange ``` - [ ] **Step 7: Wire read-only verification into every leaf `check`** At `src/build.gradle:274-280`, add: ```groovy dependsOn rootProject.tasks.named('verifyPublicPathSnapshot') ``` Expected: `check` verifies but never updates the baseline. - [ ] **Step 8: Verify positive and negative behavior** Run: ```bash ./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: ```bash 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: ```bash 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: ```dockerignore .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: ```dockerfile 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: ```yaml 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: ```bash docker build -f src/Dockerfile . ``` Every sample build command must use: ```bash docker build -f src/Dockerfile.sample . ``` Expected: no tracked command retains `src/` as its Docker context. - [ ] **Step 7: Verify Compose and both images** Run: ```bash 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: ```bash 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: ```bash 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: ```text .github .github/scripts .github/workflows ``` Create `.github/CODEOWNERS` with: ```text /.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: ```markdown # 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: ```bash 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: ```bash 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: ```text .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: ```text 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: ```bash 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: ```bash 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: ```bash 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: ```yaml 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: ```yaml 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: ```yaml 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: ```yaml 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: ```bash #!/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: ```bash #!/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: ```bash chmod 0755 \ .github/scripts/verify-gate-matrix.sh \ .github/scripts/verify-reproducible-build.sh ``` Run: ```bash 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: ```text 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: ```bash 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: ```json { "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: ```text 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: ```bash 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: ```bash ./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: ```bash ./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: ```text org.slf4j:slf4j-api:2.0.17=compileClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath ``` Then run: ```bash ./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: ```bash 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: ```bash 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: ```bash ./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: ```bash 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: ```bash 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: ```bash 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: ```bash 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.