@ccoalm/ccl-skills 0.13.0 → 0.15.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.
Files changed (52) hide show
  1. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/SKILL.md +19 -24
  2. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/client-routing.md +32 -32
  3. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/manual-invocation-and-prompts.md +16 -14
  4. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/staged-review-contract.md +24 -26
  5. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/AGENTS.md +11 -0
  6. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/claude_review.sh +60 -209
  7. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/init_policy_matrix.py +114 -367
  8. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/parse_probe_result.py +52 -672
  9. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +10 -2
  10. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/runtime-surface-verification-design.md +4 -2
  11. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_claude_review_probe.sh +77 -444
  12. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_init_policy_matrix.sh +33 -98
  13. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_parse_probe_result.sh +57 -173
  14. package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/SKILL.md +1 -1
  15. package/dist/assets/marketplace/plugins/ccl-skills/skills/grill-me/SKILL.md +1 -1
  16. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/SKILL.md +1 -1
  17. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/SKILL.md +1 -1
  18. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/SKILL.md +1 -1
  19. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/SKILL.md +2 -2
  20. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/rd-standards-doc-family-checklist.md +2 -2
  21. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-baseline/SKILL.md +1 -1
  22. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-doc-writer/SKILL.md +1 -1
  23. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-scope/SKILL.md +1 -1
  24. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md +14 -41
  25. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/attention-budget-ratchet.md +11 -0
  26. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/correction-routing-map.md +22 -0
  27. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/coverage-exhaustion-traps.md +7 -0
  28. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/description-authoring.md +26 -0
  29. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/dual-track-review-gate.md +2 -2
  30. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/eval-routing.md +8 -0
  31. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/external-practice-controls.md +7 -1
  32. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-quickstart.md +4 -2
  33. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/harness-patterns-and-eval.md +8 -0
  34. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/incident-postmortem-extraction.md +8 -0
  35. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/rule-consolidation.md +1 -0
  36. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +73 -0
  37. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/uiux-judgment-extraction.md +11 -0
  38. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/validation-and-landing.md +11 -0
  39. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/entrypoint_form_census.py +169 -0
  40. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-routing-bank.rb +62 -3
  41. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +114 -10
  42. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/reference-access-census.sh +157 -0
  43. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/review_ledger_binding.py +483 -109
  44. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh +188 -14
  45. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +10 -0
  46. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_entrypoint_form_census.sh +174 -0
  47. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_bank_resolution.sh +253 -0
  48. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_gate_verdict_differential.sh +49 -25
  49. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_reference_access_census.sh +209 -0
  50. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_review_ledger_binding.sh +394 -5
  51. package/dist/assets/release.json +77 -47
  52. package/package.json +1 -1
@@ -492,14 +492,403 @@ out="$(run_part --base "$PART_BASE")"; rc=$?
492
492
  check "the passing partition state still passes after the probes" \
493
493
  '[ "$rc" = 0 ]'
494
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
+
495
884
  # The suite above exercises a synthetic repository. The gate must also run against
496
885
  # the real checkout it ships in, or a break in that path passes every test here.
497
886
  REAL_ROOT="$(cd "$DIR/../../.." && pwd -P)"
498
887
  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.
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.
503
892
  # Which answer is legitimate depends on the checkout, so decide that FIRST and then
504
893
  # assert the one matching branch. Accepting every output would make this assertion a
505
894
  # function of repository state; branching on the state keeps it an assertion about
@@ -511,7 +900,7 @@ if [ -n "$real_dirty" ]; then
511
900
  '[ "$real_rc" != 0 ] && case "$real_out" in *"uncommitted changes"*) true;; *) false;; esac'
512
901
  else
513
902
  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; }'
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; }'
515
904
  fi
516
905
 
517
906
  if [ "$fails" -gt 0 ]; then