@ccoalm/ccl-skills 0.11.0 → 0.13.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 (19) hide show
  1. package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/SKILL.md +21 -12
  2. package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/references/diagnosis-playbook.md +42 -1
  3. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/SKILL.md +7 -4
  4. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-context-freshness.md +4 -0
  5. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-tool-dispatch.md +2 -0
  6. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/inference-capacity-operations.md +20 -1
  7. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/llm-client-gateway.md +11 -0
  8. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/model-prompt-evaluation.md +3 -2
  9. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/retrieval-agent-safety.md +5 -0
  10. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-agent-delegation/SKILL.md +13 -9
  11. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-agent-delegation/references/multi-agent-delegation-playbook.md +12 -1
  12. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/dual-track-review-gate.md +1 -1
  13. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-quickstart.md +1 -1
  14. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/harness-patterns-and-eval.md +14 -0
  15. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +38 -0
  16. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/review_ledger_binding.py +364 -23
  17. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_review_ledger_binding.sh +185 -0
  18. package/dist/assets/release.json +20 -20
  19. package/package.json +1 -1
@@ -44,6 +44,32 @@ commit; the alternative costs a review round nobody can reproduce. The workflow
44
44
  directory stays bound, as it was before: with only `skills/` bound, deleting the CI
45
45
  step that runs this gate would not move the candidate the evidence has to match.
46
46
 
