@ccoalm/ccl-skills 0.10.0 → 0.12.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/skills/code-review/SKILL.md +4 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/staged-review-contract.md +18 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +178 -4
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +127 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/SKILL.md +2 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/SKILL.md +1 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/SKILL.md +11 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/async-lifecycle-and-performance.md +16 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/source-map.md +1 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/delivery-lifecycle.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/dispatch-owner-skills.md +9 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/problem-resolution-and-learning.md +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/tag-and-prod-pipeline-gate.md +9 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md +9 -9
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/dual-track-review-gate.md +7 -23
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/external-practice-controls.md +16 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-quickstart.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/rule-consolidation.md +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +29 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-to-skill-extraction.md +2 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/validation-and-landing.md +2 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-ccl-skills.sh +5 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/contract-anchors.tsv +1 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +35 -6
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/review_ledger_binding.py +817 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh +81 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +3 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_review_ledger_binding.sh +521 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_routing_pointer_integrity.sh +41 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_extraction_review_state.sh +116 -9
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/validate_extraction_review_state.py +123 -32
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/SKILL.md +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/SKILL.md +9 -8
- package/dist/assets/release.json +47 -37
- package/package.json +1 -1
|
@@ -194,6 +194,86 @@ run_gate
|
|
|
194
194
|
assert_not_contains "impact_chain_firing_path_missing" "$out" "a changed shebang owner executable should satisfy the firing-path gate"
|
|
195
195
|
assert_rc "$rc" 0 "a complete RED row with a changed owner executable must pass"
|
|
196
196
|
|
|
197
|
+
# Non-ASCII anchors must be accepted, and the proof must be DIFFERENTIAL rather
|
|
198
|
+
# than one literal passing: a special-cased implementation, or one that handles
|
|
199
|
+
# Han but not a combining mark, would satisfy a single-literal leg. Each row below
|
|
200
|
+
# pairs a non-ASCII anchor with an ASCII anchor matched on everything the gate
|
|
201
|
+
# actually predicates on - uniqueness, the 16-character bar, list-line shape, a
|
|
202
|
+
# whitelisted normative verb - so the two verdicts must agree. This exists because
|
|
203
|
+
# a round concluded from reading the wrong lambda (`raw_blob_at` forces binary;
|
|
204
|
+
# `locator_valid` resolves through `blob_at`, which does not) that a non-ASCII
|
|
205
|
+
# anchor could never validate, and recorded a defect that was not there.
|
|
206
|
+
#
|
|
207
|
+
# Representations covered: Han; a precomposed Latin letter (NFC, two bytes); the
|
|
208
|
+
# same letter decomposed into base + combining mark (NFD, so the anchor carries a
|
|
209
|
+
# codepoint that is not a character on its own).
|
|
210
|
+
anchor_pair_case() {
|
|
211
|
+
# $1 = case label, $2 = non-ASCII anchor, $3 = ASCII anchor of the same shape
|
|
212
|
+
local label="$1" nonascii="$2" ascii="$3" verdict_nonascii verdict_ascii
|
|
213
|
+
new_case "case-ref-anchor-pair-$label-nonascii"
|
|
214
|
+
printf '\n- %s\n' "$nonascii" >> "$REPO/$UPSTREAM_REF"
|
|
215
|
+
printf '| Fixture anchor pair %s non-ASCII | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; bank-evidence: downscoped:REFSCRIPTS-FIXTURE-NO-BANK; firing-path: file:skills/product-rd-workflow/references/adr-convention.md#%s | `updated` | `product-rd-workflow/SKILL.md` reference edit |\n' "$label" "$nonascii" >> "$REGISTER"
|
|
216
|
+
routing_surface_downscope
|
|
217
|
+
commit_case "anchor pair $label non-ASCII"
|
|
218
|
+
run_gate
|
|
219
|
+
verdict_nonascii="$rc"
|
|
220
|
+
|
|
221
|
+
new_case "case-ref-anchor-pair-$label-ascii"
|
|
222
|
+
printf '\n- %s\n' "$ascii" >> "$REPO/$UPSTREAM_REF"
|
|
223
|
+
printf '| Fixture anchor pair %s ASCII | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; bank-evidence: downscoped:REFSCRIPTS-FIXTURE-NO-BANK; firing-path: file:skills/product-rd-workflow/references/adr-convention.md#%s | `updated` | `product-rd-workflow/SKILL.md` reference edit |\n' "$label" "$ascii" >> "$REGISTER"
|
|
224
|
+
routing_surface_downscope
|
|
225
|
+
commit_case "anchor pair $label ASCII"
|
|
226
|
+
run_gate
|
|
227
|
+
verdict_ascii="$rc"
|
|
228
|
+
|
|
229
|
+
assert_rc "$verdict_ascii" 0 "the ASCII control of pair $label must be accepted"
|
|
230
|
+
assert_rc "$verdict_nonascii" "$verdict_ascii" "pair $label: the non-ASCII anchor must get the same verdict as its ASCII control"
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
anchor_pair_case han \
|
|
234
|
+
'下游执行者必须在收到该契约变更时同步更新其验证面。' \
|
|
235
|
+
'The downstream executor must refresh its verification surface on this change.'
|
|
236
|
+
# Same sentence and the same whitelisted verb in both arms; only the accented
|
|
237
|
+
# codepoint differs, so a verdict difference can only come from byte composition.
|
|
238
|
+
anchor_pair_case nfc \
|
|
239
|
+
'The owner must refresh its vérification surface on this change.' \
|
|
240
|
+
'The owner must refresh its verification surface on this change.'
|
|
241
|
+
anchor_pair_case nfd \
|
|
242
|
+
'The owner must refresh its vérification surface on that change.' \
|
|
243
|
+
'The owner must refresh its verification surface on that change.'
|
|
244
|
+
|
|
245
|
+
# When one owner carries several RED rows, the owner-level check reddens the whole
|
|
246
|
+
# package for a single bad row — so the diagnostic must name WHICH row failed.
|
|
247
|
+
# Without it the author inspects the row they just wrote (usually the correct one)
|
|
248
|
+
# while an unrelated row is the offender; that cost one round three iterations.
|
|
249
|
+
new_case case-ref-firing-path-names-offending-row
|
|
250
|
+
printf '\n- 下游执行者必须在收到该契约变更时同步更新其验证面。\n' >> "$REPO/$UPSTREAM_REF"
|
|
251
|
+
printf '| Fixture good row beside a bad one | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; bank-evidence: downscoped:REFSCRIPTS-FIXTURE-NO-BANK; firing-path: file:skills/product-rd-workflow/references/adr-convention.md#下游执行者必须在收到该契约变更时同步更新其验证面 | `updated` | `product-rd-workflow/SKILL.md` reference edit |\n' >> "$REGISTER"
|
|
252
|
+
printf '| Fixture offending row with an unresolvable anchor | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; bank-evidence: downscoped:REFSCRIPTS-FIXTURE-NO-BANK; firing-path: file:skills/product-rd-workflow/references/adr-convention.md#this anchor text was never added by this round | `updated` | `product-rd-workflow/SKILL.md` reference edit |\n' >> "$REGISTER"
|
|
253
|
+
routing_surface_downscope
|
|
254
|
+
commit_case "one good row beside one unresolvable row"
|
|
255
|
+
run_gate
|
|
256
|
+
assert_rc "$rc" 1 "an unresolvable anchor on any bound RED row must fail the owner"
|
|
257
|
+
assert_contains "impact_chain_firing_path_missing" "$out" "checker should report the firing-path verdict"
|
|
258
|
+
assert_contains "Fixture offending row with an unresolvable anchor" "$out" "the diagnostic must name the offending row"
|
|
259
|
+
assert_not_contains "offending row: Fixture good row beside a bad one" "$out" "the diagnostic must not name a row whose anchor resolves"
|
|
260
|
+
|
|
261
|
+
# The offending-row line renders repository text a contributor controls onto a
|
|
262
|
+
# terminal/CI channel. Control and invisible characters must be escaped, not merely
|
|
263
|
+
# truncated: raw ANSI or carriage-return bytes can erase or restyle surrounding
|
|
264
|
+
# output, and bidi overrides can reorder it, so a reader sees a diagnostic that does
|
|
265
|
+
# not match what the gate decided. Both reviewer lanes raised this independently.
|
|
266
|
+
new_case case-ref-offending-row-escapes-control-bytes
|
|
267
|
+
printf '\n- 下游执行者必须在收到该契约变更时同步更新其验证面。\n' >> "$REPO/$UPSTREAM_REF"
|
|
268
|
+
printf '| Fixture control-byte row \033[31m\rSPOOFED\u202E | `downstream-executor` | behavioral-evidence: RED-baseline; observed-failure: yes; result-class: failure; bank-evidence: downscoped:REFSCRIPTS-FIXTURE-NO-BANK; firing-path: file:skills/product-rd-workflow/references/adr-convention.md#this anchor text was never added by this round | `updated` | `product-rd-workflow/SKILL.md` reference edit |\n' >> "$REGISTER"
|
|
269
|
+
routing_surface_downscope
|
|
270
|
+
commit_case "offending row carrying control bytes"
|
|
271
|
+
run_gate
|
|
272
|
+
assert_rc "$rc" 1 "an unresolvable anchor must still fail with a control-byte row"
|
|
273
|
+
assert_contains "\\u001B" "$out" "the diagnostic must escape an ANSI introducer rather than emit it"
|
|
274
|
+
assert_contains "\\u000D" "$out" "the diagnostic must escape a carriage return rather than emit it"
|
|
275
|
+
assert_contains "\\u202E" "$out" "the diagnostic must escape a bidi override rather than emit it"
|
|
276
|
+
|
|
197
277
|
# A malformed impact-chain-looking row (wrong cell count) is warned but stays
|
|
198
278
|
# advisory: hard-blocking would false-positive on the register's many other
|
|
199
279
|
# legitimate table shapes, and deliberate mangling is outside the trust model
|
|
@@ -1329,6 +1409,6 @@ assert_rc "$rc" 1 "a directory masquerading as SKILL.md must not read as a prese
|
|
|
1329
1409
|
assert_contains "platform-observability/SKILL.md" "$out" "the masqueraded owner must be named"
|
|
1330
1410
|
|
|
1331
1411
|
assert_rc "$full_check_runs" 1 "fixture suite must retain exactly one full checker wiring case"
|
|
1332
|
-
assert_rc "$gate_runs"
|
|
1412
|
+
assert_rc "$gate_runs" 88 "all remaining impact-chain fixtures must run the standalone gate"
|
|
1333
1413
|
|
|
1334
1414
|
echo "test_check_ccl_impact_chain_refscripts: ok"
|
|
@@ -114,6 +114,9 @@ fast_tests=(
|
|
|
114
114
|
test_shared_git_surface_gate.sh
|
|
115
115
|
test_extraction_review_gate.sh
|
|
116
116
|
test_validate_extraction_review_state.sh
|
|
117
|
+
# Merge-side binding between the landing candidate and the review evidence that
|
|
118
|
+
# inspected it: own throwaway git repo, no clone, seconds.
|
|
119
|
+
test_review_ledger_binding.sh
|
|
117
120
|
# Candidate-SHA-bound gate receipts (mint/verify): own throwaway git repo,
|
|
118
121
|
# no clone, seconds — belongs in the lane every run exercises.
|
|
119
122
|
test_gate_receipt.sh
|
|
@@ -0,0 +1,521 @@
|
|
|
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
|
+
# The suite above exercises a synthetic repository. The gate must also run against
|
|
496
|
+
# the real checkout it ships in, or a break in that path passes every test here.
|
|
497
|
+
REAL_ROOT="$(cd "$DIR/../../.." && pwd -P)"
|
|
498
|
+
real_out="$(env -u CCL_SKILL_BASE_REF python3 "$GATE" --repo-root "$REAL_ROOT" --base HEAD~1 --print-candidate 2>&1)"; real_rc=$?
|
|
499
|
+
# Both outputs are legitimate and which one appears depends on what the parent
|
|
500
|
+
# commit happened to touch: a candidate hash when a reviewed path moved, the
|
|
501
|
+
# no-change signal when it did not. Accepting only the first makes this assertion
|
|
502
|
+
# a function of repository state rather than of the gate.
|
|
503
|
+
# Which answer is legitimate depends on the checkout, so decide that FIRST and then
|
|
504
|
+
# assert the one matching branch. Accepting every output would make this assertion a
|
|
505
|
+
# function of repository state; branching on the state keeps it an assertion about
|
|
506
|
+
# the gate. A dirty checkout is a legitimate state for a developer to run in, and
|
|
507
|
+
# the gate must refuse there rather than print a hash nobody else can recompute.
|
|
508
|
+
real_dirty="$(git -C "$REAL_ROOT" status --porcelain)"
|
|
509
|
+
if [ -n "$real_dirty" ]; then
|
|
510
|
+
check "the gate refuses to answer for a real checkout that is not committed" \
|
|
511
|
+
'[ "$real_rc" != 0 ] && case "$real_out" in *"uncommitted changes"*) true;; *) false;; esac'
|
|
512
|
+
else
|
|
513
|
+
check "the gate answers for the clean real checkout it ships in, whichever legitimate answer applies" \
|
|
514
|
+
'[ "$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; }'
|
|
515
|
+
fi
|
|
516
|
+
|
|
517
|
+
if [ "$fails" -gt 0 ]; then
|
|
518
|
+
echo "test_review_ledger_binding: $fails failing case(s)" >&2
|
|
519
|
+
exit 1
|
|
520
|
+
fi
|
|
521
|
+
echo "test_review_ledger_binding: ok"
|