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

1979 lines
63 KiB
Markdown

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