47
+ The candidate's Git identity and the reviewer's input are two different sizes.
48
+ A landing candidate is base..HEAD and has no natural byte limit; a review packet
49
+ is what one reviewer can read whole, and the controller caps it at
50
+ `MAX_PACKET_BYTES`. Binding the landing candidate to ONE packet hash therefore
51
+ made the reviewer's ceiling the pull request's ceiling: a candidate larger than
52
+ one packet could not be frozen, no ledger could ever bind it, and authors split
53
+ the pull request instead of the review -- eight merges for one release. The
54
+ review side already allowed splitting a large candidate by path into partitions
55
+ (the `code-review` skill's packet rule); what was missing was the merge side
56
+ consuming them. A committed landing partition manifest closes that: it names
57
+ path partitions whose changed files together equal the candidate's changed files
58
+ exactly once, and each partition's `candidate_sha256`, which is what
59
+ `--print-candidate --paths <partition>` already answers. This gate recomputes
60
+ every partition with the same `freeze_packet`, requires a validator-accepted
61
+ ledger per partition, and refuses any manifest whose parts do not add up to the
62
+ whole: an uncovered file, an overlapping file, a partition that no longer
63
+ reproduces, a base other than the fork point, or an aggregate hash that does not
64
+ reproduce its own partitions. The manifest carries a top-level 64-hex
65
+ `candidate_sha256` -- the aggregate identity -- so it satisfies the existing
66
+ receipt predicate and committing it moves no partition; that is the load-bearing
67
+ reason for the field, and the exclusion predicate itself is unchanged. What the
68
+ manifest proves is the same narrow thing the single ledger proves, taken per
69
+ part: every byte that lands is a byte some external round froze and inspected.
70
+ Merge-queue aggregation of several pull requests into one HEAD is a different
71
+ aggregate and remains unsolved here.
72
+
47
73
  Boundaries this gate does NOT close, stated because a gate that lives inside the
48
74
  candidate cannot authenticate itself: it cannot prove the caller retained every
49
75
  earlier chain; it runs the candidate's own validator, so a candidate that also
@@ -92,6 +118,21 @@ EVIDENCE_MEMBER = re.compile(r"^specs/[^/]+/evidence/")
92
118
  # A receipt is small; anything larger is not one, and reading it is not free.
93
119
  MAX_RECEIPT_BYTES = 4_000_000
94
120
  DEFAULT_PATHS = (".",)
121
+ # A landing partition manifest splits one candidate by path into packets a
122
+ # reviewer can read whole. Its shape is closed: exactly these keys, this kind,
123
+ # and a bounded partition count, so a manifest cannot double as a ledger or
124
+ # smuggle fields the gate does not read.
125
+ MANIFEST_KIND = "landing_partition_manifest"
126
+ MANIFEST_SCHEMA_VERSION = 1
127
+ MANIFEST_KEYS = {"schema_version", "kind", "base", "partitions", "candidate_sha256"}
128
+ PARTITION_KEYS = {"paths", "candidate_sha256"}
129
+ MAX_PARTITIONS = 64
130
+ HEX40 = re.compile(r"^[0-9a-f]{40}$")
131
+ HEX64 = re.compile(r"^[0-9a-f]{64}$")
132
+
133
+
134
+ class ManifestError(Exception):
135
+ """A manifest that does not describe this candidate; the message is the reason."""
95
136
 
96
137
 
97
138
  def emit(message: str) -> None:
@@ -312,6 +353,233 @@ def candidate_hash(module: types.ModuleType, repo_root: Path, base: str, paths:
312
353
  return packet_sha256
313
354
 
314
355
 
356
+ def canonical_digest(value: object) -> str:
357
+ canonical = json.dumps(
358
+ value, sort_keys=True, separators=(",", ":"), ensure_ascii=False
359
+ ).encode("utf-8")
360
+ return hashlib.sha256(canonical).hexdigest()
361
+
362
+
363
+ def manifest_aggregate(base: str, partitions: list[dict]) -> str:
364
+ """The aggregate identity a manifest carries as its own `candidate_sha256`.
365
+
366
+ Computed here and nowhere else: the renderer writes it and the gate rechecks
367
+ it, so two implementations of one hash cannot drift into a forged manifest.
368
+ """
369
+ return canonical_digest(
370
+ {
371
+ "schema_version": MANIFEST_SCHEMA_VERSION,
372
+ "kind": MANIFEST_KIND,
373
+ "base": base,
374
+ "partitions": partitions,
375
+ }
376
+ )
377
+
378
+
379
+ def validate_partition_path(value: object) -> str:
380
+ """A partition path is a plain relative path, never a pathspec.
381
+
382
+ The gate appends its own exclusions; a manifest that could name `:(exclude)`
383
+ or a glob would choose what its partition does not cover, and a leading `-`
384
+ would reach git as an option. Reject the shape here rather than trust git to
385
+ interpret it the way the manifest author hoped.
386
+ """
387
+ if not isinstance(value, str) or not value:
388
+ raise ManifestError("partition path must be a non-empty string")
389
+ if value.startswith((":", "-", "/")) or any(ord(ch) < 32 for ch in value):
390
+ raise ManifestError(f"partition path is not a plain relative path: {value!r}")
391
+ # Git reads `*`, `?`, `[` and `\` inside a pathspec as glob/escape syntax even
392
+ # without magic; a wildcard partition would let the manifest choose its own
393
+ # coverage, so it is refused as a path rather than handed to git.
394
+ if any(ch in value for ch in "*?[]\\"):
395
+ raise ManifestError(f"partition path contains a pathspec wildcard: {value!r}")
396
+ if ".." in value.split("/"):
397
+ raise ManifestError(f"partition path escapes the repository: {value!r}")
398
+ return value
399
+
400
+
401
+ def parse_manifest(payload: dict, fork: str) -> tuple[list[list[str]], list[str]]:
402
+ """Return (partition path lists, partition hashes) or raise ManifestError.
403
+
404
+ Order of checks is cheapest first and each failure names one reason, so an
405
+ author reads which part failed to add up rather than a generic refusal.
406
+ """
407
+ if set(payload) != MANIFEST_KEYS:
408
+ raise ManifestError("manifest does not carry exactly the manifest keys")
409
+ if payload["schema_version"] != MANIFEST_SCHEMA_VERSION or type(payload["schema_version"]) is not int:
410
+ raise ManifestError(f"manifest schema_version must be {MANIFEST_SCHEMA_VERSION}")
411
+ base = payload["base"]
412
+ if not isinstance(base, str) or not HEX40.match(base):
413
+ raise ManifestError("manifest base must be a 40-hex commit id")
414
+ if base != fork:
415
+ raise ManifestError(
416
+ f"manifest base {base[:12]} is not this candidate's fork point {fork[:12]}"
417
+ )
418
+ partitions = payload["partitions"]
419
+ if not isinstance(partitions, list) or not 1 <= len(partitions) <= MAX_PARTITIONS:
420
+ raise ManifestError(f"manifest must list between 1 and {MAX_PARTITIONS} partitions")
421
+ path_lists: list[list[str]] = []
422
+ digests: list[str] = []
423
+ seen: set[str] = set()
424
+ for index, partition in enumerate(partitions, start=1):
425
+ if not isinstance(partition, dict) or set(partition) != PARTITION_KEYS:
426
+ raise ManifestError(f"partition {index} does not carry exactly paths and candidate_sha256")
427
+ paths = partition["paths"]
428
+ if not isinstance(paths, list) or not paths:
429
+ raise ManifestError(f"partition {index} names no paths")
430
+ validated = [validate_partition_path(value) for value in paths]
431
+ for value in validated:
432
+ if value in seen:
433
+ raise ManifestError(f"partition path is listed twice: {value}")
434
+ seen.add(value)
435
+ digest = partition["candidate_sha256"]
436
+ if not isinstance(digest, str) or not HEX64.match(digest):
437
+ raise ManifestError(f"partition {index} candidate_sha256 must be 64-hex")
438
+ path_lists.append(validated)
439
+ digests.append(digest)
440
+ aggregate = payload["candidate_sha256"]
441
+ if aggregate != manifest_aggregate(base, partitions):
442
+ raise ManifestError("manifest aggregate candidate_sha256 does not reproduce its partitions")
443
+ return path_lists, digests
444
+
445
+
446
+ def partition_coverage(
447
+ repo_root: Path,
448
+ base: str,
449
+ path_lists: list[list[str]],
450
+ excludes: tuple[str, ...],
451
+ changed_all: list[str],
452
+ ) -> list[list[str]]:
453
+ """Each partition's changed files; refuse unless they tile the candidate.
454
+
455
+ Name-only diffs are cheap, so every way the parts can fail to add up is found
456
+ before any packet is frozen: an empty partition, a changed file in no
457
+ partition, or a changed file in two. Overlap is refused rather than tolerated
458
+ because two verdicts over one file leave undefined which one covers it.
459
+ """
460
+ per_partition: list[list[str]] = []
461
+ owner: dict[str, int] = {}
462
+ overlaps: list[str] = []
463
+ for index, paths in enumerate(path_lists, start=1):
464
+ changed = changed_skill_paths(repo_root, base, tuple(paths) + excludes)
465
+ if not changed:
466
+ raise ManifestError(f"partition {index} ({' '.join(paths)}) covers no changed path")
467
+ for value in changed:
468
+ if value in owner:
469
+ overlaps.append(value)
470
+ owner[value] = index
471
+ per_partition.append(changed)
472
+ if overlaps:
473
+ raise ManifestError(
474
+ "partitions overlap on changed paths: " + ", ".join(sorted(set(overlaps))[:5])
475
+ )
476
+ uncovered = sorted(set(changed_all) - set(owner))
477
+ if uncovered:
478
+ raise ManifestError(
479
+ "changed paths uncovered by every partition: " + ", ".join(uncovered[:5])
480
+ )
481
+ # Equality, not containment: under a narrowed --paths scope a partition can
482
+ # reach changed files outside the reviewed set, and a union larger than the
483
+ # candidate is as wrong as one smaller than it.
484
+ outside = sorted(set(owner) - set(changed_all))
485
+ if outside:
486
+ raise ManifestError(
487
+ "partitions cover changed paths outside the reviewed scope: " + ", ".join(outside[:5])
488
+ )
489
+ return per_partition
490
+
491
+
492
+ def render_manifest(
493
+ module: types.ModuleType,
494
+ repo_root: Path,
495
+ base: str,
496
+ path_lists: list[list[str]],
497
+ excludes: tuple[str, ...],
498
+ changed_all: list[str],
499
+ ) -> dict:
500
+ """Build the manifest an author commits, with every hash computed by this gate."""
501
+ validated = [[validate_partition_path(value) for value in paths] for paths in path_lists]
502
+ seen: set[str] = set()
503
+ for paths in validated:
504
+ for value in paths:
505
+ if value in seen:
506
+ raise ManifestError(f"partition path is listed twice: {value}")
507
+ seen.add(value)
508
+ partition_coverage(repo_root, base, validated, excludes, changed_all)
509
+ partitions = [
510
+ {
511
+ "paths": paths,
512
+ "candidate_sha256": candidate_hash(module, repo_root, base, tuple(paths) + excludes),
513
+ }
514
+ for paths in validated
515
+ ]
516
+ return {
517
+ "schema_version": MANIFEST_SCHEMA_VERSION,
518
+ "kind": MANIFEST_KIND,
519
+ "base": base,
520
+ "partitions": partitions,
521
+ "candidate_sha256": manifest_aggregate(base, partitions),
522
+ }
523
+
524
+
525
+ def accepted_ledger_for(
526
+ evidence: list[tuple[Path, dict]],
527
+ repo_root: Path,
528
+ validator: Path,
529
+ digest: str,
530
+ rejected: list[str],
531
+ ) -> str | None:
532
+ """The first validator-accepted closeout ledger bound to `digest`, if any.
533
+
534
+ The same criterion the single-candidate path uses: a receipt-shaped file is
535
+ not evidence, only a ledger the validator accepts, because this gate cannot
536
+ authenticate that a controller minted what it reads.
537
+ """
538
+ for path, payload in evidence:
539
+ if payload.get("candidate_sha256") != digest:
540
+ continue
541
+ if "closeout_state" not in payload or "controller_receipts" not in payload:
542
+ continue
543
+ relative = str(path.relative_to(repo_root))
544
+ accepted, output = validator_accepts(validator, path)
545
+ if accepted:
546
+ return f"{relative} -- {output}"
547
+ rejected.append(f"{relative}: {output}")
548
+ return None
549
+
550
+
551
+ def bind_manifest(
552
+ module: types.ModuleType,
553
+ repo_root: Path,
554
+ base: str,
555
+ payload: dict,
556
+ excludes: tuple[str, ...],
557
+ changed_all: list[str],
558
+ evidence: list[tuple[Path, dict]],
559
+ validator: Path,
560
+ rejected_ledgers: list[str],
561
+ ) -> list[str]:
562
+ """Bind the candidate through one manifest; return per-partition proof lines.
563
+
564
+ Raises ManifestError naming the first part that does not add up.
565
+ """
566
+ path_lists, digests = parse_manifest(payload, base)
567
+ partition_coverage(repo_root, base, path_lists, excludes, changed_all)
568
+ proofs: list[str] = []
569
+ for index, (paths, recorded) in enumerate(zip(path_lists, digests), start=1):
570
+ label = f"partition {index} ({' '.join(paths)})"
571
+ actual = candidate_hash(module, repo_root, base, tuple(paths) + excludes)
572
+ if actual != recorded:
573
+ raise ManifestError(
574
+ f"{label} recorded {recorded[:12]}... but does not reproduce: the candidate now hashes to {actual[:12]}..."
575
+ )
576
+ proof = accepted_ledger_for(evidence, repo_root, validator, actual, rejected_ledgers)
577
+ if proof is None:
578
+ raise ManifestError(f"no accepted ledger binds {label} {actual}")
579
+ proofs.append(f" {label} {actual[:12]}... <- {proof}")
580
+ return proofs
581
+
582
+
315
583
  def validator_accepts(validator: Path, ledger_path: Path) -> tuple[bool, str]:
316
584
  result = subprocess.run(
317
585
  [sys.executable, str(validator), str(ledger_path)],
@@ -382,7 +650,27 @@ def main() -> int:
382
650
  action="store_true",
383
651
  help="print the candidate hash the evidence must bind, then exit",
384
652
  )
653
+ parser.add_argument(
654
+ "--print-manifest",
655
+ action="store_true",
656
+ help=(
657
+ "render a landing partition manifest for the --partition groups given, "
658
+ "with every hash computed by this gate, then exit"
659
+ ),
660
+ )
661
+ parser.add_argument(
662
+ "--partition",
663
+ action="append",
664
+ nargs="+",
665
+ metavar="PATH",
666
+ default=[],
667
+ help="one partition's paths; repeat per partition (only with --print-manifest)",
668
+ )
385
669
  args = parser.parse_args()
670
+ if args.print_manifest and not args.partition:
671
+ parser.error("--print-manifest needs at least one --partition")
672
+ if args.partition and not args.print_manifest:
673
+ parser.error("--partition is only meaningful with --print-manifest")
386
674
 
387
675
  repo_root = Path(args.repo_root).resolve()
388
676
  base = args.base or os.environ.get("CCL_SKILL_BASE_REF", "")
@@ -410,61 +698,114 @@ def main() -> int:
410
698
  # confirmed, and no test asserts a bypass this gate does not have. `literal`
411
699
  # stays because interpreting these names as patterns is a capability the gate
412
700
  # never needed, and removing it costs nothing.
413
- paths = tuple(args.paths) + tuple(
701
+ excludes = tuple(
414
702
  f":(exclude,literal){path}" for path in added_evidence_paths(repo_root, base)
415
703
  )
704
+ paths = tuple(args.paths) + excludes
416
705
  require_committed_tree(repo_root, paths)
417
706
  changed = changed_skill_paths(repo_root, base, paths)
418
707
  if not changed:
419
708
  # No reviewed path moved, so there is no candidate to freeze and nothing to
420
709
  # bind. Say which it is rather than letting an empty packet surface as a
421
710
  # freeze error, which reads like a broken gate.
422
- if args.print_candidate:
711
+ if args.print_candidate or args.print_manifest:
423
712
  emit(f"review_ledger_binding_no_change: no reviewed-path change against {base}")
424
713
  else:
425
714
  print(f"review_ledger_binding_ok: no reviewed-path change against {base}")
426
715
  return 0
427
716
 
428
717
  module = load_controller(repo_root)
718
+
719
+ if args.print_manifest:
720
+ try:
721
+ manifest = render_manifest(module, repo_root, base, args.partition, excludes, changed)
722
+ except ManifestError as exc:
723
+ emit(f"review_ledger_binding_error: cannot render a landing partition manifest: {exc}")
724
+ return 1
725
+ except Exception as exc: # noqa: BLE001 - surface the controller's own message
726
+ emit(f"review_ledger_binding_error: cannot freeze a partition packet: {exc}")
727
+ return 1
728
+ print(json.dumps(manifest, indent=2, ensure_ascii=False))
729
+ return 0
730
+
731
+ # The whole candidate may be larger than one packet. That is no longer a
732
+ # terminal error: record why the single freeze failed and let a committed
733
+ # partition manifest bind the candidate part by part.
734
+ expected: str | None = None
735
+ whole_error: str | None = None
429
736
  try:
430
737
  expected = candidate_hash(module, repo_root, base, paths)
431
738
  except Exception as exc: # noqa: BLE001 - surface the controller's own message
432
- emit(f"review_ledger_binding_error: cannot freeze the candidate packet: {exc}")
433
- return 1
739
+ whole_error = str(exc)
434
740
 
435
741
  if args.print_candidate:
742
+ if expected is None:
743
+ emit(f"review_ledger_binding_error: cannot freeze the candidate packet: {whole_error}")
744
+ return 1
436
745
  print(expected)
437
746
  return 0
438
747
 
439
748
  validator = repo_root / "skills" / "skill-extraction-workflow" / "scripts" / VALIDATOR
749
+ evidence = scan(repo_root, args.evidence_root)
440
750
  ledgers: list[str] = []
441
- for path, payload in scan(repo_root, args.evidence_root):
442
- if payload.get("candidate_sha256") != expected:
443
- continue
444
- relative = str(path.relative_to(repo_root))
751
+ if expected is not None:
445
752
  # Only a validator-accepted ledger counts. A receipt-shaped file proves
446
753
  # nothing on its own: this gate cannot authenticate that a controller
447
754
  # minted it, so any branch keyed on a self-declared field is a bypass a
448
755
  # contributor can hand-write.
449
- if "closeout_state" in payload and "controller_receipts" in payload:
450
- accepted, output = validator_accepts(validator, path)
451
- if accepted:
452
- print(
453
- f"review_ledger_binding_ok: {relative} binds the landing candidate "
454
- f"({expected[:12]}...) -- {output}"
455
- )
456
- return 0
457
- ledgers.append(f"{relative}: {output}")
458
-
459
- emit(
460
- "review_ledger_binding_failed: no accepted review evidence binds the landing "
461
- f"candidate {expected}"
462
- )
756
+ proof = accepted_ledger_for(evidence, repo_root, validator, expected, ledgers)
757
+ if proof is not None:
758
+ print(
759
+ f"review_ledger_binding_ok: {proof.split(' -- ', 1)[0]} binds the landing "
760
+ f"candidate ({expected[:12]}...) -- {proof.split(' -- ', 1)[1]}"
761
+ )
762
+ return 0
763
+
764
+ manifests: list[str] = []
765
+ for path, payload in evidence:
766
+ if payload.get("kind") != MANIFEST_KIND:
767
+ continue
768
+ relative = str(path.relative_to(repo_root))
769
+ try:
770
+ proofs = bind_manifest(
771
+ module, repo_root, base, payload, excludes, changed, evidence, validator, ledgers
772
+ )
773
+ except ManifestError as exc:
774
+ manifests.append(f"{relative}: {exc}")
775
+ continue
776
+ except Exception as exc: # noqa: BLE001 - surface the controller's own message
777
+ manifests.append(f"{relative}: cannot freeze a partition packet: {exc}")
778
+ continue
779
+ print(
780
+ f"review_ledger_binding_ok: {relative} binds the landing candidate as "
781
+ f"{len(proofs)} partitions (aggregate {payload['candidate_sha256'][:12]}...)"
782
+ )
783
+ for line in proofs:
784
+ print(line)
785
+ return 0
786
+
787
+ if expected is None:
788
+ emit(
789
+ "review_ledger_binding_failed: the whole candidate cannot be frozen as one "
790
+ f"packet ({whole_error}) and no committed landing partition manifest binds it"
791
+ )
792
+ emit(
793
+ " split the candidate by path: --print-manifest --partition <paths> "
794
+ "[--partition <paths> ...] renders the manifest; commit it with one "
795
+ "validated ledger per partition"
796
+ )
797
+ else:
798
+ emit(
799
+ "review_ledger_binding_failed: no accepted review evidence binds the landing "
800
+ f"candidate {expected}"
801
+ )
463
802
  emit(f" reviewed paths: {' '.join(paths)} against {base}")
464
803
  emit(f" changed files: {len(changed)}")
465
804
  for row in ledgers:
466
805
  emit(f" rejected ledger -> {row}")
467
- if not ledgers:
806
+ for row in manifests:
807
+ emit(f" rejected manifest -> {row}")
808
+ if not ledgers and not manifests:
468
809
  emit(
469
810
  " no committed ledger records this candidate; run the extraction review "
470
811
  "lane against the final, committed tree"
@@ -307,6 +307,191 @@ out="$(run_scope --base "$SCOPE_BASE" --print-candidate)"; rc=$?
307
307
  check "the same clean checkout still reaches its normal verdict" \
308
308
  '[ "$rc" = 0 ] && case "$out" in *"no reviewed-path change"*) true;; *) false;; esac'
309
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
+
310
495
  # The suite above exercises a synthetic repository. The gate must also run against
311
496
  # the real checkout it ships in, or a break in that path passes every test here.
312
497
  REAL_ROOT="$(cd "$DIR/../../.." && pwd -P)"