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— declarecontrolled-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 exclusionsdocs/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 intocheckand separate approved baseline generationsrc/Dockerfile:30-54— build from repository root while retaining/build/srcsrc/Dockerfile.sample:42-63— mirror the production builder layoutdocker-compose.yml:28-35— use repository-root context andsrc/Dockerfile.github/workflows/ci-quality-gates.yml— add control-plane preflight and stablerelease-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 pathsrenovate.json:3-34— correct the dependency model description and pause automergeREADME.md:31-107— correct quick start, Gitea CI, workflow, and Docker referencessrc/README.md:39-181— correct registry SSOT, lock, snapshot, and Gitea gate guidance
Explicitly forbidden
.gitea/workflows/— would shadow.github/workflowsunder Gitea's defaultWORKFLOW_DIRSsrc/.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
.githubtree 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/workflowsas 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:
- removing
.tool-versionsfailscontrol-plane-preflight; - creating
.gitea/workflowsfailscontrol-plane-preflight; - changing
SECURITY_PUBLIC_PATHSwithout the snapshot failsgradle-quality; - making one lockfile stale fails
gradle-quality; - adding a forbidden project edge fails
gradle-quality; - breaking the root Docker context fails
container-build; - every failure makes
release-gatefail.
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.lockfilefiles -
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.mdonly if the recovered authority requires a path or command correction -
Modify:
CLAUDE.mdonly 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/workflowsis canonical and.gitea/workflowsmust remain absent; -
Docker builds use repository-root context;
-
task packet resolution requires the restored
.harness; -
release-gateand 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; -
verifyDependencyLocksis read-only andresolveAndLockAll --write-locksis the only refresh; -
public-path verification never writes;
-
Gitea workflow discovery uses canonical
.github/workflowsonly 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:
- recovered hashes and 2026-07-20 harness parity;
- current design acceptance criteria;
- Gitea workflow semantics and runner isolation;
- Gradle/Docker behavior and negative exercises;
- 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.