@ccoalm/ccl-skills 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/marketplace/plugins/ccl-skills/agent-context/session-start.md +8 -7
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/hooks.json +22 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/remind-review-covers-head.sh +128 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/remind-untracked-background.sh +53 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_remind_review_covers_head.sh +104 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_remind_untracked_background.sh +75 -0
- package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/ccl-skills.ts +8 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/SKILL.md +6 -4
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/development-completion.md +13 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/staged-review-contract.md +78 -126
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/wording-only-review.md +136 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +521 -32
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_client_compat.py +25 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_client_order.sh +30 -15
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +439 -19
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_update_review_plan_intent.sh +14 -7
- package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md +6 -6
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/attention-budget-ratchet.md +1 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/dual-track-review-gate.md +48 -119
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-quickstart.md +9 -9
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/firing-point-placement.md +22 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +18 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/validation-and-landing.md +2 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check_review_evidence_present.py +122 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/contract-anchors.tsv +4 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/extraction_review_gate.sh +37 -8
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/register-firing-path-resolution.rb +102 -20
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_ai_coding_implementation_gates.sh +16 -27
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +3 -5
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_review_evidence_present.sh +81 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_extraction_review_gate.sh +137 -279
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_register_firing_path_resolution.sh +41 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_register_firing_path_wiring.sh +49 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/SKILL.md +3 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/closeout-reread.md +40 -0
- package/dist/assets/release.json +72 -52
- package/package.json +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/review_ledger_binding.py +0 -1242
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_review_ledger_binding.sh +0 -978
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_extraction_review_state.sh +0 -1477
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/validate_extraction_review_state.py +0 -1183
|
@@ -1,978 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Regression for the merge-side binding between the landing candidate and the
|
|
3
|
-
# review evidence that inspected it.
|
|
4
|
-
set -uo pipefail
|
|
5
|
-
|
|
6
|
-
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
7
|
-
GATE="$DIR/review_ledger_binding.py"
|
|
8
|
-
CONTROLLER="$DIR/../../code-review/scripts/review_gate.py"
|
|
9
|
-
WORK="$(mktemp -d "${TMPDIR:-/tmp}/review-ledger-binding.XXXXXX")"
|
|
10
|
-
trap 'rm -rf "$WORK"' EXIT
|
|
11
|
-
|
|
12
|
-
fails=0
|
|
13
|
-
check() { if eval "$2"; then echo "ok - $1"; else echo "FAIL - $1"; fails=$((fails+1)); fi; }
|
|
14
|
-
|
|
15
|
-
[ -f "$GATE" ] || { echo "FAIL: gate missing: $GATE" >&2; exit 1; }
|
|
16
|
-
[ -f "$CONTROLLER" ] || { echo "FAIL: controller missing: $CONTROLLER" >&2; exit 1; }
|
|
17
|
-
|
|
18
|
-
REPO="$WORK/repo"
|
|
19
|
-
mkdir -p "$REPO/skills/code-review/scripts" "$REPO/skills/skill-extraction-workflow/scripts" "$REPO/specs/round/evidence"
|
|
20
|
-
cp "$CONTROLLER" "$REPO/skills/code-review/scripts/review_gate.py"
|
|
21
|
-
# The gate delegates ledger acceptance to the validator; these stubs isolate the
|
|
22
|
-
# gate's own contract (recompute the packet, bind it, delegate) from the
|
|
23
|
-
# validator's fixture surface, which its own suite already covers.
|
|
24
|
-
cat >"$REPO/skills/skill-extraction-workflow/scripts/validate_extraction_review_state.py" <<'PY'
|
|
25
|
-
import sys
|
|
26
|
-
print("extraction_review_state_ok: stub accepted")
|
|
27
|
-
sys.exit(0)
|
|
28
|
-
PY
|
|
29
|
-
cat >"$WORK/reject-validator.py" <<'PY'
|
|
30
|
-
import sys
|
|
31
|
-
print("extraction_review_state_invalid: stub rejected")
|
|
32
|
-
sys.exit(1)
|
|
33
|
-
PY
|
|
34
|
-
|
|
35
|
-
git -C "$REPO" init -q .
|
|
36
|
-
git -C "$REPO" config user.email t@example.invalid
|
|
37
|
-
git -C "$REPO" config user.name tester
|
|
38
|
-
printf 'baseline\n' >"$REPO/skills/skill-extraction-workflow/SKILL.md"
|
|
39
|
-
git -C "$REPO" add -A
|
|
40
|
-
git -C "$REPO" commit -qm baseline
|
|
41
|
-
BASE="$(git -C "$REPO" rev-parse HEAD)"
|
|
42
|
-
|
|
43
|
-
run_gate() { python3 "$GATE" --repo-root "$REPO" "$@" 2>&1; }
|
|
44
|
-
|
|
45
|
-
out="$(run_gate --base "$BASE")"; rc=$?
|
|
46
|
-
check "an unchanged reviewed path needs no review evidence" \
|
|
47
|
-
'[ "$rc" = 0 ] && case "$out" in *"no reviewed-path change"*) true;; *) false;; esac'
|
|
48
|
-
|
|
49
|
-
out="$(CCL_SKILL_BASE_REF= run_gate)"; rc=$?
|
|
50
|
-
check "a missing base ref fails closed instead of recording a passing check" \
|
|
51
|
-
'[ "$rc" = 2 ] && case "$out" in *"nothing was checked"*) true;; *) false;; esac'
|
|
52
|
-
|
|
53
|
-
out="$(CCL_SKILL_BASE_REF= run_gate --allow-unevaluated)"; rc=$?
|
|
54
|
-
check "an event with genuinely no base must say so out loud to exit clean" \
|
|
55
|
-
'[ "$rc" = 0 ] && case "$out" in *"nothing was checked"*) true;; *) false;; esac'
|
|
56
|
-
|
|
57
|
-
out="$(CCL_SKILL_BASE_REF="$BASE" run_gate)"; rc=$?
|
|
58
|
-
check "the gate reads the base from the environment variable its diagnostic names" \
|
|
59
|
-
'[ "$rc" = 0 ] && case "$out" in *"no reviewed-path change"*) true;; *) false;; esac'
|
|
60
|
-
|
|
61
|
-
# The fix batch lands under the reviewed paths.
|
|
62
|
-
printf 'baseline\nlanded change\n' >"$REPO/skills/skill-extraction-workflow/SKILL.md"
|
|
63
|
-
git -C "$REPO" add -A
|
|
64
|
-
git -C "$REPO" commit -qm "landing change"
|
|
65
|
-
|
|
66
|
-
out="$(run_gate --base "$BASE")"; rc=$?
|
|
67
|
-
check "a reviewed-path change with no evidence fails closed" \
|
|
68
|
-
'[ "$rc" = 1 ] && case "$out" in *"no accepted review evidence binds the landing candidate"*) true;; *) false;; esac'
|
|
69
|
-
EXPECTED="$(run_gate --base "$BASE" --print-candidate)"
|
|
70
|
-
check "the gate can name the candidate hash the evidence must carry" \
|
|
71
|
-
'[ -n "$EXPECTED" ] && case "$out" in *"$EXPECTED"*) true;; *) false;; esac'
|
|
72
|
-
|
|
73
|
-
# Evidence for a different candidate must not satisfy the gate: this is the exact
|
|
74
|
-
# shape the gate exists to catch -- a chain that closed out on the pre-fix tree.
|
|
75
|
-
python3 - "$REPO/specs/round/evidence/stale-closeout.json" <<'JSONGEN'
|
|
76
|
-
import json, sys
|
|
77
|
-
from pathlib import Path
|
|
78
|
-
Path(sys.argv[1]).write_text(json.dumps({"schema_version": 3, "closeout_state": "ready_for_human_decision", "controller_receipts": [], "candidate_sha256": "0" * 64}))
|
|
79
|
-
JSONGEN
|
|
80
|
-
git -C "$REPO" add -A && git -C "$REPO" commit -qm "stale evidence"
|
|
81
|
-
out="$(run_gate --base "$BASE")"; rc=$?
|
|
82
|
-
check "a ledger bound to another candidate does not satisfy the gate" \
|
|
83
|
-
'[ "$rc" = 1 ] && case "$out" in *"no accepted review evidence"*) true;; *) false;; esac'
|
|
84
|
-
|
|
85
|
-
# Committing evidence must not move the candidate: evidence lives outside the
|
|
86
|
-
# reviewed paths, which is what lets a ledger record the tree that carries it.
|
|
87
|
-
EXPECTED_AFTER="$(run_gate --base "$BASE" --print-candidate)"
|
|
88
|
-
check "committing evidence outside the reviewed paths leaves the candidate unmoved" \
|
|
89
|
-
'[ "$EXPECTED_AFTER" = "$EXPECTED" ]'
|
|
90
|
-
|
|
91
|
-
# A ledger that binds the landing candidate but fails validation is reported as a
|
|
92
|
-
# rejected ledger, never silently ignored. Swapping the validator edits a reviewed
|
|
93
|
-
# path, so the candidate moves and the ledger is written against the moved hash.
|
|
94
|
-
cp "$WORK/reject-validator.py" "$REPO/skills/skill-extraction-workflow/scripts/validate_extraction_review_state.py"
|
|
95
|
-
git -C "$REPO" add -A && git -C "$REPO" commit -qm "rejecting validator"
|
|
96
|
-
REJECT_CANDIDATE="$(run_gate --base "$BASE" --print-candidate)"
|
|
97
|
-
check "editing a reviewed path moves the candidate the evidence must bind" \
|
|
98
|
-
'[ -n "$REJECT_CANDIDATE" ] && [ "$REJECT_CANDIDATE" != "$EXPECTED" ]'
|
|
99
|
-
python3 - "$REPO/specs/round/evidence/closeout.json" "$REJECT_CANDIDATE" <<'JSONGEN'
|
|
100
|
-
import json, sys
|
|
101
|
-
from pathlib import Path
|
|
102
|
-
Path(sys.argv[1]).write_text(json.dumps({"schema_version": 3, "closeout_state": "ready_for_human_decision", "controller_receipts": [], "candidate_sha256": sys.argv[2]}))
|
|
103
|
-
JSONGEN
|
|
104
|
-
git -C "$REPO" add -A && git -C "$REPO" commit -qm "rejected ledger"
|
|
105
|
-
out="$(run_gate --base "$BASE")"; rc=$?
|
|
106
|
-
check "a bound but invalid ledger is reported as rejected, not ignored" \
|
|
107
|
-
'[ "$rc" = 1 ] && case "$out" in *"rejected ledger"*"stub rejected"*) true;; *) false;; esac'
|
|
108
|
-
|
|
109
|
-
cat >"$REPO/skills/skill-extraction-workflow/scripts/validate_extraction_review_state.py" <<'ACCEPTSTUB'
|
|
110
|
-
import sys
|
|
111
|
-
print("extraction_review_state_ok: stub accepted")
|
|
112
|
-
sys.exit(0)
|
|
113
|
-
ACCEPTSTUB
|
|
114
|
-
git -C "$REPO" add -A && git -C "$REPO" commit -qm "accepting validator"
|
|
115
|
-
ACCEPT_CANDIDATE="$(run_gate --base "$BASE" --print-candidate)"
|
|
116
|
-
python3 - "$REPO/specs/round/evidence/closeout.json" "$ACCEPT_CANDIDATE" <<'JSONGEN'
|
|
117
|
-
import json, sys
|
|
118
|
-
from pathlib import Path
|
|
119
|
-
Path(sys.argv[1]).write_text(json.dumps({"schema_version": 3, "closeout_state": "ready_for_human_decision", "controller_receipts": [], "candidate_sha256": sys.argv[2]}))
|
|
120
|
-
JSONGEN
|
|
121
|
-
git -C "$REPO" add -A && git -C "$REPO" commit -qm "accepted ledger"
|
|
122
|
-
out="$(run_gate --base "$BASE")"; rc=$?
|
|
123
|
-
check "a validated ledger bound to the landing candidate passes the gate" \
|
|
124
|
-
'[ "$rc" = 0 ] && case "$out" in *"binds the landing candidate"*) true;; *) false;; esac'
|
|
125
|
-
|
|
126
|
-
# The wording-only lane records a single review receipt rather than a ledger.
|
|
127
|
-
rm "$REPO/specs/round/evidence/closeout.json"
|
|
128
|
-
python3 - "$REPO/specs/round/evidence/wording-review.json" "$ACCEPT_CANDIDATE" <<'JSONGEN'
|
|
129
|
-
import json, sys
|
|
130
|
-
from pathlib import Path
|
|
131
|
-
Path(sys.argv[1]).write_text(json.dumps({"schema_version": 3, "mode": "review", "candidate_sha256": sys.argv[2], "wording_only_proof_sha256": "b" * 64}))
|
|
132
|
-
JSONGEN
|
|
133
|
-
git -C "$REPO" add -A && git -C "$REPO" commit -qm "wording-only proof"
|
|
134
|
-
out="$(run_gate --base "$BASE")"; rc=$?
|
|
135
|
-
check "a hand-writable receipt-shaped file never satisfies the gate" \
|
|
136
|
-
'[ "$rc" = 1 ] && case "$out" in *"no accepted review evidence"*) true;; *) false;; esac'
|
|
137
|
-
|
|
138
|
-
# Precision: a wording-only receipt for a different candidate must not pass.
|
|
139
|
-
python3 - "$REPO/specs/round/evidence/wording-review.json" <<'PY'
|
|
140
|
-
import json, sys
|
|
141
|
-
from pathlib import Path
|
|
142
|
-
Path(sys.argv[1]).write_text(json.dumps({
|
|
143
|
-
"schema_version": 3,
|
|
144
|
-
"mode": "review",
|
|
145
|
-
"candidate_sha256": "1" * 64,
|
|
146
|
-
"wording_only_proof_sha256": "b" * 64,
|
|
147
|
-
}))
|
|
148
|
-
PY
|
|
149
|
-
git -C "$REPO" add -A && git -C "$REPO" commit -qm "stale wording-only proof"
|
|
150
|
-
out="$(run_gate --base "$BASE")"; rc=$?
|
|
151
|
-
check "a receipt-shaped file for another candidate does not satisfy the gate" \
|
|
152
|
-
'[ "$rc" = 1 ] && case "$out" in *"no accepted review evidence"*) true;; *) false;; esac'
|
|
153
|
-
|
|
154
|
-
# An option-shaped base must not reach git as an option: git diff with no revision
|
|
155
|
-
# compares the index to the working tree, which in a clean checkout reports no
|
|
156
|
-
# paths and would let the gate pass having compared nothing.
|
|
157
|
-
# The environment path is the one that reaches git without an argument parser in
|
|
158
|
-
# front of it, which is exactly how such a base arrives in CI.
|
|
159
|
-
for bad_base in --quiet --name-only refs/heads/does-not-exist; do
|
|
160
|
-
out="$(CCL_SKILL_BASE_REF="$bad_base" run_gate)"; rc=$?
|
|
161
|
-
check "an unresolvable base ($bad_base) is refused, not silently treated as no change" \
|
|
162
|
-
'[ "$rc" = 1 ] && case "$out" in *"does not resolve to a commit"*) true;; *) false;; esac'
|
|
163
|
-
done
|
|
164
|
-
|
|
165
|
-
out="$(run_gate --base "$BASE^{commit}")"; rc=$?
|
|
166
|
-
check "a revision expression that does resolve is still accepted" \
|
|
167
|
-
'[ "$rc" = 1 ] && case "$out" in *"no accepted review evidence"*) true;; *) false;; esac'
|
|
168
|
-
|
|
169
|
-
# The gate must not perturb the tree it hashes; asserting only that a hash comes
|
|
170
|
-
# back would stay green if bytecode writes or packet cleanup regressed.
|
|
171
|
-
before_state="$(git -C "$REPO" status --porcelain --ignored)"
|
|
172
|
-
run_gate --base "$BASE" --print-candidate >/dev/null 2>&1
|
|
173
|
-
after_state="$(git -C "$REPO" status --porcelain --ignored)"
|
|
174
|
-
check "running the gate leaves the hashed tree byte-identical" \
|
|
175
|
-
'[ "$before_state" = "$after_state" ]'
|
|
176
|
-
|
|
177
|
-
# What merges is the committed tree, so uncommitted evidence is not evidence about
|
|
178
|
-
# the landing candidate: reading it would accept a ledger nobody can find later.
|
|
179
|
-
printf '{"unfinished": true}\n' >"$REPO/specs/round/evidence/uncommitted.json"
|
|
180
|
-
out="$(run_gate --base "$BASE")"; rc=$?
|
|
181
|
-
check "an uncommitted evidence tree is refused rather than read from disk" \
|
|
182
|
-
'[ "$rc" = 1 ] && case "$out" in *"uncommitted changes"*) true;; *) false;; esac'
|
|
183
|
-
rm "$REPO/specs/round/evidence/uncommitted.json"
|
|
184
|
-
|
|
185
|
-
# An unrelated advance on the base branch must not restate what this branch is:
|
|
186
|
-
# measured against the tip it would, and evidence that is still correct would go
|
|
187
|
-
# stale every time somebody else merged.
|
|
188
|
-
BEFORE_ADVANCE="$(run_gate --base "$BASE" --print-candidate)"
|
|
189
|
-
git -C "$REPO" checkout -q -b other-work "$BASE"
|
|
190
|
-
printf 'unrelated\n' >"$REPO/skills/code-review/UNRELATED.md"
|
|
191
|
-
git -C "$REPO" add -A && git -C "$REPO" commit -qm "unrelated work on the base branch"
|
|
192
|
-
ADVANCED="$(git -C "$REPO" rev-parse HEAD)"
|
|
193
|
-
git -C "$REPO" checkout -q -
|
|
194
|
-
AFTER_ADVANCE="$(run_gate --base "$ADVANCED" --print-candidate)"
|
|
195
|
-
check "an unrelated advance on the base branch leaves the candidate unchanged" \
|
|
196
|
-
'[ -n "$BEFORE_ADVANCE" ] && [ "$AFTER_ADVANCE" = "$BEFORE_ADVANCE" ]'
|
|
197
|
-
|
|
198
|
-
# Scope: which paths the candidate is computed over. A whitelist of the paths the
|
|
199
|
-
# round happened to review leaves every other tracked path unbound, so a pull
|
|
200
|
-
# request can carry a ledger valid for its skill changes while also landing a root
|
|
201
|
-
# build script nobody reviewed -- the extra content does not move the candidate, so
|
|
202
|
-
# the existing ledger still passes. The bound set is therefore every tracked path
|
|
203
|
-
# except the evidence tree, which must stay outside it: evidence that moved the
|
|
204
|
-
# candidate it records could never be committed.
|
|
205
|
-
SCOPE="$WORK/scope-repo"
|
|
206
|
-
mkdir -p "$SCOPE/skills/code-review/scripts" "$SCOPE/skills/skill-extraction-workflow/scripts" "$SCOPE/specs/round/evidence" "$SCOPE/scripts"
|
|
207
|
-
cp "$CONTROLLER" "$SCOPE/skills/code-review/scripts/review_gate.py"
|
|
208
|
-
cat >"$SCOPE/skills/skill-extraction-workflow/scripts/validate_extraction_review_state.py" <<'PY'
|
|
209
|
-
import sys
|
|
210
|
-
print("extraction_review_state_ok: stub accepted")
|
|
211
|
-
sys.exit(0)
|
|
212
|
-
PY
|
|
213
|
-
git -C "$SCOPE" init -q .
|
|
214
|
-
git -C "$SCOPE" config user.email t@example.invalid
|
|
215
|
-
git -C "$SCOPE" config user.name tester
|
|
216
|
-
printf 'baseline\n' >"$SCOPE/skills/skill-extraction-workflow/SKILL.md"
|
|
217
|
-
printf 'test:\n\t@echo baseline\n' >"$SCOPE/Makefile"
|
|
218
|
-
printf 'baseline\n' >"$SCOPE/README.md"
|
|
219
|
-
printf 'baseline\n' >"$SCOPE/scripts/release.sh"
|
|
220
|
-
# Committed review history for the earlier round: a plan and a receipt that already
|
|
221
|
-
# exist at the base. These are what a later pull request must not be able to rewrite
|
|
222
|
-
# unnoticed.
|
|
223
|
-
printf 'earlier round plan\n' >"$SCOPE/specs/round/plan.md"
|
|
224
|
-
printf '{"schema_version": 3, "closeout_state": "ready_for_human_decision"}\n' \
|
|
225
|
-
>"$SCOPE/specs/round/evidence/history.json"
|
|
226
|
-
git -C "$SCOPE" add -A
|
|
227
|
-
git -C "$SCOPE" commit -qm baseline
|
|
228
|
-
SCOPE_BASE="$(git -C "$SCOPE" rev-parse HEAD)"
|
|
229
|
-
|
|
230
|
-
run_scope() { python3 "$GATE" --repo-root "$SCOPE" "$@" 2>&1; }
|
|
231
|
-
|
|
232
|
-
# Each of these is an executable or consumer-facing tracked path outside the
|
|
233
|
-
# reviewed-skill tree. Landing one with no evidence is the failure the whitelist
|
|
234
|
-
# admitted: before the bound set was inverted every one of them reported
|
|
235
|
-
# "no reviewed-path change" and merged unreviewed.
|
|
236
|
-
for unbound in Makefile README.md scripts/release.sh; do
|
|
237
|
-
git -C "$SCOPE" checkout -q -B "probe" "$SCOPE_BASE"
|
|
238
|
-
printf 'landed unreviewed\n' >>"$SCOPE/$unbound"
|
|
239
|
-
git -C "$SCOPE" add -A && git -C "$SCOPE" commit -qm "land $unbound with no evidence"
|
|
240
|
-
out="$(run_scope --base "$SCOPE_BASE")"; rc=$?
|
|
241
|
-
check "a change to $unbound alone needs review evidence like any other tracked path" \
|
|
242
|
-
'[ "$rc" = 1 ] && case "$out" in *"no accepted review evidence binds the landing candidate"*) true;; *) false;; esac'
|
|
243
|
-
done
|
|
244
|
-
|
|
245
|
-
# The other half of the same decision: a committed receipt stays excluded. If it did
|
|
246
|
-
# not, committing a ledger would move the candidate that ledger records and no
|
|
247
|
-
# ledger could ever bind its own landing candidate.
|
|
248
|
-
git -C "$SCOPE" checkout -q -B "evidence-only" "$SCOPE_BASE"
|
|
249
|
-
printf '{"schema_version": 3, "candidate_sha256": "%s"}\n' \
|
|
250
|
-
"0000000000000000000000000000000000000000000000000000000000000000" \
|
|
251
|
-
>"$SCOPE/specs/round/evidence/note.json"
|
|
252
|
-
git -C "$SCOPE" add -A && git -C "$SCOPE" commit -qm "land a receipt only"
|
|
253
|
-
out="$(run_scope --base "$SCOPE_BASE")"; rc=$?
|
|
254
|
-
check "a change confined to added receipts still reports no reviewed-path change" \
|
|
255
|
-
'[ "$rc" = 0 ] && case "$out" in *"no reviewed-path change"*) true;; *) false;; esac'
|
|
256
|
-
|
|
257
|
-
# The exclusion covers what this round ADDS, not the whole tree. Committed review
|
|
258
|
-
# history is the record the gate exists to protect, so rewriting or deleting an
|
|
259
|
-
# earlier round's plan or receipt has to reach the candidate like any other change.
|
|
260
|
-
for corruption in rewrite-plan rewrite-receipt delete-receipt; do
|
|
261
|
-
git -C "$SCOPE" checkout -q -B "corrupt" "$SCOPE_BASE"
|
|
262
|
-
case "$corruption" in
|
|
263
|
-
rewrite-plan) printf 'rewritten by a later round\n' >"$SCOPE/specs/round/plan.md";;
|
|
264
|
-
rewrite-receipt) printf '{"schema_version": 3, "closeout_state": "forged"}\n' \
|
|
265
|
-
>"$SCOPE/specs/round/evidence/history.json";;
|
|
266
|
-
delete-receipt) rm "$SCOPE/specs/round/evidence/history.json";;
|
|
267
|
-
esac
|
|
268
|
-
git -C "$SCOPE" add -A && git -C "$SCOPE" commit -qm "$corruption"
|
|
269
|
-
out="$(run_scope --base "$SCOPE_BASE")"; rc=$?
|
|
270
|
-
check "a later round cannot $corruption of committed review history without evidence" \
|
|
271
|
-
'[ "$rc" = 1 ] && case "$out" in *"no accepted review evidence binds the landing candidate"*) true;; *) false;; esac'
|
|
272
|
-
done
|
|
273
|
-
|
|
274
|
-
# The exclusion is about what a file IS, not where it sits. An evidence directory is
|
|
275
|
-
# where receipts belong, but the directory alone must not buy exclusion: otherwise a
|
|
276
|
-
# script, a fixture, or any other artifact committed there lands unreviewed while an
|
|
277
|
-
# unchanged ledger still passes.
|
|
278
|
-
for smuggled in payload.py fixture.bin notes.json; do
|
|
279
|
-
git -C "$SCOPE" checkout -q -B "smuggle" "$SCOPE_BASE"
|
|
280
|
-
mkdir -p "$SCOPE/specs/new-round/evidence"
|
|
281
|
-
case "$smuggled" in
|
|
282
|
-
payload.py) printf 'import os\nos.system("echo landed")\n' >"$SCOPE/specs/new-round/evidence/$smuggled";;
|
|
283
|
-
fixture.bin) printf 'not text at all\n' >"$SCOPE/specs/new-round/evidence/$smuggled";;
|
|
284
|
-
notes.json) printf '{"note": "json, but binds no candidate"}\n' >"$SCOPE/specs/new-round/evidence/$smuggled";;
|
|
285
|
-
esac
|
|
286
|
-
git -C "$SCOPE" add -A && git -C "$SCOPE" commit -qm "smuggle $smuggled"
|
|
287
|
-
out="$(run_scope --base "$SCOPE_BASE")"; rc=$?
|
|
288
|
-
check "an added $smuggled under an evidence directory is bound, not excluded" \
|
|
289
|
-
'[ "$rc" = 1 ] && case "$out" in *"no accepted review evidence binds the landing candidate"*) true;; *) false;; esac'
|
|
290
|
-
done
|
|
291
|
-
|
|
292
|
-
# The candidate has to be the tree a clean checkout recomputes. An uncommitted edit
|
|
293
|
-
# or a scratch file inside the bound paths would otherwise produce a hash only this
|
|
294
|
-
# working copy can reproduce, which the author then records in the ledger.
|
|
295
|
-
git -C "$SCOPE" checkout -q -B "dirty-probe" "$SCOPE_BASE"
|
|
296
|
-
printf 'scratch\n' >"$SCOPE/scratch-note.txt"
|
|
297
|
-
out="$(run_scope --base "$SCOPE_BASE" --print-candidate)"; rc=$?
|
|
298
|
-
check "an untracked file inside the bound paths refuses instead of printing an unreproducible hash" \
|
|
299
|
-
'[ "$rc" != 0 ] && case "$out" in *"uncommitted changes"*) true;; *) false;; esac'
|
|
300
|
-
rm "$SCOPE/scratch-note.txt"
|
|
301
|
-
printf 'unstaged edit\n' >>"$SCOPE/Makefile"
|
|
302
|
-
out="$(run_scope --base "$SCOPE_BASE" --print-candidate)"; rc=$?
|
|
303
|
-
check "an unstaged edit inside the bound paths refuses instead of printing an unreproducible hash" \
|
|
304
|
-
'[ "$rc" != 0 ] && case "$out" in *"uncommitted changes"*) true;; *) false;; esac'
|
|
305
|
-
git -C "$SCOPE" checkout -q -- Makefile
|
|
306
|
-
out="$(run_scope --base "$SCOPE_BASE" --print-candidate)"; rc=$?
|
|
307
|
-
check "the same clean checkout still reaches its normal verdict" \
|
|
308
|
-
'[ "$rc" = 0 ] && case "$out" in *"no reviewed-path change"*) true;; *) false;; esac'
|
|
309
|
-
|
|
310
|
-
# Partition: the candidate's Git identity is base..HEAD, but the packet a reviewer
|
|
311
|
-
# can read is capped at the controller's byte ceiling. A landing candidate larger
|
|
312
|
-
# than one packet used to be unlandable as one pull request -- the whole-candidate
|
|
313
|
-
# freeze failed and no ledger could ever bind it -- so authors split the pull
|
|
314
|
-
# request instead of the review. A committed landing partition manifest names
|
|
315
|
-
# path partitions that together cover every changed file exactly once, each bound
|
|
316
|
-
# by its own validated ledger; the gate recomputes every partition and refuses any
|
|
317
|
-
# manifest whose parts do not add up to the whole.
|
|
318
|
-
PART="$WORK/partition-repo"
|
|
319
|
-
mkdir -p "$PART/skills/code-review/scripts" "$PART/skills/skill-extraction-workflow/scripts" "$PART/specs/round/evidence" "$PART/lane-a" "$PART/lane-b"
|
|
320
|
-
cp "$CONTROLLER" "$PART/skills/code-review/scripts/review_gate.py"
|
|
321
|
-
cat >"$PART/skills/skill-extraction-workflow/scripts/validate_extraction_review_state.py" <<'PY'
|
|
322
|
-
import sys
|
|
323
|
-
print("extraction_review_state_ok: stub accepted")
|
|
324
|
-
sys.exit(0)
|
|
325
|
-
PY
|
|
326
|
-
git -C "$PART" init -q .
|
|
327
|
-
git -C "$PART" config user.email t@example.invalid
|
|
328
|
-
git -C "$PART" config user.name tester
|
|
329
|
-
printf 'baseline\n' >"$PART/skills/skill-extraction-workflow/SKILL.md"
|
|
330
|
-
printf 'baseline\n' >"$PART/lane-a/big.txt"
|
|
331
|
-
printf 'baseline\n' >"$PART/lane-b/big.txt"
|
|
332
|
-
git -C "$PART" add -A && git -C "$PART" commit -qm baseline
|
|
333
|
-
PART_BASE="$(git -C "$PART" rev-parse HEAD)"
|
|
334
|
-
|
|
335
|
-
run_part() { python3 "$GATE" --repo-root "$PART" "$@" 2>&1; }
|
|
336
|
-
|
|
337
|
-
# The manifest oracle mirrors the documented canonical form (sorted keys, compact
|
|
338
|
-
# separators, UTF-8) independently of the gate, so a drift in either side reds.
|
|
339
|
-
cat >"$WORK/write-manifest.py" <<'PY'
|
|
340
|
-
import hashlib, json, sys
|
|
341
|
-
out, base, aggregate = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
342
|
-
partitions = []
|
|
343
|
-
for spec in sys.argv[4:]:
|
|
344
|
-
paths, digest = spec.rsplit("=", 1)
|
|
345
|
-
partitions.append({"paths": paths.split(), "candidate_sha256": digest})
|
|
346
|
-
body = {"schema_version": 1, "kind": "landing_partition_manifest", "base": base, "partitions": partitions}
|
|
347
|
-
if aggregate == "AUTO":
|
|
348
|
-
canonical = json.dumps(body, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
|
|
349
|
-
aggregate = hashlib.sha256(canonical).hexdigest()
|
|
350
|
-
body["candidate_sha256"] = aggregate
|
|
351
|
-
with open(out, "w", encoding="utf-8") as handle:
|
|
352
|
-
json.dump(body, handle, indent=2)
|
|
353
|
-
handle.write("\n")
|
|
354
|
-
PY
|
|
355
|
-
write_closeout() { python3 - "$1" "$2" <<'PY'
|
|
356
|
-
import json, sys
|
|
357
|
-
from pathlib import Path
|
|
358
|
-
Path(sys.argv[1]).write_text(json.dumps({"schema_version": 3, "closeout_state": "ready_for_human_decision", "controller_receipts": [], "candidate_sha256": sys.argv[2]}))
|
|
359
|
-
PY
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
# Two lanes each change ~130 KB: either fits one packet, the pair does not.
|
|
363
|
-
python3 - "$PART" <<'PY'
|
|
364
|
-
import sys
|
|
365
|
-
from pathlib import Path
|
|
366
|
-
root = Path(sys.argv[1])
|
|
367
|
-
for lane in ("lane-a", "lane-b"):
|
|
368
|
-
lines = [f"{lane} line {index:06d} " + "x" * 40 for index in range(2200)]
|
|
369
|
-
(root / lane / "big.txt").write_text("\n".join(lines) + "\n")
|
|
370
|
-
PY
|
|
371
|
-
git -C "$PART" add -A && git -C "$PART" commit -qm "oversized landing"
|
|
372
|
-
|
|
373
|
-
out="$(run_part --base "$PART_BASE")"; rc=$?
|
|
374
|
-
check "a candidate too large for one packet is refused with the partition recipe, not a bare freeze error" \
|
|
375
|
-
'[ "$rc" = 1 ] && case "$out" in *"exceeds 200000 bytes"*"landing partition manifest"*) true;; *) false;; esac'
|
|
376
|
-
|
|
377
|
-
MANIFEST_JSON="$(run_part --base "$PART_BASE" --print-manifest --partition lane-a --partition lane-b)"; rc=$?
|
|
378
|
-
check "the gate renders a manifest for a partition that covers the whole candidate" \
|
|
379
|
-
'[ "$rc" = 0 ] && python3 -c "import json,sys; m=json.loads(sys.argv[1]); assert m[\"kind\"]==\"landing_partition_manifest\" and len(m[\"partitions\"])==2 and m[\"base\"]==sys.argv[2]" "$MANIFEST_JSON" "$PART_BASE"'
|
|
380
|
-
|
|
381
|
-
out="$(run_part --base "$PART_BASE" --print-manifest --partition lane-a)"; rc=$?
|
|
382
|
-
check "rendering refuses a partition that leaves a changed path uncovered" \
|
|
383
|
-
'[ "$rc" != 0 ] && case "$out" in *"uncovered"*"lane-b/big.txt"*) true;; *) false;; esac'
|
|
384
|
-
|
|
385
|
-
out="$(run_part --base "$PART_BASE" --print-manifest --partition lane-a --partition lane-a/big.txt lane-b)"; rc=$?
|
|
386
|
-
check "rendering refuses partitions that overlap on a changed path" \
|
|
387
|
-
'[ "$rc" != 0 ] && case "$out" in *"overlap"*"lane-a/big.txt"*) true;; *) false;; esac'
|
|
388
|
-
|
|
389
|
-
out="$(run_part --base "$PART_BASE" --print-manifest --partition lane-a --partition lane-a lane-b)"; rc=$?
|
|
390
|
-
check "rendering refuses the same path listed in two partitions before touching git" \
|
|
391
|
-
'[ "$rc" != 0 ] && case "$out" in *"listed twice"*"lane-a"*) true;; *) false;; esac'
|
|
392
|
-
|
|
393
|
-
HASH_A="$(run_part --base "$PART_BASE" --print-candidate --paths lane-a)"
|
|
394
|
-
HASH_B="$(run_part --base "$PART_BASE" --print-candidate --paths lane-b)"
|
|
395
|
-
check "a partition's hash is exactly what --print-candidate --paths already answers" \
|
|
396
|
-
'[ ${#HASH_A} = 64 ] && python3 -c "import json,sys; m=json.loads(sys.argv[1]); assert [p[\"candidate_sha256\"] for p in m[\"partitions\"]]==[sys.argv[2], sys.argv[3]]" "$MANIFEST_JSON" "$HASH_A" "$HASH_B"'
|
|
397
|
-
|
|
398
|
-
printf '%s\n' "$MANIFEST_JSON" >"$PART/specs/round/evidence/landing-partitions.json"
|
|
399
|
-
git -C "$PART" add -A && git -C "$PART" commit -qm "partition manifest"
|
|
400
|
-
out="$(run_part --base "$PART_BASE")"; rc=$?
|
|
401
|
-
check "a manifest whose partitions carry no validated ledger is refused, naming the partition" \
|
|
402
|
-
'[ "$rc" = 1 ] && case "$out" in *"no accepted ledger"*"lane-a"*) true;; *) false;; esac'
|
|
403
|
-
HASH_A_AFTER="$(run_part --base "$PART_BASE" --print-candidate --paths lane-a)"
|
|
404
|
-
check "committing the manifest does not move the partition it describes" \
|
|
405
|
-
'[ "$HASH_A_AFTER" = "$HASH_A" ]'
|
|
406
|
-
|
|
407
|
-
write_closeout "$PART/specs/round/evidence/closeout-a.json" "$HASH_A"
|
|
408
|
-
write_closeout "$PART/specs/round/evidence/closeout-b.json" "$HASH_B"
|
|
409
|
-
git -C "$PART" add -A && git -C "$PART" commit -qm "partition ledgers"
|
|
410
|
-
out="$(run_part --base "$PART_BASE")"; rc=$?
|
|
411
|
-
check "a complete manifest with a validated ledger per partition binds a candidate no single packet could" \
|
|
412
|
-
'[ "$rc" = 0 ] && case "$out" in *"binds the landing candidate"*"2 partitions"*) true;; *) false;; esac'
|
|
413
|
-
PART_GOOD="$(git -C "$PART" rev-parse HEAD)"
|
|
414
|
-
|
|
415
|
-
# Every way the parts can fail to add up to the whole is refused for its own
|
|
416
|
-
# reason. Each probe branches from the passing state so the cases stay independent.
|
|
417
|
-
MANIFEST="$PART/specs/round/evidence/landing-partitions.json"
|
|
418
|
-
probe_part() {
|
|
419
|
-
git -C "$PART" checkout -q -B "probe" "$PART_GOOD"
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
probe_part
|
|
423
|
-
python3 "$WORK/write-manifest.py" "$MANIFEST" "$PART_BASE" AUTO "lane-a=$HASH_A"
|
|
424
|
-
git -C "$PART" add -A && git -C "$PART" commit -qm "manifest drops a lane"
|
|
425
|
-
out="$(run_part --base "$PART_BASE")"; rc=$?
|
|
426
|
-
check "a manifest that leaves a changed path in no partition is refused" \
|
|
427
|
-
'[ "$rc" = 1 ] && case "$out" in *"uncovered"*"lane-b/big.txt"*) true;; *) false;; esac'
|
|
428
|
-
|
|
429
|
-
probe_part
|
|
430
|
-
python3 "$WORK/write-manifest.py" "$MANIFEST" "$PART_BASE" AUTO "lane-a=$HASH_A" "lane-a/big.txt lane-b=$HASH_B"
|
|
431
|
-
git -C "$PART" add -A && git -C "$PART" commit -qm "manifest overlaps"
|
|
432
|
-
out="$(run_part --base "$PART_BASE")"; rc=$?
|
|
433
|
-
check "a manifest whose partitions overlap is refused before any packet is frozen" \
|
|
434
|
-
'[ "$rc" = 1 ] && case "$out" in *"overlap"*"lane-a/big.txt"*) true;; *) false;; esac'
|
|
435
|
-
|
|
436
|
-
probe_part
|
|
437
|
-
printf 'moved after the manifest was written\n' >>"$PART/lane-a/big.txt"
|
|
438
|
-
git -C "$PART" add -A && git -C "$PART" commit -qm "candidate moves after manifest"
|
|
439
|
-
out="$(run_part --base "$PART_BASE")"; rc=$?
|
|
440
|
-
check "a partition whose recorded hash no longer reproduces is refused" \
|
|
441
|
-
'[ "$rc" = 1 ] && case "$out" in *"lane-a"*"does not reproduce"*) true;; *) false;; esac'
|
|
442
|
-
|
|
443
|
-
probe_part
|
|
444
|
-
python3 "$WORK/write-manifest.py" "$MANIFEST" "0000000000000000000000000000000000000000" AUTO "lane-a=$HASH_A" "lane-b=$HASH_B"
|
|
445
|
-
git -C "$PART" add -A && git -C "$PART" commit -qm "manifest names another base"
|
|
446
|
-
out="$(run_part --base "$PART_BASE")"; rc=$?
|
|
447
|
-
check "a manifest written against another base is refused" \
|
|
448
|
-
'[ "$rc" = 1 ] && case "$out" in *"base"*"fork point"*) true;; *) false;; esac'
|
|
449
|
-
|
|
450
|
-
probe_part
|
|
451
|
-
python3 "$WORK/write-manifest.py" "$MANIFEST" "$PART_BASE" "$(printf 'f%.0s' $(seq 64))" "lane-a=$HASH_A" "lane-b=$HASH_B"
|
|
452
|
-
git -C "$PART" add -A && git -C "$PART" commit -qm "manifest aggregate forged"
|
|
453
|
-
out="$(run_part --base "$PART_BASE")"; rc=$?
|
|
454
|
-
check "a manifest whose aggregate hash does not reproduce its partitions is refused" \
|
|
455
|
-
'[ "$rc" = 1 ] && case "$out" in *"aggregate"*) true;; *) false;; esac'
|
|
456
|
-
|
|
457
|
-
for bad_path in ":(exclude)lane-b" "-lane-b" "../lane-b" "/lane-b" "lane-*" "lane-?" "lane-[ab]" "lane-\\b"; do
|
|
458
|
-
probe_part
|
|
459
|
-
python3 "$WORK/write-manifest.py" "$MANIFEST" "$PART_BASE" AUTO "lane-a=$HASH_A" "$bad_path=$HASH_B"
|
|
460
|
-
git -C "$PART" add -A && git -C "$PART" commit -qm "manifest smuggles a pathspec"
|
|
461
|
-
out="$(run_part --base "$PART_BASE")"; rc=$?
|
|
462
|
-
check "a manifest partition path shaped like $bad_path is refused as a path, not handed to git" \
|
|
463
|
-
'[ "$rc" = 1 ] && case "$out" in *"partition path"*) true;; *) false;; esac'
|
|
464
|
-
done
|
|
465
|
-
|
|
466
|
-
out="$(run_part --base "$PART_BASE" --print-manifest --partition 'lane-*')"; rc=$?
|
|
467
|
-
check "rendering refuses a wildcard partition instead of letting git expand it" \
|
|
468
|
-
'[ "$rc" != 0 ] && case "$out" in *"partition path"*"wildcard"*) true;; *) false;; esac'
|
|
469
|
-
|
|
470
|
-
# Under a narrowed --paths scope the partition union must equal the reviewed
|
|
471
|
-
# changed set, not merely contain it: a partition naming `.` reaches lane-b even
|
|
472
|
-
# though only lane-a is under review, and that surplus is refused before any
|
|
473
|
-
# packet is frozen. The ledgers are removed so the single-ledger path cannot
|
|
474
|
-
# satisfy the narrowed scope first.
|
|
475
|
-
probe_part
|
|
476
|
-
git -C "$PART" rm -q "$PART/specs/round/evidence/closeout-a.json" "$PART/specs/round/evidence/closeout-b.json"
|
|
477
|
-
python3 "$WORK/write-manifest.py" "$MANIFEST" "$PART_BASE" AUTO ".=$(printf '0%.0s' $(seq 64))"
|
|
478
|
-
git -C "$PART" add -A && git -C "$PART" commit -qm "manifest reaches outside the reviewed scope"
|
|
479
|
-
out="$(run_part --base "$PART_BASE" --paths lane-a)"; rc=$?
|
|
480
|
-
check "a partition reaching changed paths outside a narrowed --paths scope is refused, not accepted as covering" \
|
|
481
|
-
'[ "$rc" = 1 ] && case "$out" in *"outside the reviewed scope"*"lane-b/big.txt"*) true;; *) false;; esac'
|
|
482
|
-
|
|
483
|
-
probe_part
|
|
484
|
-
git -C "$PART" rm -q "$PART/specs/round/evidence/closeout-b.json"
|
|
485
|
-
git -C "$PART" commit -qm "one ledger missing"
|
|
486
|
-
out="$(run_part --base "$PART_BASE")"; rc=$?
|
|
487
|
-
check "a manifest with one partition unbound is refused, naming that partition" \
|
|
488
|
-
'[ "$rc" = 1 ] && case "$out" in *"no accepted ledger"*"lane-b"*) true;; *) false;; esac'
|
|
489
|
-
|
|
490
|
-
git -C "$PART" checkout -q -B "landing" "$PART_GOOD"
|
|
491
|
-
out="$(run_part --base "$PART_BASE")"; rc=$?
|
|
492
|
-
check "the passing partition state still passes after the probes" \
|
|
493
|
-
'[ "$rc" = 0 ]'
|
|
494
|
-
|
|
495
|
-
# Chain: an integration branch accumulates several reviewed rounds, each merged
|
|
496
|
-
# with its own ledger bound at its own base, and is then promoted as one pull
|
|
497
|
-
# request. No single ledger binds the promotion candidate, and a path partition
|
|
498
|
-
# cannot either when two rounds append to the same file: that file's promotion
|
|
499
|
-
# diff is the sum of both appends and no round ever froze that sum. Every byte
|
|
500
|
-
# of the promotion reached the branch through a first-parent merge whose second
|
|
501
|
-
# parent is a reviewed head, so the gate walks that chain and rebinds each round
|
|
502
|
-
# in a detached checkout at the round's own base, requiring every merge to be
|
|
503
|
-
# exactly the automatic merge of its parents.
|
|
504
|
-
CHAIN="$WORK/chain-repo"
|
|
505
|
-
mkdir -p "$CHAIN/skills/code-review/scripts" "$CHAIN/skills/skill-extraction-workflow/scripts" "$CHAIN/specs"
|
|
506
|
-
cp "$CONTROLLER" "$CHAIN/skills/code-review/scripts/review_gate.py"
|
|
507
|
-
# This stub has a rule, unlike the accept-all stubs above, so a round can forge a
|
|
508
|
-
# ledger the real validator would reject and the suite can tell whose validator
|
|
509
|
-
# judged it.
|
|
510
|
-
STRICT_VALIDATOR='import json, sys
|
|
511
|
-
payload = json.load(open(sys.argv[1], encoding="utf-8"))
|
|
512
|
-
if payload.get("closeout_state") == "ready_for_human_decision":
|
|
513
|
-
print("extraction_review_state_ok: stub accepted")
|
|
514
|
-
sys.exit(0)
|
|
515
|
-
print("extraction_review_state_invalid: stub rejected")
|
|
516
|
-
sys.exit(1)
|
|
517
|
-
'
|
|
518
|
-
printf '%s' "$STRICT_VALIDATOR" >"$CHAIN/skills/skill-extraction-workflow/scripts/validate_extraction_review_state.py"
|
|
519
|
-
git -C "$CHAIN" init -q -b main .
|
|
520
|
-
git -C "$CHAIN" config user.email t@example.invalid
|
|
521
|
-
git -C "$CHAIN" config user.name tester
|
|
522
|
-
printf 'baseline\n' >"$CHAIN/skills/skill-extraction-workflow/SKILL.md"
|
|
523
|
-
printf '| row | owner |\n' >"$CHAIN/skills/skill-extraction-workflow/register.md"
|
|
524
|
-
printf 'baseline\n' >"$CHAIN/README.md"
|
|
525
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm baseline
|
|
526
|
-
CHAIN_BASE="$(git -C "$CHAIN" rev-parse HEAD)"
|
|
527
|
-
git -C "$CHAIN" checkout -q -b dev
|
|
528
|
-
|
|
529
|
-
run_chain() { python3 "$GATE" --repo-root "$CHAIN" "$@" 2>&1; }
|
|
530
|
-
|
|
531
|
-
# Land one reviewed round on the integration branch: branch, edit, ledger bound
|
|
532
|
-
# to the round's own candidate (its base is the integration tip it forked from),
|
|
533
|
-
# then a non-fast-forward merge so the round is one first-parent step.
|
|
534
|
-
land_round() {
|
|
535
|
-
local name="$1" ledger="$2"; shift 2
|
|
536
|
-
git -C "$CHAIN" checkout -q -b "round-$name" dev
|
|
537
|
-
"$@"
|
|
538
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "round $name"
|
|
539
|
-
if [ "$ledger" = with-ledger ]; then
|
|
540
|
-
local digest
|
|
541
|
-
digest="$(run_chain --base dev --print-candidate)"
|
|
542
|
-
mkdir -p "$CHAIN/specs/$name/evidence"
|
|
543
|
-
write_closeout "$CHAIN/specs/$name/evidence/closeout.json" "$digest"
|
|
544
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "round $name ledger"
|
|
545
|
-
fi
|
|
546
|
-
git -C "$CHAIN" checkout -q dev
|
|
547
|
-
git -C "$CHAIN" merge -q --no-ff -m "merge round $name" "round-$name"
|
|
548
|
-
}
|
|
549
|
-
edit_round_a() {
|
|
550
|
-
printf 'baseline\nround a\n' >"$CHAIN/skills/skill-extraction-workflow/SKILL.md"
|
|
551
|
-
printf '| round a | owner |\n' >>"$CHAIN/skills/skill-extraction-workflow/register.md"
|
|
552
|
-
}
|
|
553
|
-
edit_round_b() {
|
|
554
|
-
mkdir -p "$CHAIN/lane"
|
|
555
|
-
printf 'round b\n' >"$CHAIN/lane/file.txt"
|
|
556
|
-
printf '| round b | owner |\n' >>"$CHAIN/skills/skill-extraction-workflow/register.md"
|
|
557
|
-
}
|
|
558
|
-
land_round a with-ledger edit_round_a
|
|
559
|
-
land_round b with-ledger edit_round_b
|
|
560
|
-
CHAIN_TWO="$(git -C "$CHAIN" rev-parse HEAD)"
|
|
561
|
-
|
|
562
|
-
worktrees_before="$(git -C "$CHAIN" worktree list --porcelain)"
|
|
563
|
-
tree_before="$(git -C "$CHAIN" status --porcelain --ignored)"
|
|
564
|
-
out="$(run_chain --base "$CHAIN_BASE")"; rc=$?
|
|
565
|
-
check "two rounds appending to one file bind through the first-parent chain where no single ledger or path partition can" \
|
|
566
|
-
'[ "$rc" = 0 ] && case "$out" in *"first-parent chain"*"2 rounds"*"specs/b/evidence/closeout.json"*"specs/a/evidence/closeout.json"*) true;; *) false;; esac'
|
|
567
|
-
check "walking the chain leaves no detached checkout registered and the bound tree byte-identical" \
|
|
568
|
-
'[ "$(git -C "$CHAIN" worktree list --porcelain)" = "$worktrees_before" ] && [ "$(git -C "$CHAIN" status --porcelain --ignored)" = "$tree_before" ]'
|
|
569
|
-
|
|
570
|
-
out="$(run_chain --base "$CHAIN_BASE" --paths skills)"; rc=$?
|
|
571
|
-
check "a narrowed --paths scope does not evaluate the chain, because each round's ledger binds the whole round" \
|
|
572
|
-
'[ "$rc" = 1 ] && case "$out" in *"default path set"*) true;; *) false;; esac'
|
|
573
|
-
|
|
574
|
-
# The promotion pull request is brought up to date with its target: the target
|
|
575
|
-
# advanced, and the sync merge's second parent is already on the base. That step
|
|
576
|
-
# owes no evidence, but it must still be exactly the automatic merge.
|
|
577
|
-
git -C "$CHAIN" checkout -q main
|
|
578
|
-
printf 'baseline\nfixed on the target\n' >"$CHAIN/README.md"
|
|
579
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "target advances"
|
|
580
|
-
CHAIN_MAIN="$(git -C "$CHAIN" rev-parse HEAD)"
|
|
581
|
-
git -C "$CHAIN" checkout -q dev
|
|
582
|
-
git -C "$CHAIN" merge -q --no-ff -m "sync target into dev" main
|
|
583
|
-
out="$(run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
584
|
-
check "a sync merge of the advanced target on top of the chain owes no evidence and still binds" \
|
|
585
|
-
'[ "$rc" = 0 ] && case "$out" in *"first-parent chain"*"2 rounds"*"sync"*) true;; *) false;; esac'
|
|
586
|
-
CHAIN_GOOD="$(git -C "$CHAIN" rev-parse HEAD)"
|
|
587
|
-
|
|
588
|
-
# A round the integration branch advanced past before it merged: its fork point
|
|
589
|
-
# is not the merge's first parent, the merge is still the automatic one, and the
|
|
590
|
-
# round's ledger was bound at its own fork point.
|
|
591
|
-
git -C "$CHAIN" checkout -q -b round-late dev
|
|
592
|
-
printf 'round late\n' >"$CHAIN/lane/late.txt"
|
|
593
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "round late"
|
|
594
|
-
LATE_DIGEST="$(run_chain --base dev --print-candidate)"
|
|
595
|
-
mkdir -p "$CHAIN/specs/late/evidence"
|
|
596
|
-
write_closeout "$CHAIN/specs/late/evidence/closeout.json" "$LATE_DIGEST"
|
|
597
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "round late ledger"
|
|
598
|
-
git -C "$CHAIN" checkout -q dev
|
|
599
|
-
edit_round_c() { printf 'round c\n' >"$CHAIN/lane/c.txt"; }
|
|
600
|
-
land_round c with-ledger edit_round_c
|
|
601
|
-
git -C "$CHAIN" merge -q --no-ff -m "merge round late" round-late
|
|
602
|
-
out="$(run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
603
|
-
check "a round merged after the integration branch advanced still binds at its own fork point" \
|
|
604
|
-
'[ "$rc" = 0 ] && case "$out" in *"first-parent chain"*"4 rounds"*) true;; *) false;; esac'
|
|
605
|
-
CHAIN_ADVANCED="$(git -C "$CHAIN" rev-parse HEAD)"
|
|
606
|
-
|
|
607
|
-
# Every way a chain step can fail to be a reviewed round is refused for its own
|
|
608
|
-
# reason. Each probe branches from the passing state so the cases stay independent.
|
|
609
|
-
probe_chain() { git -C "$CHAIN" checkout -q -B dev "$CHAIN_GOOD"; }
|
|
610
|
-
|
|
611
|
-
probe_chain
|
|
612
|
-
edit_round_d() { printf 'round d\n' >"$CHAIN/lane/d.txt"; }
|
|
613
|
-
land_round d no-ledger edit_round_d
|
|
614
|
-
UNBOUND_MERGE="$(git -C "$CHAIN" rev-parse --short=12 HEAD)"
|
|
615
|
-
out="$(run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
616
|
-
check "a round with no accepted ledger at its own base refuses the whole chain, naming that round" \
|
|
617
|
-
'[ "$rc" = 1 ] && case "$out" in *"round $UNBOUND_MERGE"*"does not bind at its own base"*) true;; *) false;; esac'
|
|
618
|
-
|
|
619
|
-
# Evidence is read from the LANDING tree, not from the round's own checkout. A
|
|
620
|
-
# round that merged without its ledger is still a set of bytes some later review
|
|
621
|
-
# can freeze and inspect; a validator-accepted closeout for exactly that candidate,
|
|
622
|
-
# committed on the integration branch afterwards, binds it. A closeout for any
|
|
623
|
-
# other digest does not, wherever it sits — the candidate hash, not the file's
|
|
624
|
-
# location, is what makes a ledger evidence. The retro ledger lands as a round of
|
|
625
|
-
# its own (a merge whose only change is receipt-shaped evidence), because a
|
|
626
|
-
# direct commit on the integration branch is refused for its own reason.
|
|
627
|
-
D_BASE="$(git -C "$CHAIN" rev-parse HEAD^1)"
|
|
628
|
-
git -C "$CHAIN" checkout -q round-d
|
|
629
|
-
D_DIGEST="$(run_chain --base "$D_BASE" --print-candidate)"
|
|
630
|
-
git -C "$CHAIN" checkout -q dev
|
|
631
|
-
edit_retro_wrong() {
|
|
632
|
-
mkdir -p "$CHAIN/specs/d-retro-wrong/evidence"
|
|
633
|
-
write_closeout "$CHAIN/specs/d-retro-wrong/evidence/closeout.json" "$(printf '%064d' 7)"
|
|
634
|
-
}
|
|
635
|
-
land_round d-retro-wrong no-ledger edit_retro_wrong
|
|
636
|
-
out="$(run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
637
|
-
check "a later closeout for a different digest does not bind the unbound round" \
|
|
638
|
-
'[ "$rc" = 1 ] && case "$out" in *"round $UNBOUND_MERGE"*"does not bind at its own base"*) true;; *) false;; esac'
|
|
639
|
-
# The right digest in a closeout the validator rejects is still not evidence.
|
|
640
|
-
edit_retro_rejected() {
|
|
641
|
-
mkdir -p "$CHAIN/specs/d-retro-rejected/evidence"
|
|
642
|
-
python3 - "$CHAIN/specs/d-retro-rejected/evidence/closeout.json" "$D_DIGEST" <<'PY'
|
|
643
|
-
import json, sys
|
|
644
|
-
from pathlib import Path
|
|
645
|
-
Path(sys.argv[1]).write_text(json.dumps({"schema_version": 3, "closeout_state": "forged", "controller_receipts": [], "candidate_sha256": sys.argv[2]}))
|
|
646
|
-
PY
|
|
647
|
-
}
|
|
648
|
-
land_round d-retro-rejected no-ledger edit_retro_rejected
|
|
649
|
-
out="$(run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
650
|
-
check "a later closeout for the right digest that the validator rejects does not bind the unbound round" \
|
|
651
|
-
'[ "$rc" = 1 ] && case "$out" in *"round $UNBOUND_MERGE"*"does not bind at its own base"*) true;; *) false;; esac'
|
|
652
|
-
# Evidence in the landing tree must be COMMITTED there, exactly as before: an
|
|
653
|
-
# untracked closeout for the right digest is refused outright, not read from disk.
|
|
654
|
-
mkdir -p "$CHAIN/specs/d-retro/evidence"
|
|
655
|
-
write_closeout "$CHAIN/specs/d-retro/evidence/closeout.json" "$D_DIGEST"
|
|
656
|
-
out="$(run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
657
|
-
check "an uncommitted closeout in the landing tree is refused before any round is bound" \
|
|
658
|
-
'[ "$rc" != 0 ] && case "$out" in *"uncommitted changes"*) true;; *) false;; esac'
|
|
659
|
-
rm -r "$CHAIN/specs/d-retro"
|
|
660
|
-
edit_retro_right() {
|
|
661
|
-
mkdir -p "$CHAIN/specs/d-retro/evidence"
|
|
662
|
-
write_closeout "$CHAIN/specs/d-retro/evidence/closeout.json" "$D_DIGEST"
|
|
663
|
-
}
|
|
664
|
-
land_round d-retro no-ledger edit_retro_right
|
|
665
|
-
out="$(run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
666
|
-
check "a validator-accepted closeout for the round's own candidate, committed on the integration branch after the merge, binds it through the chain" \
|
|
667
|
-
'[ "$rc" = 0 ] && case "$out" in *"first-parent chain"*"specs/d-retro/evidence/closeout.json"*) true;; *) false;; esac'
|
|
668
|
-
# Later evidence changes where a ledger may be found, never what the round is:
|
|
669
|
-
# the unbound round's candidate at its own base hashes exactly as it did before
|
|
670
|
-
# any retro ledger existed, so its packet, base and excludes are untouched.
|
|
671
|
-
git -C "$CHAIN" checkout -q round-d
|
|
672
|
-
D_DIGEST_AFTER="$(run_chain --base "$D_BASE" --print-candidate)"
|
|
673
|
-
git -C "$CHAIN" checkout -q dev
|
|
674
|
-
check "the round's candidate hash is unchanged by evidence committed later in the landing tree" \
|
|
675
|
-
'[ "$D_DIGEST_AFTER" = "$D_DIGEST" ]'
|
|
676
|
-
|
|
677
|
-
probe_chain
|
|
678
|
-
printf 'pushed straight to the integration branch\n' >"$CHAIN/lane/direct.txt"
|
|
679
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "direct commit on dev"
|
|
680
|
-
DIRECT="$(git -C "$CHAIN" rev-parse --short=12 HEAD)"
|
|
681
|
-
out="$(run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
682
|
-
check "a non-merge commit on the integration branch is refused: nothing reviewed binds its content" \
|
|
683
|
-
'[ "$rc" = 1 ] && case "$out" in *"$DIRECT is not a merge commit"*) true;; *) false;; esac'
|
|
684
|
-
|
|
685
|
-
probe_chain
|
|
686
|
-
git -C "$CHAIN" checkout -q -b round-evil dev
|
|
687
|
-
printf 'round evil\n' >"$CHAIN/lane/evil.txt"
|
|
688
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "round evil"
|
|
689
|
-
EVIL_DIGEST="$(run_chain --base dev --print-candidate)"
|
|
690
|
-
mkdir -p "$CHAIN/specs/evil/evidence"
|
|
691
|
-
write_closeout "$CHAIN/specs/evil/evidence/closeout.json" "$EVIL_DIGEST"
|
|
692
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "round evil ledger"
|
|
693
|
-
git -C "$CHAIN" checkout -q dev
|
|
694
|
-
git -C "$CHAIN" merge -q --no-ff --no-commit round-evil
|
|
695
|
-
printf 'slipped into the merge commit itself\n' >"$CHAIN/lane/smuggled.txt"
|
|
696
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "merge round evil (with extra content)"
|
|
697
|
-
EVIL_MERGE="$(git -C "$CHAIN" rev-parse --short=12 HEAD)"
|
|
698
|
-
out="$(run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
699
|
-
check "a merge whose tree is not the automatic merge of its parents is refused, naming the merge" \
|
|
700
|
-
'[ "$rc" = 1 ] && case "$out" in *"$EVIL_MERGE"*"automatic merge"*) true;; *) false;; esac'
|
|
701
|
-
|
|
702
|
-
probe_chain
|
|
703
|
-
git -C "$CHAIN" checkout -q -b round-conflict "$CHAIN_TWO"
|
|
704
|
-
printf 'baseline\nconflicting fix\n' >"$CHAIN/README.md"
|
|
705
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "round conflict"
|
|
706
|
-
CONFLICT_DIGEST="$(run_chain --base "$CHAIN_TWO" --print-candidate)"
|
|
707
|
-
mkdir -p "$CHAIN/specs/conflict/evidence"
|
|
708
|
-
write_closeout "$CHAIN/specs/conflict/evidence/closeout.json" "$CONFLICT_DIGEST"
|
|
709
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "round conflict ledger"
|
|
710
|
-
git -C "$CHAIN" checkout -q dev
|
|
711
|
-
git -C "$CHAIN" merge -q --no-ff --no-commit round-conflict >/dev/null 2>&1 || true
|
|
712
|
-
printf 'baseline\nresolved by hand\n' >"$CHAIN/README.md"
|
|
713
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "merge round conflict (resolved by hand)"
|
|
714
|
-
CONFLICT_MERGE="$(git -C "$CHAIN" rev-parse --short=12 HEAD)"
|
|
715
|
-
out="$(run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
716
|
-
check "a merge that needed hand resolution is refused: the resolution is content no round reviewed" \
|
|
717
|
-
'[ "$rc" = 1 ] && case "$out" in *"$CONFLICT_MERGE"*"automatic merge"*) true;; *) false;; esac'
|
|
718
|
-
|
|
719
|
-
probe_chain
|
|
720
|
-
git -C "$CHAIN" checkout -q -b round-stale dev
|
|
721
|
-
printf 'round stale\n' >"$CHAIN/lane/stale.txt"
|
|
722
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "round stale"
|
|
723
|
-
STALE_DIGEST="$(run_chain --base dev --print-candidate)"
|
|
724
|
-
mkdir -p "$CHAIN/specs/stale/evidence"
|
|
725
|
-
write_closeout "$CHAIN/specs/stale/evidence/closeout.json" "$STALE_DIGEST"
|
|
726
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "round stale ledger"
|
|
727
|
-
printf 'edited after the review\n' >>"$CHAIN/lane/stale.txt"
|
|
728
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "round stale moves after its ledger"
|
|
729
|
-
git -C "$CHAIN" checkout -q dev
|
|
730
|
-
git -C "$CHAIN" merge -q --no-ff -m "merge round stale" round-stale
|
|
731
|
-
STALE_MERGE="$(git -C "$CHAIN" rev-parse --short=12 HEAD)"
|
|
732
|
-
out="$(run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
733
|
-
check "a round whose ledger binds a candidate it later moved away from is refused" \
|
|
734
|
-
'[ "$rc" = 1 ] && case "$out" in *"round $STALE_MERGE"*"does not bind at its own base"*) true;; *) false;; esac'
|
|
735
|
-
|
|
736
|
-
# A round is judged with the landing tree's controller and validator, never its
|
|
737
|
-
# own: a round that installs an accept-all validator in its own branch, forges a
|
|
738
|
-
# ledger the real validator rejects, and is later followed by a round restoring
|
|
739
|
-
# the real validator must not bind through history's tools.
|
|
740
|
-
probe_chain
|
|
741
|
-
git -C "$CHAIN" checkout -q -b round-forged dev
|
|
742
|
-
printf 'round forged\n' >"$CHAIN/lane/forged.txt"
|
|
743
|
-
printf 'import sys\nprint("extraction_review_state_ok: hollow validator accepts everything")\nsys.exit(0)\n' \
|
|
744
|
-
>"$CHAIN/skills/skill-extraction-workflow/scripts/validate_extraction_review_state.py"
|
|
745
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "round forged"
|
|
746
|
-
FORGED_DIGEST="$(run_chain --base dev --print-candidate)"
|
|
747
|
-
mkdir -p "$CHAIN/specs/forged/evidence"
|
|
748
|
-
python3 - "$CHAIN/specs/forged/evidence/closeout.json" "$FORGED_DIGEST" <<'PY'
|
|
749
|
-
import json, sys
|
|
750
|
-
from pathlib import Path
|
|
751
|
-
Path(sys.argv[1]).write_text(json.dumps({"schema_version": 3, "closeout_state": "forged", "controller_receipts": [], "candidate_sha256": sys.argv[2]}))
|
|
752
|
-
PY
|
|
753
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "round forged ledger"
|
|
754
|
-
git -C "$CHAIN" checkout -q dev
|
|
755
|
-
git -C "$CHAIN" merge -q --no-ff -m "merge round forged" round-forged
|
|
756
|
-
FORGED_MERGE="$(git -C "$CHAIN" rev-parse --short=12 HEAD)"
|
|
757
|
-
edit_round_restore() { printf '%s' "$STRICT_VALIDATOR" >"$CHAIN/skills/skill-extraction-workflow/scripts/validate_extraction_review_state.py"; }
|
|
758
|
-
land_round restore with-ledger edit_round_restore
|
|
759
|
-
out="$(run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
760
|
-
check "a round that forged a ledger under its own hollow validator is refused even after a later round restored the real one" \
|
|
761
|
-
'[ "$rc" = 1 ] && case "$out" in *"round $FORGED_MERGE"*"does not bind at its own base"*) true;; *) false;; esac'
|
|
762
|
-
|
|
763
|
-
# The walk is bounded at exactly 64 steps: a chain of 64 passes, 65 is refused.
|
|
764
|
-
# Receipt-only rounds keep the fixture fast, since a round whose only change is
|
|
765
|
-
# an excluded receipt binds as "no reviewed-path change" without a freeze.
|
|
766
|
-
probe_chain
|
|
767
|
-
fill_round() {
|
|
768
|
-
git -C "$CHAIN" checkout -q -b "round-fill-$1" dev
|
|
769
|
-
mkdir -p "$CHAIN/specs/fill-$1/evidence"
|
|
770
|
-
printf '{"schema_version": 3, "candidate_sha256": "%s"}\n' "$(printf '0%.0s' $(seq 64))" >"$CHAIN/specs/fill-$1/evidence/note.json"
|
|
771
|
-
git -C "$CHAIN" add -A && git -C "$CHAIN" commit -qm "fill $1"
|
|
772
|
-
git -C "$CHAIN" checkout -q dev
|
|
773
|
-
git -C "$CHAIN" merge -q --no-ff -m "merge fill $1" "round-fill-$1"
|
|
774
|
-
}
|
|
775
|
-
for index in $(seq 1 61); do fill_round "$index"; done
|
|
776
|
-
out="$(run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
777
|
-
check "a chain of exactly 64 steps is still walked and binds" \
|
|
778
|
-
'[ "$rc" = 0 ] && case "$out" in *"first-parent chain"*"63 rounds"*) true;; *) false;; esac'
|
|
779
|
-
fill_round 62
|
|
780
|
-
out="$(run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
781
|
-
check "a chain of 65 steps is refused as longer than the bound" \
|
|
782
|
-
'[ "$rc" = 1 ] && case "$out" in *"more than 64 steps"*) true;; *) false;; esac'
|
|
783
|
-
|
|
784
|
-
# A checkout this run cannot release is an error, never a pass: the removal's
|
|
785
|
-
# result is checked and the registry read back, and nothing prunes registrations
|
|
786
|
-
# the run did not create. A git shim refuses only `worktree remove`.
|
|
787
|
-
git -C "$CHAIN" checkout -q -B dev "$CHAIN_GOOD"
|
|
788
|
-
REAL_GIT="$(command -v git)"
|
|
789
|
-
mkdir -p "$WORK/fakegit" "$WORK/shim-tmp"
|
|
790
|
-
cat >"$WORK/fakegit/git" <<EOF
|
|
791
|
-
#!/usr/bin/env bash
|
|
792
|
-
if [ "\$1" = "-C" ] && [ "\$3" = "worktree" ] && [ "\$4" = "remove" ]; then
|
|
793
|
-
echo "shim: refusing to remove the worktree" >&2
|
|
794
|
-
exit 1
|
|
795
|
-
fi
|
|
796
|
-
exec "$REAL_GIT" "\$@"
|
|
797
|
-
EOF
|
|
798
|
-
chmod +x "$WORK/fakegit/git"
|
|
799
|
-
out="$(PATH="$WORK/fakegit:$PATH" TMPDIR="$WORK/shim-tmp" run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
800
|
-
check "a detached checkout that cannot be released turns the verdict into an error, never a pass" \
|
|
801
|
-
'[ "$rc" != 0 ] && case "$out" in *"cannot release the detached checkout"*) case "$out" in *review_ledger_binding_ok*) false;; *) true;; esac;; *) false;; esac'
|
|
802
|
-
git -C "$CHAIN" worktree prune
|
|
803
|
-
|
|
804
|
-
# `git worktree add` can register and populate the checkout and still return
|
|
805
|
-
# nonzero. The failure is an error either way, but the registration it left
|
|
806
|
-
# behind must be released and verified, not merely have its directory deleted.
|
|
807
|
-
mkdir -p "$WORK/fakegit-add" "$WORK/shim-add-tmp"
|
|
808
|
-
cat >"$WORK/fakegit-add/git" <<EOF
|
|
809
|
-
#!/usr/bin/env bash
|
|
810
|
-
if [ "\$1" = "-C" ] && [ "\$3" = "worktree" ] && [ "\$4" = "add" ]; then
|
|
811
|
-
"$REAL_GIT" "\$@"
|
|
812
|
-
echo "shim: worktree add failed after registering" >&2
|
|
813
|
-
exit 1
|
|
814
|
-
fi
|
|
815
|
-
exec "$REAL_GIT" "\$@"
|
|
816
|
-
EOF
|
|
817
|
-
chmod +x "$WORK/fakegit-add/git"
|
|
818
|
-
worktrees_before="$(git -C "$CHAIN" worktree list --porcelain)"
|
|
819
|
-
out="$(PATH="$WORK/fakegit-add:$PATH" TMPDIR="$WORK/shim-add-tmp" run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
820
|
-
check "a checkout creation that fails after registering is an error and leaves no registration or directory behind" \
|
|
821
|
-
'[ "$rc" != 0 ] && case "$out" in *"cannot check out round head"*) case "$out" in *review_ledger_binding_ok*) false;; *) true;; esac;; *) false;; esac && [ "$(git -C "$CHAIN" worktree list --porcelain)" = "$worktrees_before" ] && [ -z "$(ls -A "$WORK/shim-add-tmp")" ]'
|
|
822
|
-
git -C "$CHAIN" worktree prune
|
|
823
|
-
|
|
824
|
-
# The benign sibling: an add that fails before registering anything must error
|
|
825
|
-
# the same way, leave the registry untouched, and remove the directory this run
|
|
826
|
-
# created for it, without reporting a release problem it does not have.
|
|
827
|
-
mkdir -p "$WORK/fakegit-noadd" "$WORK/shim-noadd-tmp"
|
|
828
|
-
cat >"$WORK/fakegit-noadd/git" <<EOF
|
|
829
|
-
#!/usr/bin/env bash
|
|
830
|
-
if [ "\$1" = "-C" ] && [ "\$3" = "worktree" ] && [ "\$4" = "add" ]; then
|
|
831
|
-
echo "shim: worktree add refused before registering" >&2
|
|
832
|
-
exit 1
|
|
833
|
-
fi
|
|
834
|
-
exec "$REAL_GIT" "\$@"
|
|
835
|
-
EOF
|
|
836
|
-
chmod +x "$WORK/fakegit-noadd/git"
|
|
837
|
-
worktrees_before="$(git -C "$CHAIN" worktree list --porcelain)"
|
|
838
|
-
out="$(PATH="$WORK/fakegit-noadd:$PATH" TMPDIR="$WORK/shim-noadd-tmp" run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
839
|
-
check "a checkout creation that fails before registering errors cleanly with no registration, no directory, and no release complaint" \
|
|
840
|
-
'[ "$rc" != 0 ] && case "$out" in *"cannot check out round head"*) case "$out" in *"could not be released"*|*review_ledger_binding_ok*) false;; *) true;; esac;; *) false;; esac && [ "$(git -C "$CHAIN" worktree list --porcelain)" = "$worktrees_before" ] && [ -z "$(ls -A "$WORK/shim-noadd-tmp")" ]'
|
|
841
|
-
|
|
842
|
-
# The registry is read NUL-terminated: line-oriented porcelain cannot carry a
|
|
843
|
-
# path with a newline (the line splits; a tab is printed raw on the git in use,
|
|
844
|
-
# and quoting depends on version and core.quotePath), so a registration under
|
|
845
|
-
# such a temporary directory would match neither spelling and read as gone.
|
|
846
|
-
# Here the add registers and fails, the remove is refused, and the surviving
|
|
847
|
-
# registration must be reported.
|
|
848
|
-
TAB_TMP="$WORK/shim nl
|
|
849
|
-
dir"
|
|
850
|
-
mkdir -p "$WORK/fakegit-tab" "$TAB_TMP"
|
|
851
|
-
cat >"$WORK/fakegit-tab/git" <<EOF
|
|
852
|
-
#!/usr/bin/env bash
|
|
853
|
-
if [ "\$1" = "-C" ] && [ "\$3" = "worktree" ] && [ "\$4" = "add" ]; then
|
|
854
|
-
"$REAL_GIT" "\$@"
|
|
855
|
-
echo "shim: worktree add failed after registering" >&2
|
|
856
|
-
exit 1
|
|
857
|
-
fi
|
|
858
|
-
if [ "\$1" = "-C" ] && [ "\$3" = "worktree" ] && [ "\$4" = "remove" ]; then
|
|
859
|
-
echo "shim: refusing to remove the worktree" >&2
|
|
860
|
-
exit 1
|
|
861
|
-
fi
|
|
862
|
-
exec "$REAL_GIT" "\$@"
|
|
863
|
-
EOF
|
|
864
|
-
chmod +x "$WORK/fakegit-tab/git"
|
|
865
|
-
worktrees_before="$(git -C "$CHAIN" worktree list --porcelain)"
|
|
866
|
-
out="$(PATH="$WORK/fakegit-tab:$PATH" TMPDIR="$TAB_TMP" run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
867
|
-
check "a surviving registration under a newline-bearing temporary path is still reported, not read as gone through line-oriented porcelain" \
|
|
868
|
-
'[ "$rc" != 0 ] && case "$out" in *"cannot check out round head"*"could not be released"*"still registered"*) true;; *) false;; esac'
|
|
869
|
-
rm -rf "$TAB_TMP"
|
|
870
|
-
git -C "$CHAIN" worktree prune
|
|
871
|
-
# This case deliberately leaves a registration behind, so its own cleanup is
|
|
872
|
-
# what restores the fixture. Prune only drops registrations whose directory is
|
|
873
|
-
# already gone, so the directory must go first; asserting the restoration keeps
|
|
874
|
-
# a later reordering from silently handing every following case a mutated
|
|
875
|
-
# registry.
|
|
876
|
-
check "the newline-path case restores the fixture's worktree registry for the cases that follow" \
|
|
877
|
-
'[ "$(git -C "$CHAIN" worktree list --porcelain)" = "$worktrees_before" ]'
|
|
878
|
-
|
|
879
|
-
git -C "$CHAIN" checkout -q -B dev "$CHAIN_ADVANCED"
|
|
880
|
-
out="$(run_chain --base "$CHAIN_MAIN")"; rc=$?
|
|
881
|
-
check "the passing chain state still passes after the probes" \
|
|
882
|
-
'[ "$rc" = 0 ]'
|
|
883
|
-
|
|
884
|
-
# The suite above exercises a synthetic repository. The gate must also run against
|
|
885
|
-
# the real checkout it ships in, or a break in that path passes every test here.
|
|
886
|
-
REAL_ROOT="$(cd "$DIR/../../.." && pwd -P)"
|
|
887
|
-
real_out="$(env -u CCL_SKILL_BASE_REF python3 "$GATE" --repo-root "$REAL_ROOT" --base HEAD~1 --print-candidate 2>&1)"; real_rc=$?
|
|
888
|
-
# Three outputs are legitimate and which one appears depends on what the parent
|
|
889
|
-
# commit happened to touch: a candidate hash when a bounded reviewed path moved,
|
|
890
|
-
# the no-change signal when it did not, or the packet-ceiling refusal when the
|
|
891
|
-
# parent diff is too large for one reviewer packet.
|
|
892
|
-
# Which answer is legitimate depends on the checkout, so decide that FIRST and then
|
|
893
|
-
# assert the one matching branch. Accepting every output would make this assertion a
|
|
894
|
-
# function of repository state; branching on the state keeps it an assertion about
|
|
895
|
-
# the gate. A dirty checkout is a legitimate state for a developer to run in, and
|
|
896
|
-
# the gate must refuse there rather than print a hash nobody else can recompute.
|
|
897
|
-
real_dirty="$(git -C "$REAL_ROOT" status --porcelain)"
|
|
898
|
-
if [ -n "$real_dirty" ]; then
|
|
899
|
-
check "the gate refuses to answer for a real checkout that is not committed" \
|
|
900
|
-
'[ "$real_rc" != 0 ] && case "$real_out" in *"uncommitted changes"*) true;; *) false;; esac'
|
|
901
|
-
else
|
|
902
|
-
check "the gate answers for the clean real checkout it ships in, whichever legitimate answer applies" \
|
|
903
|
-
'{ [ "$real_rc" = 0 ] && { case "$real_out" in [0-9a-f]*) [ ${#real_out} = 64 ];; *) false;; esac || case "$real_out" in *review_ledger_binding_no_change*) true;; *) false;; esac; }; } || { [ "$real_rc" = 1 ] && case "$real_out" in *"cannot freeze the candidate packet"*"review packet exceeds 200000 bytes"*) true;; *) false;; esac; }'
|
|
904
|
-
fi
|
|
905
|
-
|
|
906
|
-
# The point of splitting the candidate from the packet is that a round which
|
|
907
|
-
# widened its packet to answer an evidence-gap finding still produces a receipt
|
|
908
|
-
# this gate can accept. That claim spans both sides, so it is asserted across
|
|
909
|
-
# both: the identity the controller records for a widened packet must be the
|
|
910
|
-
# identity this gate recomputes from the repository. Mutate the gate to return
|
|
911
|
-
# the packet hash instead and this goes red while nothing else does.
|
|
912
|
-
printf 'widened-candidate\n' >>"$REPO/skills/skill-extraction-workflow/SKILL.md"
|
|
913
|
-
git -C "$REPO" add -A
|
|
914
|
-
git -C "$REPO" commit -qm widened
|
|
915
|
-
WIDENED_BASE="$BASE"
|
|
916
|
-
GATE_CANDIDATE="$(run_gate --base "$WIDENED_BASE" --print-candidate)"
|
|
917
|
-
controller_candidate="$(
|
|
918
|
-
CONTROLLER_DIR="$(dirname "$CONTROLLER")" REPO="$REPO" BASE="$WIDENED_BASE" \
|
|
919
|
-
GATE="$GATE" WORK="$WORK" python3 - <<'PY' 2>&1
|
|
920
|
-
import importlib.util
|
|
921
|
-
import os
|
|
922
|
-
import sys
|
|
923
|
-
import time
|
|
924
|
-
from pathlib import Path
|
|
925
|
-
from types import SimpleNamespace
|
|
926
|
-
|
|
927
|
-
sys.path.insert(0, os.environ["CONTROLLER_DIR"])
|
|
928
|
-
import review_gate
|
|
929
|
-
|
|
930
|
-
spec = importlib.util.spec_from_file_location("binder", os.environ["GATE"])
|
|
931
|
-
binder = importlib.util.module_from_spec(spec)
|
|
932
|
-
spec.loader.exec_module(binder)
|
|
933
|
-
|
|
934
|
-
repo = Path(os.environ["REPO"])
|
|
935
|
-
# An author cannot hand-build these: the base is a fork point and the paths
|
|
936
|
-
# carry the receipt exclusions this round already added. Both come from the gate
|
|
937
|
-
# that will judge the receipt, which is what makes it one identity.
|
|
938
|
-
base, _excludes, paths, _changed = binder.candidate_scope(
|
|
939
|
-
repo, os.environ["BASE"], (".",)
|
|
940
|
-
)
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
def freeze(**overrides):
|
|
944
|
-
args = SimpleNamespace(
|
|
945
|
-
cwd=str(repo),
|
|
946
|
-
diff_file=None,
|
|
947
|
-
base=base,
|
|
948
|
-
paths=list(paths),
|
|
949
|
-
wording_only_proof_file=None,
|
|
950
|
-
)
|
|
951
|
-
for key, value in overrides.items():
|
|
952
|
-
setattr(args, key, value)
|
|
953
|
-
return review_gate.freeze_packet(args, time.monotonic() + 60)
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
narrow_path, _narrow_packet, narrow_candidate, _n, _p, _s = freeze()
|
|
957
|
-
subject = narrow_path.read_bytes()
|
|
958
|
-
narrow_path.unlink()
|
|
959
|
-
|
|
960
|
-
# Outside the repository: the candidate includes untracked files.
|
|
961
|
-
widened = Path(os.environ["WORK"]) / "widened-for-binder.patch"
|
|
962
|
-
widened.write_bytes(subject + b"\n--- appended context for the reviewer ---\n")
|
|
963
|
-
wide_path, wide_packet, wide_candidate, _wn, _p, _s = freeze(diff_file=str(widened))
|
|
964
|
-
wide_path.unlink()
|
|
965
|
-
|
|
966
|
-
assert wide_packet != wide_candidate, "the widened packet must not be its own candidate"
|
|
967
|
-
assert wide_candidate == narrow_candidate, "widening moved the candidate"
|
|
968
|
-
print(wide_candidate)
|
|
969
|
-
PY
|
|
970
|
-
)"
|
|
971
|
-
check "a widened packet records the candidate identity this gate recomputes" \
|
|
972
|
-
'[ ${#GATE_CANDIDATE} = 64 ] && [ "$controller_candidate" = "$GATE_CANDIDATE" ]'
|
|
973
|
-
|
|
974
|
-
if [ "$fails" -gt 0 ]; then
|
|
975
|
-
echo "test_review_ledger_binding: $fails failing case(s)" >&2
|
|
976
|
-
exit 1
|
|
977
|
-
fi
|
|
978
|
-
echo "test_review_ledger_binding: ok"
|