1979 lines
63 KiB
Markdown
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.
|