@ccoalm/ccl-skills 0.7.0 → 0.9.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 (127) hide show
  1. package/README.md +2 -2
  2. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/SKILL.md +8 -7
  3. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/mobile-quality-release.md +6 -1
  4. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/SKILL.md +16 -17
  5. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/client-routing.md +1 -1
  6. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/manual-invocation-and-prompts.md +6 -0
  7. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/staged-review-contract.md +195 -7
  8. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/timeout-auth-and-capabilities.md +3 -3
  9. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/claude_review.sh +13 -5
  10. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/codex_review.sh +9 -3
  11. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/kimi_review.sh +9 -3
  12. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/normalize_review_timeout.sh +22 -0
  13. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/opencode_review.sh +9 -3
  14. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +1540 -129
  15. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_claude_review_probe.sh +8 -3
  16. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_client_compat.py +76 -1
  17. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +1858 -3
  18. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_update_review_plan_intent.sh +789 -0
  19. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/update_review_plan_intent.py +513 -0
  20. package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/SKILL.md +1 -0
  21. package/dist/assets/marketplace/plugins/ccl-skills/skills/feature-risk-router/SKILL.md +3 -1
  22. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/architecture-playbook.md +1 -1
  23. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/multi-tenant-isolation.md +1 -1
  24. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/SKILL.md +4 -1
  25. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/state-machine-task-patterns.md +2 -0
  26. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/SKILL.md +2 -1
  27. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/inference-capacity-operations.md +24 -0
  28. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/llm-client-gateway.md +1 -1
  29. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/model-prompt-evaluation.md +4 -1
  30. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/SKILL.md +11 -10
  31. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/contracts-and-state.md +5 -0
  32. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/SKILL.md +64 -0
  33. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/agents/openai.yaml +4 -0
  34. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/async-lifecycle-and-performance.md +73 -0
  35. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/runtime-and-project-contract.md +58 -0
  36. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/source-map.md +41 -0
  37. package/dist/assets/marketplace/plugins/ccl-skills/skills/nodejs-service-dev/references/verification-diagnostics-and-security.md +63 -0
  38. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/SKILL.md +3 -2
  39. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/metrics-conventions.md +8 -1
  40. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/sli-slo-design.md +2 -2
  41. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/canary-and-rollout-strategy.md +16 -2
  42. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/promotion-gate-and-review.md +9 -0
  43. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/SKILL.md +14 -16
  44. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/code-review-checklist.md +4 -0
  45. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/delivery-lifecycle.md +1 -1
  46. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/design-routing-and-readiness.md +10 -14
  47. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/rd-standards-doc-family-checklist.md +1 -0
  48. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/verify-developer-experience.md +1 -1
  49. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/SKILL.md +135 -86
  50. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/behavioral-aesthetic-logic.md +66 -80
  51. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/delivery-contract.md +275 -0
  52. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-execution-checklist.md +88 -214
  53. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-impl-naming-and-versioning.md +2 -2
  54. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-intake-and-acceptance.md +10 -8
  55. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-system-source-of-truth.md +6 -5
  56. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/external-ui-ux-quality-benchmarks.md +112 -95
  57. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/frontend-code-evidence-map.md +30 -21
  58. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/interaction-design-patterns.md +22 -3
  59. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/layout-recipes-and-screenshot-acceptance.md +20 -17
  60. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/multi-project-token-consistency.md +7 -9
  61. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/multi-stack-strategy.md +14 -10
  62. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/operational-processing-workflows.md +2 -0
  63. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/platform-mobile-patterns.md +3 -3
  64. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/product-lifecycle-acceptance-and-iteration.md +9 -6
  65. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/product-surface-patterns.md +3 -0
  66. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/source-map.md +37 -10
  67. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/tokens-and-components.md +8 -1
  68. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/ui-ux-audit.md +16 -5
  69. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/ui-ux-design-development.md +16 -5
  70. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/visual-craft.md +4 -2
  71. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/multi-tenant-isolation.md +1 -1
  72. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/SKILL.md +4 -1
  73. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/state-machine-task-patterns.md +2 -0
  74. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/SKILL.md +1 -1
  75. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md +8 -8
  76. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/description-authoring.md +4 -0
  77. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/dual-track-review-gate.md +104 -5
  78. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-quickstart.md +11 -9
  79. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/r0-leakage-audit.md +102 -0
  80. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +103 -0
  81. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-to-skill-extraction.md +20 -0
  82. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/uiux-judgment-extraction.md +6 -6
  83. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/validation-and-landing.md +4 -3
  84. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-ccl-skills.sh +69 -2
  85. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/extraction_review_gate.sh +22 -0
  86. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +49 -4
  87. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/obligation-ledger.py +2748 -0
  88. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/register-firing-path-resolution.rb +20 -5
  89. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/shared_git_surface_gate.py +1142 -0
  90. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +17 -0
  91. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_skill_catalog.sh +41 -4
  92. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_ci_checkout_ref_binding.sh +120 -0
  93. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_entrypoint_domain_scan_terms.sh +82 -8
  94. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_extraction_review_gate.sh +336 -0
  95. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_self_adjudication.sh +82 -10
  96. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_obligation_ledger.sh +1416 -0
  97. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_obligation_ledger_repo_audit.sh +57 -0
  98. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_register_firing_path_wiring.sh +141 -4
  99. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_routing_pointer_integrity.sh +3 -1
  100. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_shared_git_surface_gate.sh +1696 -0
  101. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_uiux_delivery_contract.sh +2117 -0
  102. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_uiux_loading_budget.sh +316 -0
  103. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_extraction_review_state.sh +1176 -0
  104. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_skill_cross_refs.sh +31 -1
  105. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/validate-skill.sh +9 -4
  106. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/validate_extraction_review_state.py +980 -0
  107. package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/SKILL.md +8 -6
  108. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/classical-test-design-techniques.md +1 -1
  109. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc-review-and-prioritization.md +1 -1
  110. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/update-lifecycle.md +2 -0
  111. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/SKILL.md +16 -15
  112. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/ci-fixtures-and-flake-control.md +5 -1
  113. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/client-runtime-test-matrices.md +10 -2
  114. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/e2e-real-flow-testing.md +2 -2
  115. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/integration-contract-testing.md +10 -0
  116. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/test-code-authoring-patterns.md +2 -2
  117. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/test-topology-and-commands.md +1 -1
  118. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/SKILL.md +2 -1
  119. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/annotation-driven-revision.md +9 -0
  120. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/figure-and-table-craft.md +8 -2
  121. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/SKILL.md +7 -5
  122. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/complex-workspace-patterns.md +1 -1
  123. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/react-architecture.md +3 -0
  124. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/web-quality-release.md +37 -4
  125. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/web-ui-quality.md +10 -1
  126. package/dist/assets/release.json +215 -105
  127. package/package.json +1 -1
@@ -205,7 +205,7 @@ PYTHONPATH="$WORK/harness/scripts" python3 - "$WORK" <<'PY'
205
205
  from pathlib import Path
206
206
  import sys
207
207
 
208
- from review_gate import candidate_paths_from_packet, derive_owner_selection
208
+ from review_gate import candidate_paths_from_packet, decode_git_c_path, derive_owner_selection
209
209
 
210
210
  registry = Path(sys.argv[1])
211
211
  packet = (
@@ -216,10 +216,23 @@ packet = (
216
216
  b'--- a/skills/fake-owner/SKILL.md\n'
217
217
  b'+++ b/skills/fake-owner/SKILL.md\n'
218
218
  b'diff --git "a/services/caf\\303\\251.py" "b/services/caf\\303\\251.py"\n'
219
+ + (
220
+ 'diff --git "a/services/caf\N{LATIN SMALL LETTER E WITH ACUTE} file.py" '
221
+ '"b/services/caf\N{LATIN SMALL LETTER E WITH ACUTE} file.py"\n'
222
+ ).encode()
219
223
  )
220
224
  paths = candidate_paths_from_packet(packet)
221
225
  assert "services/my file.py" in paths, paths
222
226
  assert "services/caf\N{LATIN SMALL LETTER E WITH ACUTE}.py" in paths, paths
227
+ assert "services/caf\N{LATIN SMALL LETTER E WITH ACUTE} file.py" in paths, paths
228
+ assert decode_git_c_path(
229
+ '"caf\N{LATIN SMALL LETTER E WITH ACUTE} file\\\\name.md"', strict_utf8=True
230
+ ) == "caf\N{LATIN SMALL LETTER E WITH ACUTE} file\\name.md"
231
+ assert decode_git_c_path('"caf\\303\\251.md"', strict_utf8=True) == "caf\N{LATIN SMALL LETTER E WITH ACUTE}.md"
232
+ assert decode_git_c_path('"python\\x2descape.md"', strict_utf8=True) is None
233
+ assert decode_git_c_path('"bad\\377.md"', strict_utf8=True) is None
234
+ assert decode_git_c_path('"bad\\400.md"', strict_utf8=True) is None
235
+ assert decode_git_c_path('"bad\\777.md"', strict_utf8=True) is None
223
236
  assert "skills/fake-owner/SKILL.md" not in paths, paths
224
237
  assert not any(
225
238
  row["skill"] == "README.md"
@@ -556,6 +569,71 @@ if [ "$?" -ne 0 ]; then
556
569
  exit 1
557
570
  fi
558
571
 
572
+ PYTHONPATH="$WORK/harness/scripts" python3 - "$WORK" <<'PY'
573
+ from pathlib import Path
574
+ import sys
575
+ import unicodedata
576
+
577
+ from review_gate import (
578
+ _validate_untracked_path_text,
579
+ GateError,
580
+ candidate_paths_from_packet,
581
+ derive_owner_selection,
582
+ render_untracked_file,
583
+ )
584
+
585
+ registry = Path(sys.argv[1])
586
+ probe = registry / "untracked-path-probe"
587
+ probe.write_text("probe\n", encoding="utf-8")
588
+ metadata = probe.stat()
589
+ probe.unlink()
590
+
591
+ relative_paths = (
592
+ "skills/testing-strategy/references/café.py",
593
+ "skills/testing-strategy/references/emoji-🚀.py",
594
+ "skills/testing-strategy/references/space name.py",
595
+ 'skills/testing-strategy/references/quote"name.py',
596
+ "skills/testing-strategy/references/back\\slash.py",
597
+ )
598
+ for relative in relative_paths:
599
+ packet = render_untracked_file(relative, b"print('ok')\n", metadata)
600
+ paths = candidate_paths_from_packet(packet)
601
+ assert paths == [relative], (relative, paths, packet)
602
+
603
+ for codepoint in range(sys.maxunicode + 1):
604
+ character = chr(codepoint)
605
+ if unicodedata.category(character) not in {"Cc", "Zl", "Zp"}:
606
+ continue
607
+ try:
608
+ _validate_untracked_path_text(f"before{character}after.py")
609
+ except GateError as exc:
610
+ assert "control-character" in exc.reason, (codepoint, exc.reason)
611
+ else:
612
+ raise AssertionError(f"transport control U+{codepoint:04X} was accepted")
613
+
614
+ owners = {
615
+ row["skill"]
616
+ for row in derive_owner_selection([relative_paths[0]], registry)
617
+ }
618
+ assert {"python-service-dev", "testing-strategy"}.issubset(owners), owners
619
+
620
+ try:
621
+ render_untracked_file(
622
+ "skills/testing-strategy/references/non-utf8-\udcff.py",
623
+ b"print('ok')\n",
624
+ metadata,
625
+ )
626
+ except GateError as exc:
627
+ assert exc.reason_code == "invalid_input", exc.reason_code
628
+ assert "non-UTF-8 untracked path" in exc.reason, exc.reason
629
+ else:
630
+ raise AssertionError("surrogate-bearing untracked path did not fail closed")
631
+ PY
632
+ if [ "$?" -ne 0 ]; then
633
+ printf 'FAIL - untracked path rendering round-trips Unicode, quoting, and owner routing\n' >&2
634
+ exit 1
635
+ fi
636
+
559
637
  cat >"$WORK/harness/scripts/claude_review.sh" <<'CLAUDE_STUB'
560
638
  #!/usr/bin/env bash
561
639
  set -u
@@ -614,6 +692,10 @@ print(json.dumps([
614
692
  ),
615
693
  }
616
694
  for item in profile["required_concerns"]
695
+ if not (
696
+ sys.argv[2] == "missing_wording_boundary"
697
+ and item["id"] == "wording_only_boundary"
698
+ )
617
699
  ], separators=(",", ":")))
618
700
  PY
619
701
  )"
@@ -621,6 +703,7 @@ native_skill_binding="not_requested"
621
703
  [ -z "$review_skills" ] || native_skill_binding="established"
622
704
  case "$behavior" in
623
705
  passed) printf '{"mode":"%s","native_skill_binding":"%s","concern_results":%s,"findings":[]}\n' "$mode" "$native_skill_binding" "$concern_results"; exit 0 ;;
706
+ missing_wording_boundary) printf '{"mode":"%s","native_skill_binding":"%s","concern_results":%s,"findings":[]}\n' "$mode" "$native_skill_binding" "$concern_results"; exit 0 ;;
624
707
  missing_binding) printf '{"mode":"%s","concern_results":%s,"findings":[]}\n' "$mode" "$concern_results"; exit 0 ;;
625
708
  passed_slow)
626
709
  sleep 5
@@ -1300,6 +1383,38 @@ check "a non-regular entrypoint in an explicit owner package is terminal" \
1300
1383
  rmdir "$WORK/testing-strategy/SKILL.md"
1301
1384
  mv "$WORK/testing-strategy/SKILL.md.real" "$WORK/testing-strategy/SKILL.md"
1302
1385
 
1386
+ python3 - "$WORK/harness/scripts/oversized-runtime.py" <<'PY'
1387
+ import os
1388
+ import sys
1389
+
1390
+ path = sys.argv[1]
1391
+ open(path, "wb").close()
1392
+ os.truncate(path, 1_048_577)
1393
+ PY
1394
+ reset_case passed unavailable unavailable
1395
+ out="$(run_gate)"; rc=$?
1396
+ check "an oversized controller-runtime file fails before provider execution" \
1397
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=local_tool_failure'
1398
+ rm -f "$WORK/harness/scripts/oversized-runtime.py"
1399
+
1400
+ mkdir -p "$WORK/harness/scripts/runtime-aggregate"
1401
+ python3 - "$WORK/harness/scripts/runtime-aggregate" <<'PY'
1402
+ import os
1403
+ import sys
1404
+ from pathlib import Path
1405
+
1406
+ root = Path(sys.argv[1])
1407
+ for index in range(9):
1408
+ path = root / f"part-{index}.py"
1409
+ path.touch()
1410
+ os.truncate(path, 1_000_000)
1411
+ PY
1412
+ reset_case passed unavailable unavailable
1413
+ out="$(run_gate)"; rc=$?
1414
+ check "an oversized controller-runtime aggregate fails before provider execution" \
1415
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=local_tool_failure'
1416
+ rm -rf "$WORK/harness/scripts/runtime-aggregate"
1417
+
1303
1418
  reset_case passed unavailable unavailable
1304
1419
  out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
1305
1420
  --mode review --cwd "$WORK/repo" --diff-file "$WORK/large-diff.patch" \
@@ -1321,6 +1436,279 @@ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.s
1321
1436
  check "a valid plan near the documented 32 KB input limit can render its profile" \
1322
1437
  '[ "$(wc -c <"$WORK/near-limit-plan.json")" -le 32000 ] && [ "$rc" = 0 ] && [ "$(cat "$WORK/state/client_sequence")" = claude ]'
1323
1438
 
1439
+ ln -s "$WORK/review-plan.json" "$WORK/symlink-review-plan.json"
1440
+ reset_case passed unavailable unavailable
1441
+ out="$(run_gate --review-plan-file "$WORK/symlink-review-plan.json")"; rc=$?
1442
+ check "a symlinked review plan fails before provider execution" \
1443
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=invalid_input'
1444
+ unlink "$WORK/symlink-review-plan.json"
1445
+
1446
+ cp "$WORK/review-plan.json" "$WORK/hardlink-review-plan.json"
1447
+ ln "$WORK/hardlink-review-plan.json" "$WORK/hardlink-review-plan-alias.json"
1448
+ reset_case passed unavailable unavailable
1449
+ out="$(run_gate --review-plan-file "$WORK/hardlink-review-plan.json")"; rc=$?
1450
+ check "a hardlinked review plan fails before provider execution" \
1451
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=invalid_input'
1452
+ unlink "$WORK/hardlink-review-plan-alias.json"
1453
+ unlink "$WORK/hardlink-review-plan.json"
1454
+
1455
+ mkfifo "$WORK/fifo-review-plan.json"
1456
+ reset_case passed unavailable unavailable
1457
+ fifo_probe="$(python3 - \
1458
+ "$WORK/harness/scripts/review_gate.py" \
1459
+ "$WORK/repo" \
1460
+ "$WORK/diff.patch" \
1461
+ "$WORK/fifo-review-plan.json" \
1462
+ "$WORK/state" <<'PY'
1463
+ import json
1464
+ import os
1465
+ import subprocess
1466
+ import sys
1467
+
1468
+ gate, cwd, diff, plan, state = sys.argv[1:]
1469
+ environment = os.environ.copy()
1470
+ environment["REVIEW_GATE_TEST_STATE"] = state
1471
+ try:
1472
+ completed = subprocess.run(
1473
+ [
1474
+ sys.executable,
1475
+ gate,
1476
+ "--mode", "review",
1477
+ "--cwd", cwd,
1478
+ "--diff-file", diff,
1479
+ "--implementer-family", "openai",
1480
+ "--review-plan-file", plan,
1481
+ ],
1482
+ text=True,
1483
+ stdout=subprocess.PIPE,
1484
+ stderr=subprocess.PIPE,
1485
+ timeout=1,
1486
+ env=environment,
1487
+ check=False,
1488
+ )
1489
+ except subprocess.TimeoutExpired as exc:
1490
+ raise AssertionError("FIFO review-plan open blocked") from exc
1491
+ assert completed.returncode == 2, (completed.returncode, completed.stdout, completed.stderr)
1492
+ payload = json.loads(completed.stdout)
1493
+ assert payload["reason_code"] == "invalid_input", payload
1494
+ assert "Traceback" not in completed.stderr, completed.stderr
1495
+ print("ok")
1496
+ PY
1497
+ )"
1498
+ fifo_probe_rc=$?
1499
+ check "a FIFO review plan fails quickly before provider execution" \
1500
+ '[ "$fifo_probe_rc" = 0 ] && [ "$fifo_probe" = ok ] && [ ! -e "$WORK/state/client_sequence" ]'
1501
+
1502
+ python3 - "$WORK/review-plan.json" "$WORK/surrogate-review-plan.json" <<'PY'
1503
+ import json
1504
+ import sys
1505
+ from pathlib import Path
1506
+
1507
+ source, target = map(Path, sys.argv[1:])
1508
+ plan = json.loads(source.read_text(encoding="utf-8"))
1509
+ plan["intent"] = "Reject this escaped lone surrogate: \ud800"
1510
+ target.write_text(json.dumps(plan, ensure_ascii=True), encoding="utf-8")
1511
+ PY
1512
+ reset_case passed unavailable unavailable
1513
+ out="$(run_gate --review-plan-file "$WORK/surrogate-review-plan.json" 2>&1)"; rc=$?
1514
+ check "an escaped lone surrogate is a stable rc2 before provider execution" \
1515
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=invalid_input completion_gated=true'
1516
+
1517
+ python3 - "$WORK/oversized-review-plan.json" <<'PY'
1518
+ import sys
1519
+ from pathlib import Path
1520
+
1521
+ Path(sys.argv[1]).write_bytes(b"x" * 32001)
1522
+ PY
1523
+ reset_case passed unavailable unavailable
1524
+ out="$(run_gate --review-plan-file "$WORK/oversized-review-plan.json")"; rc=$?
1525
+ check "an oversized review plan fails before provider execution" \
1526
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=invalid_input'
1527
+
1528
+ # The path checks for diff/prior/completion inputs must bind the bytes to one
1529
+ # already-open descriptor. Simulate the old check-then-open race by replacing a
1530
+ # regular path with a symlink immediately after Path.is_symlink() reports clean.
1531
+ # A secure open-once implementation never calls that pathname predicate, so it
1532
+ # either reads the original inode or rejects the input; it can never consume the
1533
+ # alternate bytes behind the replacement link.
1534
+ file_input_race_probe="$(PYTHONPATH="$WORK/harness/scripts" python3 - "$WORK" <<'PY' 2>&1
1535
+ import contextlib
1536
+ import ast
1537
+ import hashlib
1538
+ import json
1539
+ import os
1540
+ from pathlib import Path
1541
+ from types import SimpleNamespace
1542
+ import sys
1543
+ import time
1544
+
1545
+ import review_gate
1546
+
1547
+
1548
+ @contextlib.contextmanager
1549
+ def replace_after_symlink_check(source: Path, alternate: Path):
1550
+ original_is_symlink = Path.is_symlink
1551
+ backup = source.with_name(source.name + ".before-link-swap")
1552
+ swapped = False
1553
+
1554
+ def injecting_is_symlink(path: Path) -> bool:
1555
+ nonlocal swapped
1556
+ result = original_is_symlink(path)
1557
+ if path == source and not swapped:
1558
+ source.rename(backup)
1559
+ source.symlink_to(alternate)
1560
+ swapped = True
1561
+ return result
1562
+
1563
+ Path.is_symlink = injecting_is_symlink
1564
+ try:
1565
+ yield
1566
+ finally:
1567
+ Path.is_symlink = original_is_symlink
1568
+ if swapped:
1569
+ source.unlink()
1570
+ backup.rename(source)
1571
+
1572
+
1573
+ @contextlib.contextmanager
1574
+ def replace_after_first_fstat(source: Path, alternate: Path):
1575
+ """Replace the pathname after the reader has already opened its inode."""
1576
+
1577
+ original_fstat = os.fstat
1578
+ original_inode = source.stat().st_ino
1579
+ backup = source.with_name(source.name + ".after-open-link-swap")
1580
+ swapped = False
1581
+
1582
+ def injecting_fstat(fd: int):
1583
+ nonlocal swapped
1584
+ metadata = original_fstat(fd)
1585
+ if metadata.st_ino == original_inode and not swapped:
1586
+ source.rename(backup)
1587
+ source.symlink_to(alternate)
1588
+ swapped = True
1589
+ return metadata
1590
+
1591
+ os.fstat = injecting_fstat
1592
+ try:
1593
+ yield
1594
+ finally:
1595
+ os.fstat = original_fstat
1596
+ if swapped:
1597
+ source.unlink()
1598
+ backup.rename(source)
1599
+
1600
+
1601
+ root = Path(sys.argv[1]) / "file-input-race"
1602
+ root.mkdir()
1603
+
1604
+ original_diff = (
1605
+ b"diff --git a/safe b/safe\n--- a/safe\n+++ b/safe\n"
1606
+ b"@@ -1 +1 @@\n-old\n+safe\n"
1607
+ )
1608
+ alternate_diff = (
1609
+ b"diff --git a/other b/other\n--- a/other\n+++ b/other\n"
1610
+ b"@@ -1 +1 @@\n-old\n+alternate\n"
1611
+ )
1612
+ diff_source = root / "candidate.patch"
1613
+ diff_alternate = root / "alternate.patch"
1614
+ diff_source.write_bytes(original_diff)
1615
+ diff_alternate.write_bytes(alternate_diff)
1616
+ with replace_after_symlink_check(diff_source, diff_alternate):
1617
+ packet_path, digest, _, _ = review_gate.freeze_packet(
1618
+ SimpleNamespace(
1619
+ cwd=str(root), diff_file=str(diff_source), base=None, paths=[]
1620
+ ),
1621
+ time.monotonic() + 5,
1622
+ )
1623
+ try:
1624
+ assert packet_path.read_bytes() == original_diff
1625
+ assert digest == hashlib.sha256(original_diff).hexdigest()
1626
+ finally:
1627
+ packet_path.unlink()
1628
+
1629
+ # Both --prior-review-result-file and --completion-review-result-file consume
1630
+ # this loader. Exercise two independent path replacements so neither call site
1631
+ # can regress to check-then-open behavior under a later refactor.
1632
+ for label in ("prior", "completion"):
1633
+ source = root / f"{label}.json"
1634
+ alternate = root / f"{label}-alternate.json"
1635
+ original_bytes = json.dumps({"source": label}).encode()
1636
+ alternate.write_text(json.dumps({"source": "alternate"}), encoding="utf-8")
1637
+ source.write_bytes(original_bytes)
1638
+ with replace_after_symlink_check(source, alternate):
1639
+ payload, digest = review_gate._load_prior_review_result(str(source), 1)
1640
+ assert payload == {"source": label}
1641
+ assert digest == hashlib.sha256(original_bytes).hexdigest()
1642
+
1643
+ # Opening once prevents following an attacker-selected replacement, but the
1644
+ # gate also needs to reject that the pathname stopped naming the opened inode.
1645
+ # Otherwise a successful result can be attributed to bytes that are no longer
1646
+ # the candidate at the point the freeze returns.
1647
+ swap_source = root / "opened-then-swapped.patch"
1648
+ swap_alternate = root / "opened-then-swapped-alternate.patch"
1649
+ swap_source.write_bytes(original_diff)
1650
+ swap_alternate.write_bytes(alternate_diff)
1651
+ with replace_after_first_fstat(swap_source, swap_alternate):
1652
+ try:
1653
+ review_gate.freeze_packet(
1654
+ SimpleNamespace(
1655
+ cwd=str(root), diff_file=str(swap_source), base=None, paths=[]
1656
+ ),
1657
+ time.monotonic() + 5,
1658
+ )
1659
+ except review_gate.GateError as exc:
1660
+ assert "changed" in exc.reason, exc.reason
1661
+ else:
1662
+ raise AssertionError("a pathname replaced after open was accepted")
1663
+
1664
+ # Frozen packet/profile verification and owner/controller hashing used to
1665
+ # reopen by pathname as well. Keep a structural assertion beside the live race
1666
+ # probes so a new Path.read_bytes() call cannot silently recreate that class.
1667
+ tree = ast.parse(Path(review_gate.__file__).read_text(encoding="utf-8"))
1668
+ path_reads = [
1669
+ node
1670
+ for node in ast.walk(tree)
1671
+ if isinstance(node, ast.Call)
1672
+ and isinstance(node.func, ast.Attribute)
1673
+ and node.func.attr in {"read_bytes", "read_text"}
1674
+ ]
1675
+ assert not path_reads, [(node.func.attr, node.lineno) for node in path_reads]
1676
+
1677
+ # Resource bounds are checked from fstat before reading. A sparse oversized
1678
+ # owner file must fail closed instead of allocating its declared size, and an
1679
+ # individually valid set must still respect the package aggregate ceiling.
1680
+ oversized_skill = root / "oversized-skill"
1681
+ oversized_skill.mkdir()
1682
+ (oversized_skill / "SKILL.md").touch()
1683
+ os.truncate(oversized_skill / "SKILL.md", 1_048_577)
1684
+ try:
1685
+ review_gate._hash_skill_package(oversized_skill, "oversized-skill")
1686
+ except review_gate.GateError as exc:
1687
+ assert exc.reason_code == "local_tool_failure", exc.reason_code
1688
+ else:
1689
+ raise AssertionError("an oversized selected-skill file was hashed")
1690
+
1691
+ aggregate_skill = root / "aggregate-skill"
1692
+ (aggregate_skill / "references").mkdir(parents=True)
1693
+ (aggregate_skill / "SKILL.md").write_text("# aggregate\n", encoding="utf-8")
1694
+ for index in range(9):
1695
+ part = aggregate_skill / "references" / f"part-{index}.md"
1696
+ part.touch()
1697
+ os.truncate(part, 1_000_000)
1698
+ try:
1699
+ review_gate._hash_skill_package(aggregate_skill, "aggregate-skill")
1700
+ except review_gate.GateError as exc:
1701
+ assert exc.reason_code == "local_tool_failure", exc.reason_code
1702
+ else:
1703
+ raise AssertionError("an oversized selected-skill package was hashed")
1704
+
1705
+ print("open_once_file_inputs_ok")
1706
+ PY
1707
+ )"
1708
+ file_input_race_rc=$?
1709
+ check "diff, prior, and completion inputs are read once from a bounded opened descriptor" \
1710
+ '[ "$file_input_race_rc" = 0 ] && [ "$file_input_race_probe" = open_once_file_inputs_ok ]'
1711
+
1324
1712
  reset_case missing_coverage passed unavailable
1325
1713
  out="$(run_gate --diff-file "$WORK/secret-diff.patch")"; rc=$?
1326
1714
  check "missing coverage cannot widen egress for a secret-bearing diff without approval" \
@@ -1665,14 +2053,1065 @@ out="$(run_gate --stage explore --risk-tag shared-gate --review-plan-file "$WORK
1665
2053
  check "high-risk tags raise explore to release depth and default one challenge" \
1666
2054
  '[ "$rc" = 0 ] && json_fields "$out" stage=explore stage_source=caller-declared review_depth=release risk_tags_source=caller-declared challenge_budget=1 challenge_rounds_remaining=1 review_chain_tracked=true review_chain_id=high-risk-task autonomous_review_budget=2 autonomous_review_index=1 autonomous_reviews_remaining=1 autonomous_review_allowed=true next_action=run_challenge completion_gated=true risk_tags.0=shared-gate reviewed_concerns.7=high_risk_boundary self_review_gate.required=false self_review_gate.satisfied_triggers.0=before_external_review self_review_gate.satisfied_triggers.1=risk_or_scope_escalation'
1667
2055
 
2056
+ # Release/high-risk normally requires a challenge. The only single-review
2057
+ # exception is a candidate-bound deterministic wording-only proof whose result
2058
+ # the controller can reproduce from the frozen canonical unified packet.
2059
+ mkdir -p "$WORK/wording-reviewed-repo/skills/code-review/references"
2060
+ printf '%s\n' '# Code Review' \
2061
+ >"$WORK/wording-reviewed-repo/skills/code-review/SKILL.md"
2062
+ for target in \
2063
+ fenced-backtick.md \
2064
+ fenced-tilde.md \
2065
+ fenced-unclosed.md \
2066
+ fenced-list-bullet.md \
2067
+ fenced-list-ordered.md \
2068
+ fenced-list-indented.md \
2069
+ indented-space.md \
2070
+ indented-tab.md \
2071
+ indented-list.md \
2072
+ html-pre.md \
2073
+ html-code.md \
2074
+ inline-code.md \
2075
+ link-destination.md \
2076
+ heading-structure.md \
2077
+ list-structure.md \
2078
+ table-structure.md \
2079
+ after-closed-fence.md \
2080
+ after-closed-list-fence.md \
2081
+ ordinary-list-punctuation.md \
2082
+ numeric-merge.md \
2083
+ fenced-token.md \
2084
+ question-mark.md; do
2085
+ printf '%s\n' '# Existing Markdown fixture' \
2086
+ >"$WORK/wording-reviewed-repo/skills/code-review/references/$target"
2087
+ done
2088
+ mkdir -p "$WORK/repo/skills/code-review/references"
2089
+ printf '%s\n' '# Reviewed Code Review' >"$WORK/repo/skills/code-review/SKILL.md"
2090
+ for target in \
2091
+ punctuation.md \
2092
+ 'café file.md' \
2093
+ typo.md \
2094
+ normative.md \
2095
+ zero-width.md \
2096
+ bidi-control.md \
2097
+ emoji-symbol.md \
2098
+ currency-symbol.md \
2099
+ decomposed-boundary.md \
2100
+ zwj-boundary.md; do
2101
+ printf '%s\n' '# Existing Markdown fixture' \
2102
+ >"$WORK/repo/skills/code-review/references/$target"
2103
+ done
2104
+ mkdir -p "$WORK/wording-missing-package-repo"
2105
+ mkdir -p "$WORK/wording-repo-only/skills/repo-only/references"
2106
+ printf '%s\n' '# Repo Only' \
2107
+ >"$WORK/wording-repo-only/skills/repo-only/SKILL.md"
2108
+ printf '%s\n' '# Existing Markdown fixture' \
2109
+ >"$WORK/wording-repo-only/skills/repo-only/references/punctuation.md"
2110
+ mkdir -p "$WORK/wording-linked-package-repo/skills"
2111
+ ln -s "$WORK/wording-reviewed-repo/skills/code-review" \
2112
+ "$WORK/wording-linked-package-repo/skills/code-review"
2113
+ mkdir -p "$WORK/wording-hardlinked-package-repo/skills/code-review"
2114
+ printf '%s\n' '# Hardlinked Code Review' \
2115
+ >"$WORK/wording-hardlinked-package-repo/skills/code-review/SKILL.md"
2116
+ ln "$WORK/wording-hardlinked-package-repo/skills/code-review/SKILL.md" \
2117
+ "$WORK/wording-hardlinked-package-repo/skills/code-review/SKILL.alias"
2118
+ mkdir -p "$WORK/wording-nonregular-package-repo/skills/code-review/SKILL.md"
2119
+ mkdir -p "$WORK/wording-oversized-package-repo/skills/code-review"
2120
+ python3 - "$WORK/wording-oversized-package-repo/skills/code-review/SKILL.md" <<'PY'
2121
+ import os
2122
+ from pathlib import Path
2123
+ import sys
2124
+
2125
+ path = Path(sys.argv[1])
2126
+ path.touch()
2127
+ os.truncate(path, 1_048_577)
2128
+ PY
2129
+ for unsafe_target in missing linked hardlinked nonregular oversized; do
2130
+ mkdir -p \
2131
+ "$WORK/wording-$unsafe_target-target-repo/skills/code-review/references"
2132
+ printf '%s\n' '# Code Review' \
2133
+ >"$WORK/wording-$unsafe_target-target-repo/skills/code-review/SKILL.md"
2134
+ done
2135
+ ln -s "$WORK/repo/skills/code-review/references/punctuation.md" \
2136
+ "$WORK/wording-linked-target-repo/skills/code-review/references/punctuation.md"
2137
+ printf '%s\n' '# Hardlinked Markdown fixture' \
2138
+ >"$WORK/wording-hardlinked-target-repo/skills/code-review/references/punctuation.md"
2139
+ ln "$WORK/wording-hardlinked-target-repo/skills/code-review/references/punctuation.md" \
2140
+ "$WORK/wording-hardlinked-target-repo/skills/code-review/references/punctuation.alias"
2141
+ mkdir \
2142
+ "$WORK/wording-nonregular-target-repo/skills/code-review/references/punctuation.md"
2143
+ python3 - "$WORK/wording-oversized-target-repo/skills/code-review/references/punctuation.md" <<'PY'
2144
+ import os
2145
+ from pathlib import Path
2146
+ import sys
2147
+
2148
+ path = Path(sys.argv[1])
2149
+ path.touch()
2150
+ os.truncate(path, 1_048_577)
2151
+ PY
2152
+ cat >"$WORK/wording-punctuation.patch" <<'DIFF'
2153
+ diff --git a/skills/code-review/references/punctuation.md b/skills/code-review/references/punctuation.md
2154
+ index 1111111..2222222 100644
2155
+ --- a/skills/code-review/references/punctuation.md
2156
+ +++ b/skills/code-review/references/punctuation.md
2157
+ @@ -1 +1 @@
2158
+ -Review remains exact.
2159
+ +Review remains exact!
2160
+ DIFF
2161
+ cat >"$WORK/repo-only-wording.patch" <<'DIFF'
2162
+ diff --git a/skills/repo-only/references/punctuation.md b/skills/repo-only/references/punctuation.md
2163
+ index 1111111..2222222 100644
2164
+ --- a/skills/repo-only/references/punctuation.md
2165
+ +++ b/skills/repo-only/references/punctuation.md
2166
+ @@ -1 +1 @@
2167
+ -Review remains exact.
2168
+ +Review remains exact!
2169
+ DIFF
2170
+ cat >"$WORK/fenced-backtick-wording.patch" <<'DIFF'
2171
+ diff --git a/skills/code-review/references/fenced-backtick.md b/skills/code-review/references/fenced-backtick.md
2172
+ index 1111111..2222222 100644
2173
+ --- a/skills/code-review/references/fenced-backtick.md
2174
+ +++ b/skills/code-review/references/fenced-backtick.md
2175
+ @@ -1,3 +1,3 @@
2176
+ ```bash
2177
+ -command;;
2178
+ +command;
2179
+ ```
2180
+ DIFF
2181
+ cat >"$WORK/fenced-tilde-wording.patch" <<'DIFF'
2182
+ diff --git a/skills/code-review/references/fenced-tilde.md b/skills/code-review/references/fenced-tilde.md
2183
+ index 1111111..2222222 100644
2184
+ --- a/skills/code-review/references/fenced-tilde.md
2185
+ +++ b/skills/code-review/references/fenced-tilde.md
2186
+ @@ -1,3 +1,3 @@
2187
+ ~~~sh
2188
+ -command;;
2189
+ +command;
2190
+ ~~~
2191
+ DIFF
2192
+ cat >"$WORK/after-closed-fence-wording.patch" <<'DIFF'
2193
+ diff --git a/skills/code-review/references/after-closed-fence.md b/skills/code-review/references/after-closed-fence.md
2194
+ index 1111111..2222222 100644
2195
+ --- a/skills/code-review/references/after-closed-fence.md
2196
+ +++ b/skills/code-review/references/after-closed-fence.md
2197
+ @@ -1,4 +1,4 @@
2198
+ ```shell
2199
+ command stays unchanged
2200
+ ```
2201
+ -Review remains exact.
2202
+ +Review remains exact!
2203
+ DIFF
2204
+ cat >"$WORK/fenced-unclosed-wording.patch" <<'DIFF'
2205
+ diff --git a/skills/code-review/references/fenced-unclosed.md b/skills/code-review/references/fenced-unclosed.md
2206
+ index 1111111..2222222 100644
2207
+ --- a/skills/code-review/references/fenced-unclosed.md
2208
+ +++ b/skills/code-review/references/fenced-unclosed.md
2209
+ @@ -1,2 +1,2 @@
2210
+ ```bash
2211
+ -command;;
2212
+ +command;
2213
+ DIFF
2214
+ cat >"$WORK/fenced-list-bullet-wording.patch" <<'DIFF'
2215
+ diff --git a/skills/code-review/references/fenced-list-bullet.md b/skills/code-review/references/fenced-list-bullet.md
2216
+ index 1111111..2222222 100644
2217
+ --- a/skills/code-review/references/fenced-list-bullet.md
2218
+ +++ b/skills/code-review/references/fenced-list-bullet.md
2219
+ @@ -1,3 +1,3 @@
2220
+ - ```bash
2221
+ - command;;
2222
+ + command;
2223
+ ```
2224
+ DIFF
2225
+ cat >"$WORK/fenced-list-ordered-wording.patch" <<'DIFF'
2226
+ diff --git a/skills/code-review/references/fenced-list-ordered.md b/skills/code-review/references/fenced-list-ordered.md
2227
+ index 1111111..2222222 100644
2228
+ --- a/skills/code-review/references/fenced-list-ordered.md
2229
+ +++ b/skills/code-review/references/fenced-list-ordered.md
2230
+ @@ -1,3 +1,3 @@
2231
+ 10. ~~~sh
2232
+ - command;;
2233
+ + command;
2234
+ ~~~
2235
+ DIFF
2236
+ python3 - "$WORK/fenced-list-indented-wording.patch" <<'PY'
2237
+ from pathlib import Path
2238
+ import sys
2239
+
2240
+ Path(sys.argv[1]).write_text(
2241
+ "diff --git a/skills/code-review/references/fenced-list-indented.md "
2242
+ "b/skills/code-review/references/fenced-list-indented.md\n"
2243
+ "index 1111111..2222222 100644\n"
2244
+ "--- a/skills/code-review/references/fenced-list-indented.md\n"
2245
+ "+++ b/skills/code-review/references/fenced-list-indented.md\n"
2246
+ "@@ -1,5 +1,5 @@\n"
2247
+ " - shell example\n"
2248
+ " \n"
2249
+ " ```sh\n"
2250
+ "- command;;\n"
2251
+ "+ command;\n"
2252
+ " ```\n",
2253
+ encoding="utf-8",
2254
+ )
2255
+ PY
2256
+ cat >"$WORK/indented-space-wording.patch" <<'DIFF'
2257
+ diff --git a/skills/code-review/references/indented-space.md b/skills/code-review/references/indented-space.md
2258
+ index 1111111..2222222 100644
2259
+ --- a/skills/code-review/references/indented-space.md
2260
+ +++ b/skills/code-review/references/indented-space.md
2261
+ @@ -1 +1 @@
2262
+ - command;;
2263
+ + command;
2264
+ DIFF
2265
+ python3 - "$WORK/indented-tab-wording.patch" <<'PY'
2266
+ from pathlib import Path
2267
+ import sys
2268
+
2269
+ Path(sys.argv[1]).write_text(
2270
+ "diff --git a/skills/code-review/references/indented-tab.md "
2271
+ "b/skills/code-review/references/indented-tab.md\n"
2272
+ "index 1111111..2222222 100644\n"
2273
+ "--- a/skills/code-review/references/indented-tab.md\n"
2274
+ "+++ b/skills/code-review/references/indented-tab.md\n"
2275
+ "@@ -1 +1 @@\n"
2276
+ "-\tcommand;;\n"
2277
+ "+\tcommand;\n",
2278
+ encoding="utf-8",
2279
+ )
2280
+ PY
2281
+ cat >"$WORK/indented-list-wording.patch" <<'DIFF'
2282
+ diff --git a/skills/code-review/references/indented-list.md b/skills/code-review/references/indented-list.md
2283
+ index 1111111..2222222 100644
2284
+ --- a/skills/code-review/references/indented-list.md
2285
+ +++ b/skills/code-review/references/indented-list.md
2286
+ @@ -1,2 +1,2 @@
2287
+ - item
2288
+ - command;;
2289
+ + command;
2290
+ DIFF
2291
+ cat >"$WORK/html-pre-wording.patch" <<'DIFF'
2292
+ diff --git a/skills/code-review/references/html-pre.md b/skills/code-review/references/html-pre.md
2293
+ index 1111111..2222222 100644
2294
+ --- a/skills/code-review/references/html-pre.md
2295
+ +++ b/skills/code-review/references/html-pre.md
2296
+ @@ -1,3 +1,3 @@
2297
+ <pre>
2298
+ -command;;
2299
+ +command;
2300
+ </pre>
2301
+ DIFF
2302
+ cat >"$WORK/html-code-wording.patch" <<'DIFF'
2303
+ diff --git a/skills/code-review/references/html-code.md b/skills/code-review/references/html-code.md
2304
+ index 1111111..2222222 100644
2305
+ --- a/skills/code-review/references/html-code.md
2306
+ +++ b/skills/code-review/references/html-code.md
2307
+ @@ -1,3 +1,3 @@
2308
+ <code>
2309
+ -command;;
2310
+ +command;
2311
+ </code>
2312
+ DIFF
2313
+ cat >"$WORK/inline-code-wording.patch" <<'DIFF'
2314
+ diff --git a/skills/code-review/references/inline-code.md b/skills/code-review/references/inline-code.md
2315
+ index 1111111..2222222 100644
2316
+ --- a/skills/code-review/references/inline-code.md
2317
+ +++ b/skills/code-review/references/inline-code.md
2318
+ @@ -1 +1 @@
2319
+ -`command;;`
2320
+ +`command;`
2321
+ DIFF
2322
+ cat >"$WORK/link-destination-wording.patch" <<'DIFF'
2323
+ diff --git a/skills/code-review/references/link-destination.md b/skills/code-review/references/link-destination.md
2324
+ index 1111111..2222222 100644
2325
+ --- a/skills/code-review/references/link-destination.md
2326
+ +++ b/skills/code-review/references/link-destination.md
2327
+ @@ -1 +1 @@
2328
+ -[docs](#)
2329
+ +[docs](/)
2330
+ DIFF
2331
+ cat >"$WORK/heading-structure-wording.patch" <<'DIFF'
2332
+ diff --git a/skills/code-review/references/heading-structure.md b/skills/code-review/references/heading-structure.md
2333
+ index 1111111..2222222 100644
2334
+ --- a/skills/code-review/references/heading-structure.md
2335
+ +++ b/skills/code-review/references/heading-structure.md
2336
+ @@ -1 +1 @@
2337
+ -# Review remains exact.
2338
+ +## Review remains exact.
2339
+ DIFF
2340
+ cat >"$WORK/list-structure-wording.patch" <<'DIFF'
2341
+ diff --git a/skills/code-review/references/list-structure.md b/skills/code-review/references/list-structure.md
2342
+ index 1111111..2222222 100644
2343
+ --- a/skills/code-review/references/list-structure.md
2344
+ +++ b/skills/code-review/references/list-structure.md
2345
+ @@ -1 +1 @@
2346
+ -- Review remains exact.
2347
+ +* Review remains exact.
2348
+ DIFF
2349
+ cat >"$WORK/table-structure-wording.patch" <<'DIFF'
2350
+ diff --git a/skills/code-review/references/table-structure.md b/skills/code-review/references/table-structure.md
2351
+ index 1111111..2222222 100644
2352
+ --- a/skills/code-review/references/table-structure.md
2353
+ +++ b/skills/code-review/references/table-structure.md
2354
+ @@ -1 +1 @@
2355
+ -| Review remains exact |
2356
+ +| Review remains exact: |
2357
+ DIFF
2358
+ cat >"$WORK/after-closed-list-fence-wording.patch" <<'DIFF'
2359
+ diff --git a/skills/code-review/references/after-closed-list-fence.md b/skills/code-review/references/after-closed-list-fence.md
2360
+ index 1111111..2222222 100644
2361
+ --- a/skills/code-review/references/after-closed-list-fence.md
2362
+ +++ b/skills/code-review/references/after-closed-list-fence.md
2363
+ @@ -1,4 +1,4 @@
2364
+ - ```shell
2365
+ command stays unchanged
2366
+ ```
2367
+ -Review remains exact.
2368
+ +Review remains exact!
2369
+ DIFF
2370
+ cat >"$WORK/ordinary-list-punctuation-wording.patch" <<'DIFF'
2371
+ diff --git a/skills/code-review/references/ordinary-list-punctuation.md b/skills/code-review/references/ordinary-list-punctuation.md
2372
+ index 1111111..2222222 100644
2373
+ --- a/skills/code-review/references/ordinary-list-punctuation.md
2374
+ +++ b/skills/code-review/references/ordinary-list-punctuation.md
2375
+ @@ -1 +1 @@
2376
+ -- Review remains exact.
2377
+ +- Review remains exact!
2378
+ DIFF
2379
+ cat >"$WORK/numeric-merge-wording.patch" <<'DIFF'
2380
+ diff --git a/skills/code-review/references/numeric-merge.md b/skills/code-review/references/numeric-merge.md
2381
+ index 1111111..2222222 100644
2382
+ --- a/skills/code-review/references/numeric-merge.md
2383
+ +++ b/skills/code-review/references/numeric-merge.md
2384
+ @@ -1 +1 @@
2385
+ -Retries wait 5.5 seconds in total.
2386
+ +Retries wait 55 seconds in total.
2387
+ DIFF
2388
+ cat >"$WORK/question-mark-wording.patch" <<'DIFF'
2389
+ diff --git a/skills/code-review/references/question-mark.md b/skills/code-review/references/question-mark.md
2390
+ index 1111111..2222222 100644
2391
+ --- a/skills/code-review/references/question-mark.md
2392
+ +++ b/skills/code-review/references/question-mark.md
2393
+ @@ -1 +1 @@
2394
+ -Agents must reject unsafe input.
2395
+ +Agents must reject unsafe input?
2396
+ DIFF
2397
+ cat >"$WORK/fenced-token-wording.patch" <<'DIFF'
2398
+ diff --git a/skills/code-review/references/fenced-token.md b/skills/code-review/references/fenced-token.md
2399
+ index 1111111..2222222 100644
2400
+ --- a/skills/code-review/references/fenced-token.md
2401
+ +++ b/skills/code-review/references/fenced-token.md
2402
+ @@ -1,3 +1,3 @@
2403
+ ```shell
2404
+ -This teh command stays quoted.
2405
+ +This the command stays quoted.
2406
+ ```
2407
+ DIFF
2408
+ cat >"$WORK/wording-token.patch" <<'DIFF'
2409
+ diff --git a/skills/code-review/references/typo.md b/skills/code-review/references/typo.md
2410
+ index 1111111..2222222 100644
2411
+ --- a/skills/code-review/references/typo.md
2412
+ +++ b/skills/code-review/references/typo.md
2413
+ @@ -1 +1 @@
2414
+ -This teh rule stays otherwise byte-identical.
2415
+ +This the rule stays otherwise byte-identical.
2416
+ DIFF
2417
+ cat >"$WORK/wording-normative-token.patch" <<'DIFF'
2418
+ diff --git a/skills/code-review/references/normative.md b/skills/code-review/references/normative.md
2419
+ index 1111111..2222222 100644
2420
+ --- a/skills/code-review/references/normative.md
2421
+ +++ b/skills/code-review/references/normative.md
2422
+ @@ -1 +1 @@
2423
+ -Agents must reject unsafe input.
2424
+ +Agents may reject unsafe input.
2425
+ DIFF
2426
+ cat >"$WORK/quoted-unicode-wording.patch" <<'DIFF'
2427
+ diff --git "a/skills/code-review/references/café file.md" "b/skills/code-review/references/café file.md"
2428
+ index 1111111..2222222 100644
2429
+ --- "a/skills/code-review/references/café file.md"
2430
+ +++ "b/skills/code-review/references/café file.md"
2431
+ @@ -1 +1 @@
2432
+ -Review remains exact.
2433
+ +Review remains exact!
2434
+ DIFF
2435
+ cat >"$WORK/non-wording-punctuation.patch" <<'DIFF'
2436
+ diff --git a/skills/demo/scripts/runtime.py b/skills/demo/scripts/runtime.py
2437
+ index 1111111..2222222 100644
2438
+ --- a/skills/demo/scripts/runtime.py
2439
+ +++ b/skills/demo/scripts/runtime.py
2440
+ @@ -1 +1 @@
2441
+ -.
2442
+ +!
2443
+ DIFF
2444
+ cat >"$WORK/multi-skill-wording.patch" <<'DIFF'
2445
+ diff --git a/skills/code-review/references/one.md b/skills/code-review/references/one.md
2446
+ index 1111111..2222222 100644
2447
+ --- a/skills/code-review/references/one.md
2448
+ +++ b/skills/code-review/references/one.md
2449
+ @@ -1 +1,2 @@
2450
+ First package.
2451
+ +***
2452
+ diff --git a/skills/testing-strategy/references/two.md b/skills/testing-strategy/references/two.md
2453
+ index 1111111..2222222 100644
2454
+ --- a/skills/testing-strategy/references/two.md
2455
+ +++ b/skills/testing-strategy/references/two.md
2456
+ @@ -1 +1,2 @@
2457
+ Second package.
2458
+ +***
2459
+ DIFF
2460
+ cat >"$WORK/truncated-context-wording.patch" <<'DIFF'
2461
+ diff --git a/skills/code-review/references/deep.md b/skills/code-review/references/deep.md
2462
+ index 1111111..2222222 100644
2463
+ --- a/skills/code-review/references/deep.md
2464
+ +++ b/skills/code-review/references/deep.md
2465
+ @@ -8 +8 @@
2466
+ -This teh rule is below omitted context.
2467
+ +This the rule is below omitted context.
2468
+ DIFF
2469
+ cat >"$WORK/symlink-mode-wording.patch" <<'DIFF'
2470
+ diff --git a/skills/code-review/references/link.md b/skills/code-review/references/link.md
2471
+ index 1111111..2222222 120000
2472
+ --- a/skills/code-review/references/link.md
2473
+ +++ b/skills/code-review/references/link.md
2474
+ @@ -1 +1 @@
2475
+ -targets/teh
2476
+ +targets/the
2477
+ DIFF
2478
+ cat >"$WORK/frontmatter-shift-insert-wording.patch" <<'DIFF'
2479
+ diff --git a/skills/code-review/SKILL.md b/skills/code-review/SKILL.md
2480
+ index 1111111..2222222 100644
2481
+ --- a/skills/code-review/SKILL.md
2482
+ +++ b/skills/code-review/SKILL.md
2483
+ @@ -1,3 +1,4 @@
2484
+ +#
2485
+ ---
2486
+ description: Canonical routing metadata.
2487
+ ---
2488
+ DIFF
2489
+ cat >"$WORK/frontmatter-shift-delete-wording.patch" <<'DIFF'
2490
+ diff --git a/skills/code-review/SKILL.md b/skills/code-review/SKILL.md
2491
+ index 1111111..2222222 100644
2492
+ --- a/skills/code-review/SKILL.md
2493
+ +++ b/skills/code-review/SKILL.md
2494
+ @@ -1,4 +1,3 @@
2495
+ -#
2496
+ ---
2497
+ description: Canonical routing metadata.
2498
+ ---
2499
+ DIFF
2500
+ cat >"$WORK/no-final-newline-wording.patch" <<'DIFF'
2501
+ diff --git a/skills/code-review/references/no-newline.md b/skills/code-review/references/no-newline.md
2502
+ index 1111111..2222222 100644
2503
+ --- a/skills/code-review/references/no-newline.md
2504
+ +++ b/skills/code-review/references/no-newline.md
2505
+ @@ -1 +1 @@
2506
+ -This teh token has no final newline.
2507
+
2508
+ +This the token has no final newline.
2509
+
2510
+ DIFF
2511
+ cat >"$WORK/invalid-octal-wording.patch" <<'DIFF'
2512
+ diff --git "a/skills/code-review/references/bad\777.md" "b/skills/code-review/references/bad\777.md"
2513
+ index 1111111..2222222 100644
2514
+ --- "a/skills/code-review/references/bad\777.md"
2515
+ +++ "b/skills/code-review/references/bad\777.md"
2516
+ @@ -1 +1,2 @@
2517
+ Plain context.
2518
+ +***
2519
+ DIFF
2520
+ python3 - "$WORK/huge-hunk-number-wording.patch" <<'PY'
2521
+ from pathlib import Path
2522
+ import sys
2523
+
2524
+ Path(sys.argv[1]).write_text(
2525
+ "diff --git a/skills/code-review/references/huge.md b/skills/code-review/references/huge.md\n"
2526
+ "index 1111111..2222222 100644\n"
2527
+ "--- a/skills/code-review/references/huge.md\n"
2528
+ "+++ b/skills/code-review/references/huge.md\n"
2529
+ f"@@ -{'9' * 5001} +1 @@\n"
2530
+ "-*\n"
2531
+ "+!\n",
2532
+ encoding="utf-8",
2533
+ )
2534
+ PY
2535
+ python3 - "$WORK" <<'PY'
2536
+ from pathlib import Path
2537
+ import sys
2538
+
2539
+ root = Path(sys.argv[1])
2540
+ for slug, value in (
2541
+ ("zero-width", "\u200b"),
2542
+ ("bidi-control", "\u202e"),
2543
+ ("emoji-symbol", "\U0001f6a8"),
2544
+ ("currency-symbol", "$$"),
2545
+ ):
2546
+ (root / f"{slug}-wording.patch").write_text(
2547
+ f"diff --git a/skills/code-review/references/{slug}.md b/skills/code-review/references/{slug}.md\n"
2548
+ "index 1111111..2222222 100644\n"
2549
+ f"--- a/skills/code-review/references/{slug}.md\n"
2550
+ f"+++ b/skills/code-review/references/{slug}.md\n"
2551
+ "@@ -1 +1,2 @@\n"
2552
+ " Plain context.\n"
2553
+ f"+{value}\n",
2554
+ encoding="utf-8",
2555
+ )
2556
+ PY
2557
+ python3 - "$WORK" <<'PY'
2558
+ from pathlib import Path
2559
+ import sys
2560
+
2561
+ root = Path(sys.argv[1])
2562
+ for slug, old_line, new_line in (
2563
+ (
2564
+ "decomposed-boundary",
2565
+ "Context cafe\u0301 token.",
2566
+ "Context coffee\u0301 token.",
2567
+ ),
2568
+ (
2569
+ "zwj-boundary",
2570
+ "Context admin\u200distrator token.",
2571
+ "Context root\u200distrator token.",
2572
+ ),
2573
+ ):
2574
+ (root / f"{slug}-wording.patch").write_text(
2575
+ f"diff --git a/skills/code-review/references/{slug}.md b/skills/code-review/references/{slug}.md\n"
2576
+ "index 1111111..2222222 100644\n"
2577
+ f"--- a/skills/code-review/references/{slug}.md\n"
2578
+ f"+++ b/skills/code-review/references/{slug}.md\n"
2579
+ "@@ -1 +1 @@\n"
2580
+ f"-{old_line}\n"
2581
+ f"+{new_line}\n",
2582
+ encoding="utf-8",
2583
+ )
2584
+ PY
2585
+
2586
+ write_wording_proof() { # <packet> <proof> <kind> [old new count]
2587
+ python3 - "$@" <<'PY'
2588
+ import hashlib
2589
+ import json
2590
+ from pathlib import Path
2591
+ import sys
2592
+
2593
+ packet_path, proof_path, kind, *replacement = sys.argv[1:]
2594
+ packet = Path(packet_path).read_bytes()
2595
+ candidate_sha = hashlib.sha256(packet).hexdigest()
2596
+ if kind == "markdown-punctuation-only":
2597
+ check = {"kind": kind}
2598
+ elif kind == "markdown-token-replacement":
2599
+ old, new, count_text = replacement
2600
+ check = {
2601
+ "kind": kind,
2602
+ "old_token": old,
2603
+ "new_token": new,
2604
+ "expected_count": int(count_text),
2605
+ }
2606
+ else:
2607
+ raise AssertionError(kind)
2608
+ proof = {
2609
+ "schema_version": 1,
2610
+ "candidate_sha256": candidate_sha,
2611
+ "check": check,
2612
+ }
2613
+ Path(proof_path).write_text(
2614
+ json.dumps(proof, sort_keys=True, separators=(",", ":")), encoding="utf-8"
2615
+ )
2616
+ PY
2617
+ }
2618
+
2619
+ write_wording_proof "$WORK/wording-punctuation.patch" \
2620
+ "$WORK/wording-punctuation-proof.json" markdown-punctuation-only
2621
+ write_wording_proof "$WORK/repo-only-wording.patch" \
2622
+ "$WORK/repo-only-wording-proof.json" markdown-punctuation-only
2623
+ for fenced_scope in \
2624
+ fenced-backtick \
2625
+ fenced-tilde \
2626
+ fenced-unclosed \
2627
+ fenced-list-bullet \
2628
+ fenced-list-ordered \
2629
+ fenced-list-indented; do
2630
+ write_wording_proof "$WORK/$fenced_scope-wording.patch" \
2631
+ "$WORK/$fenced_scope-wording-proof.json" markdown-punctuation-only
2632
+ done
2633
+ for rejected_punctuation_scope in \
2634
+ indented-space \
2635
+ indented-tab \
2636
+ indented-list \
2637
+ html-pre \
2638
+ html-code \
2639
+ inline-code \
2640
+ link-destination \
2641
+ heading-structure \
2642
+ list-structure \
2643
+ table-structure \
2644
+ ordinary-list-punctuation \
2645
+ numeric-merge \
2646
+ question-mark; do
2647
+ write_wording_proof "$WORK/$rejected_punctuation_scope-wording.patch" \
2648
+ "$WORK/$rejected_punctuation_scope-wording-proof.json" \
2649
+ markdown-punctuation-only
2650
+ done
2651
+ write_wording_proof "$WORK/fenced-token-wording.patch" \
2652
+ "$WORK/fenced-token-wording-proof.json" markdown-token-replacement teh the 1
2653
+ for eligible_scope in \
2654
+ after-closed-fence \
2655
+ after-closed-list-fence; do
2656
+ write_wording_proof "$WORK/$eligible_scope-wording.patch" \
2657
+ "$WORK/$eligible_scope-wording-proof.json" markdown-punctuation-only
2658
+ done
2659
+ write_wording_proof "$WORK/wording-token.patch" \
2660
+ "$WORK/wording-token-proof.json" markdown-token-replacement teh the 1
2661
+ write_wording_proof "$WORK/wording-normative-token.patch" \
2662
+ "$WORK/wording-normative-token-proof.json" markdown-token-replacement must may 1
2663
+ write_wording_proof "$WORK/quoted-unicode-wording.patch" \
2664
+ "$WORK/quoted-unicode-wording-proof.json" markdown-punctuation-only
2665
+ write_wording_proof "$WORK/non-wording-punctuation.patch" \
2666
+ "$WORK/non-wording-proof.json" markdown-punctuation-only
2667
+ write_wording_proof "$WORK/multi-skill-wording.patch" \
2668
+ "$WORK/multi-skill-wording-proof.json" markdown-punctuation-only
2669
+ write_wording_proof "$WORK/truncated-context-wording.patch" \
2670
+ "$WORK/truncated-context-wording-proof.json" markdown-token-replacement teh the 1
2671
+ write_wording_proof "$WORK/symlink-mode-wording.patch" \
2672
+ "$WORK/symlink-mode-wording-proof.json" markdown-token-replacement teh the 1
2673
+ write_wording_proof "$WORK/frontmatter-shift-insert-wording.patch" \
2674
+ "$WORK/frontmatter-shift-insert-wording-proof.json" markdown-punctuation-only
2675
+ write_wording_proof "$WORK/frontmatter-shift-delete-wording.patch" \
2676
+ "$WORK/frontmatter-shift-delete-wording-proof.json" markdown-punctuation-only
2677
+ write_wording_proof "$WORK/no-final-newline-wording.patch" \
2678
+ "$WORK/no-final-newline-wording-proof.json" markdown-token-replacement teh the 1
2679
+ write_wording_proof "$WORK/invalid-octal-wording.patch" \
2680
+ "$WORK/invalid-octal-wording-proof.json" markdown-punctuation-only
2681
+ write_wording_proof "$WORK/huge-hunk-number-wording.patch" \
2682
+ "$WORK/huge-hunk-number-wording-proof.json" markdown-punctuation-only
2683
+ for rejected_character in zero-width bidi-control emoji-symbol currency-symbol; do
2684
+ write_wording_proof "$WORK/$rejected_character-wording.patch" \
2685
+ "$WORK/$rejected_character-wording-proof.json" markdown-punctuation-only
2686
+ done
2687
+ write_wording_proof "$WORK/decomposed-boundary-wording.patch" \
2688
+ "$WORK/decomposed-boundary-wording-proof.json" markdown-token-replacement cafe coffee 1
2689
+ write_wording_proof "$WORK/zwj-boundary-wording.patch" \
2690
+ "$WORK/zwj-boundary-wording-proof.json" markdown-token-replacement admin root 1
2691
+
2692
+ mkdir -p "$WORK/wording-base-repo/skills/code-review/references"
2693
+ git -C "$WORK/wording-base-repo" init -q
2694
+ git -C "$WORK/wording-base-repo" config user.name 'Review Gate Test'
2695
+ git -C "$WORK/wording-base-repo" config user.email 'review-gate@example.invalid'
2696
+ printf '%s\n' '# Code Review' \
2697
+ >"$WORK/wording-base-repo/skills/code-review/SKILL.md"
2698
+ cat >"$WORK/wording-base-repo/skills/code-review/references/long.md" <<'MARKDOWN'
2699
+ ---
2700
+ title: Bounded example
2701
+ ---
2702
+ # Review contract
2703
+
2704
+ Context line one.
2705
+ Context line two.
2706
+ Context line three.
2707
+ This teh token is below frontmatter and ordinary context.
2708
+ Context line four.
2709
+ MARKDOWN
2710
+ git -C "$WORK/wording-base-repo" add skills/code-review
2711
+ git -C "$WORK/wording-base-repo" commit -q -m base
2712
+ python3 - "$WORK/wording-base-repo/skills/code-review/references/long.md" <<'PY'
2713
+ from pathlib import Path
2714
+ import sys
2715
+
2716
+ path = Path(sys.argv[1])
2717
+ path.write_text(path.read_text().replace("This teh token", "This the token"))
2718
+ PY
2719
+ git -C "$WORK/wording-base-repo" diff --no-color --no-ext-diff --no-textconv \
2720
+ --unified=1000000 HEAD >"$WORK/wording-base.patch"
2721
+ write_wording_proof "$WORK/wording-base.patch" \
2722
+ "$WORK/wording-base-proof.json" markdown-token-replacement teh the 1
2723
+
2724
+ reset_case passed unavailable unavailable
2725
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2726
+ --mode review --stage release --challenge-budget 0 \
2727
+ --cwd "$WORK/wording-missing-package-repo" \
2728
+ --diff-file "$WORK/wording-punctuation.patch" \
2729
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2730
+ --wording-only-proof-file "$WORK/wording-punctuation-proof.json")"; rc=$?
2731
+ check "wording proof cannot borrow a missing candidate package from the controller tree" \
2732
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=wording_only_proof_invalid'
2733
+
2734
+ reset_case passed unavailable unavailable
2735
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2736
+ --mode review --stage release --challenge-budget 0 \
2737
+ --cwd "$WORK/wording-repo-only" \
2738
+ --diff-file "$WORK/repo-only-wording.patch" \
2739
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2740
+ --wording-only-proof-file "$WORK/repo-only-wording-proof.json")"; rc=$?
2741
+ check "wording proof accepts a candidate-only package absent from the controller tree" \
2742
+ '[ "$rc" = 0 ] && json_fields "$out" wording_only_scope.status=passed wording_only_scope.changed_files.0=skills/repo-only/references/punctuation.md'
2743
+
2744
+ for unsafe_package in linked hardlinked nonregular oversized; do
2745
+ reset_case passed unavailable unavailable
2746
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2747
+ --mode review --stage release --challenge-budget 0 \
2748
+ --cwd "$WORK/wording-$unsafe_package-package-repo" \
2749
+ --diff-file "$WORK/wording-punctuation.patch" \
2750
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2751
+ --wording-only-proof-file "$WORK/wording-punctuation-proof.json")"; rc=$?
2752
+ check "wording proof rejects a $unsafe_package candidate package entrypoint" \
2753
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=wording_only_proof_invalid'
2754
+ done
2755
+
2756
+ for unsafe_target in missing linked hardlinked nonregular oversized; do
2757
+ reset_case passed unavailable unavailable
2758
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2759
+ --mode review --stage release --challenge-budget 0 \
2760
+ --cwd "$WORK/wording-$unsafe_target-target-repo" \
2761
+ --diff-file "$WORK/wording-punctuation.patch" \
2762
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2763
+ --wording-only-proof-file "$WORK/wording-punctuation-proof.json")"; rc=$?
2764
+ check "wording proof rejects a $unsafe_target changed Markdown target" \
2765
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=wording_only_proof_invalid'
2766
+ done
2767
+
2768
+ for fenced_scope in \
2769
+ fenced-backtick \
2770
+ fenced-tilde \
2771
+ fenced-unclosed \
2772
+ fenced-list-bullet \
2773
+ fenced-list-ordered \
2774
+ fenced-list-indented; do
2775
+ reset_case passed unavailable unavailable
2776
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2777
+ --mode review --stage release --challenge-budget 0 \
2778
+ --cwd "$WORK/wording-reviewed-repo" \
2779
+ --diff-file "$WORK/$fenced_scope-wording.patch" \
2780
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2781
+ --wording-only-proof-file "$WORK/$fenced_scope-wording-proof.json")"; rc=$?
2782
+ check "$fenced_scope cannot use the punctuation-only release waiver" \
2783
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=wording_only_proof_invalid'
2784
+ done
2785
+
2786
+ for rejected_punctuation_scope in \
2787
+ indented-space \
2788
+ indented-tab \
2789
+ indented-list \
2790
+ html-pre \
2791
+ html-code \
2792
+ inline-code \
2793
+ link-destination \
2794
+ heading-structure \
2795
+ list-structure \
2796
+ table-structure \
2797
+ ordinary-list-punctuation \
2798
+ numeric-merge \
2799
+ question-mark; do
2800
+ reset_case passed unavailable unavailable
2801
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2802
+ --mode review --stage release --challenge-budget 0 \
2803
+ --cwd "$WORK/wording-reviewed-repo" \
2804
+ --diff-file "$WORK/$rejected_punctuation_scope-wording.patch" \
2805
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2806
+ --wording-only-proof-file "$WORK/$rejected_punctuation_scope-wording-proof.json")"; rc=$?
2807
+ check "$rejected_punctuation_scope cannot use the punctuation-only release waiver" \
2808
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=wording_only_proof_invalid'
2809
+ done
2810
+
2811
+ reset_case passed unavailable unavailable
2812
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2813
+ --mode review --stage release --challenge-budget 0 \
2814
+ --cwd "$WORK/wording-reviewed-repo" \
2815
+ --diff-file "$WORK/fenced-token-wording.patch" \
2816
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2817
+ --wording-only-proof-file "$WORK/fenced-token-wording-proof.json")"; rc=$?
2818
+ check "token replacement inside a code container cannot use the wording-only scope" \
2819
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=wording_only_proof_invalid'
2820
+
2821
+ # An in-tree .gitattributes `-diff` entry must not collapse a changed file to a
2822
+ # binary marker: the frozen packet has to carry the exact changed lines.
2823
+ mkdir -p "$WORK/attr-diff-repo"
2824
+ git -C "$WORK/attr-diff-repo" init -q
2825
+ git -C "$WORK/attr-diff-repo" config user.name 'Review Gate Test'
2826
+ git -C "$WORK/attr-diff-repo" config user.email 'review-gate@example.invalid'
2827
+ printf 'notes.md -diff\n' >"$WORK/attr-diff-repo/.gitattributes"
2828
+ printf 'stable baseline line\n' >"$WORK/attr-diff-repo/notes.md"
2829
+ git -C "$WORK/attr-diff-repo" add .gitattributes notes.md
2830
+ git -C "$WORK/attr-diff-repo" commit -q -m 'baseline'
2831
+ printf 'stable baseline line\nhidden shared-gate rule change\n' >"$WORK/attr-diff-repo/notes.md"
2832
+ reset_case passed unavailable unavailable
2833
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2834
+ --mode review --stage build --challenge-budget 0 \
2835
+ --cwd "$WORK/attr-diff-repo" --base HEAD \
2836
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json")"; rc=$?
2837
+ check "a -diff attribute cannot hide changed lines from the frozen packet" \
2838
+ '[ "$rc" = 0 ] && grep -q "hidden shared-gate rule change" "$WORK/state/claude_packet" && ! grep -q "Binary files" "$WORK/state/claude_packet"'
2839
+
2840
+
2841
+ for eligible_scope in \
2842
+ after-closed-fence \
2843
+ after-closed-list-fence; do
2844
+ reset_case passed unavailable unavailable
2845
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2846
+ --mode review --stage release --challenge-budget 0 \
2847
+ --cwd "$WORK/wording-reviewed-repo" \
2848
+ --diff-file "$WORK/$eligible_scope-wording.patch" \
2849
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2850
+ --wording-only-proof-file "$WORK/$eligible_scope-wording-proof.json")"; rc=$?
2851
+ check "$eligible_scope remains eligible for punctuation-only proof" \
2852
+ '[ "$rc" = 0 ] && json_fields "$out" wording_only_scope.status=passed wording_only_scope.check_kind=markdown-punctuation-only'
2853
+ done
2854
+
2855
+ reset_case passed unavailable unavailable
2856
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2857
+ --mode review --stage release --challenge-budget 0 \
2858
+ --cwd "$WORK/repo" --diff-file "$WORK/wording-punctuation.patch" \
2859
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2860
+ --wording-only-proof-file "$WORK/wording-punctuation-proof.json")"; rc=$?
2861
+ punctuation_proof_hash="$(shasum -a 256 "$WORK/wording-punctuation-proof.json" | awk '{print $1}')"
2862
+ check "release wording-only punctuation scope can take one proof-bound review" \
2863
+ '[ "$rc" = 0 ] && json_fields "$out" challenge_budget=0 review_chain_tracked=false wording_only_scope.status=passed wording_only_scope.check_kind=markdown-punctuation-only wording_only_proof_sha256="$punctuation_proof_hash" reviewed_concerns.7=wording_only_boundary'
2864
+
2865
+ reset_case passed unavailable unavailable
2866
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2867
+ --mode review --stage build --challenge-budget 0 \
2868
+ --cwd "$WORK/repo" --diff-file "$WORK/wording-punctuation.patch" \
2869
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2870
+ --wording-only-proof-file "$WORK/wording-punctuation-proof.json")"; rc=$?
2871
+ check "build wording-only review records the same controller-bound proof" \
2872
+ '[ "$rc" = 0 ] && json_fields "$out" review_depth=build challenge_budget=0 wording_only_scope.check_kind=markdown-punctuation-only reviewed_concerns.5=wording_only_boundary'
2873
+ printf '%s\n' "$out" >"$WORK/wording-punctuation-review.json"
2874
+ reset_case passed unavailable unavailable
2875
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2876
+ --mode complete --stage build --challenge-budget 0 \
2877
+ --cwd "$WORK/repo" --diff-file "$WORK/wording-punctuation.patch" \
2878
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2879
+ --completion-review-result-file "$WORK/wording-punctuation-review.json")"; rc=$?
2880
+ check "a wording-only single review cannot become a complete checkpoint" \
2881
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=completion_checkpoint_invalid'
2882
+
2883
+ python3 - "$WORK/wording-punctuation-review.json" "$WORK/wording-punctuation-review-without-proof-keys.json" <<'PY'
2884
+ import json
2885
+ import sys
2886
+ from pathlib import Path
2887
+
2888
+ source, target = map(Path, sys.argv[1:])
2889
+ payload = json.loads(source.read_text(encoding="utf-8"))
2890
+ payload.pop("wording_only_proof_sha256")
2891
+ payload.pop("wording_only_scope")
2892
+ target.write_text(json.dumps(payload), encoding="utf-8")
2893
+ PY
2894
+ reset_case passed unavailable unavailable
2895
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2896
+ --mode complete --stage build --challenge-budget 0 \
2897
+ --cwd "$WORK/repo" --diff-file "$WORK/wording-punctuation.patch" \
2898
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2899
+ --completion-review-result-file "$WORK/wording-punctuation-review-without-proof-keys.json")"; rc=$?
2900
+ check "a receipt cannot hide wording-only proof by dropping controller-owned keys" \
2901
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=completion_checkpoint_invalid'
2902
+
2903
+ python3 - "$WORK/wording-punctuation-review.json" "$WORK/wording-punctuation-review-with-null-proof.json" <<'PY'
2904
+ import json
2905
+ import sys
2906
+ from pathlib import Path
2907
+
2908
+ source, target = map(Path, sys.argv[1:])
2909
+ payload = json.loads(source.read_text(encoding="utf-8"))
2910
+ payload["wording_only_proof_sha256"] = None
2911
+ payload["wording_only_scope"] = None
2912
+ target.write_text(json.dumps(payload), encoding="utf-8")
2913
+ PY
2914
+ reset_case passed unavailable unavailable
2915
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2916
+ --mode complete --stage build --challenge-budget 0 \
2917
+ --cwd "$WORK/repo" --diff-file "$WORK/wording-punctuation.patch" \
2918
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2919
+ --completion-review-result-file "$WORK/wording-punctuation-review-with-null-proof.json")"; rc=$?
2920
+ check "a receipt cannot hide wording-only proof by replacing it with null" \
2921
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=completion_checkpoint_invalid'
2922
+
2923
+ reset_case passed unavailable unavailable
2924
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2925
+ --mode review --stage build --challenge-budget 0 \
2926
+ --cwd "$WORK/repo" --diff-file "$WORK/quoted-unicode-wording.patch" \
2927
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2928
+ --wording-only-proof-file "$WORK/quoted-unicode-wording-proof.json")"; rc=$?
2929
+ check "wording proof binds Git-quoted literal Unicode and space paths" \
2930
+ '[ "$rc" = 0 ] && json_fields "$out" wording_only_scope.changed_files.0="skills/code-review/references/café file.md"'
2931
+
2932
+ reset_case passed unavailable unavailable
2933
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2934
+ --mode review --stage build --challenge-budget 1 \
2935
+ --cwd "$WORK/repo" --diff-file "$WORK/wording-punctuation.patch" \
2936
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2937
+ --wording-only-proof-file "$WORK/wording-punctuation-proof.json")"; rc=$?
2938
+ check "wording-only proof cannot open positive autonomous review capacity" \
2939
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=wording_only_proof_invalid'
2940
+
2941
+ reset_case passed unavailable unavailable
2942
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2943
+ --mode review --stage build --challenge-budget 0 \
2944
+ --cwd "$WORK/repo" --diff-file "$WORK/wording-token.patch" \
2945
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2946
+ --wording-only-proof-file "$WORK/wording-token-proof.json")"; rc=$?
2947
+ check "build exact typo replacement can take one proof-bound review" \
2948
+ '[ "$rc" = 0 ] && json_fields "$out" review_depth=build challenge_budget=0 wording_only_scope.check_kind=markdown-token-replacement wording_only_scope.old_token=teh wording_only_scope.new_token=the wording_only_scope.expected_count=1 wording_only_scope.replacement_count=1 reviewed_concerns.5=wording_only_boundary'
2949
+
2950
+ reset_case passed unavailable unavailable
2951
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2952
+ --mode review --stage release --challenge-budget 0 \
2953
+ --cwd "$WORK/repo" --diff-file "$WORK/wording-normative-token.patch" \
2954
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2955
+ --wording-only-proof-file "$WORK/wording-normative-token-proof.json")"; rc=$?
2956
+ check "release token replacement cannot waive its required challenge" \
2957
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=wording_only_proof_invalid'
2958
+
2959
+ reset_case passed unavailable unavailable
2960
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2961
+ --mode review --stage explore --risk-tag shared-gate --challenge-budget 0 \
2962
+ --cwd "$WORK/repo" --diff-file "$WORK/wording-normative-token.patch" \
2963
+ --implementer-family openai --review-plan-file "$WORK/high-risk-plan.json" \
2964
+ --wording-only-proof-file "$WORK/wording-normative-token-proof.json")"; rc=$?
2965
+ check "high-risk token replacement cannot waive its required challenge" \
2966
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=wording_only_proof_invalid'
2967
+
2968
+ reset_case passed unavailable unavailable
2969
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2970
+ --mode review --stage build --challenge-budget 0 \
2971
+ --cwd "$WORK/wording-base-repo" --base HEAD \
2972
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2973
+ --wording-only-proof-file "$WORK/wording-base-proof.json")"; rc=$?
2974
+ check "base-mode build wording proof freezes full context from line one" \
2975
+ '[ "$rc" = 0 ] && json_fields "$out" review_depth=build wording_only_scope.check_kind=markdown-token-replacement wording_only_scope.replacement_count=1 reviewed_concerns.5=wording_only_boundary'
2976
+
2977
+ for rejected_scope in multi-skill-wording truncated-context-wording symlink-mode-wording frontmatter-shift-insert-wording frontmatter-shift-delete-wording no-final-newline-wording invalid-octal-wording huge-hunk-number-wording zero-width-wording bidi-control-wording emoji-symbol-wording currency-symbol-wording decomposed-boundary-wording zwj-boundary-wording; do
2978
+ reset_case passed unavailable unavailable
2979
+ packet="$WORK/$rejected_scope.patch"
2980
+ proof="$WORK/$rejected_scope-proof.json"
2981
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2982
+ --mode review --stage release --challenge-budget 0 \
2983
+ --cwd "$WORK/repo" --diff-file "$packet" \
2984
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2985
+ --wording-only-proof-file "$proof")"; rc=$?
2986
+ check "$rejected_scope cannot waive the release challenge" \
2987
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=wording_only_proof_invalid'
2988
+ done
2989
+
2990
+ reset_case missing_wording_boundary unavailable unavailable
2991
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
2992
+ --mode review --stage release --challenge-budget 0 \
2993
+ --cwd "$WORK/repo" --diff-file "$WORK/wording-punctuation.patch" \
2994
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
2995
+ --wording-only-proof-file "$WORK/wording-punctuation-proof.json")"; rc=$?
2996
+ check "wording-only proof still requires independent semantic-boundary review" \
2997
+ '[ "$rc" = 2 ] && json_fields "$out" status=inconclusive reason_code=invalid_model_output'
2998
+
2999
+ reset_case passed unavailable unavailable
3000
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
3001
+ --mode review --stage release --challenge-budget 0 \
3002
+ --cwd "$WORK/repo" --diff-file "$WORK/wording-punctuation.patch" \
3003
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
3004
+ --review-chain-id wording-chain --autonomous-review-index 1 \
3005
+ --wording-only-proof-file "$WORK/wording-punctuation-proof.json")"; rc=$?
3006
+ check "wording-only single review cannot open a tracked review chain" \
3007
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=wording_only_proof_invalid'
3008
+
3009
+ python3 - "$WORK/wording-punctuation-proof.json" \
3010
+ "$WORK/stale-wording-proof.json" "$WORK/extra-result-wording-proof.json" \
3011
+ "$WORK/noninteger-schema-wording-proof.json" \
3012
+ "$WORK/extra-check-wording-proof.json" \
3013
+ "$WORK/duplicate-top-wording-proof.json" \
3014
+ "$WORK/duplicate-check-wording-proof.json" <<'PY'
3015
+ import json
3016
+ from pathlib import Path
3017
+ import sys
3018
+
3019
+ source = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
3020
+ stale = json.loads(json.dumps(source))
3021
+ stale["candidate_sha256"] = "0" * 64
3022
+ Path(sys.argv[2]).write_text(json.dumps(stale), encoding="utf-8")
3023
+ extra_result = json.loads(json.dumps(source))
3024
+ extra_result["scope_result"] = {"status": "passed"}
3025
+ Path(sys.argv[3]).write_text(json.dumps(extra_result), encoding="utf-8")
3026
+ noninteger = json.loads(json.dumps(source))
3027
+ noninteger["schema_version"] = 1.0
3028
+ Path(sys.argv[4]).write_text(json.dumps(noninteger), encoding="utf-8")
3029
+ extra_check = json.loads(json.dumps(source))
3030
+ extra_check["check"]["result"] = "passed"
3031
+ Path(sys.argv[5]).write_text(json.dumps(extra_check), encoding="utf-8")
3032
+ source_text = json.dumps(source, sort_keys=True, separators=(",", ":"))
3033
+ candidate_field = (
3034
+ '"candidate_sha256":' + json.dumps(source["candidate_sha256"])
3035
+ )
3036
+ Path(sys.argv[6]).write_text(
3037
+ source_text.replace(candidate_field, candidate_field + "," + candidate_field, 1),
3038
+ encoding="utf-8",
3039
+ )
3040
+ kind_field = '"kind":"markdown-punctuation-only"'
3041
+ Path(sys.argv[7]).write_text(
3042
+ source_text.replace(kind_field, kind_field + "," + kind_field, 1),
3043
+ encoding="utf-8",
3044
+ )
3045
+ PY
3046
+
3047
+ for invalid_proof in stale-wording-proof extra-result-wording-proof noninteger-schema-wording-proof extra-check-wording-proof duplicate-top-wording-proof duplicate-check-wording-proof non-wording-proof; do
3048
+ reset_case passed unavailable unavailable
3049
+ packet="$WORK/wording-punctuation.patch"
3050
+ [ "$invalid_proof" != non-wording-proof ] || packet="$WORK/non-wording-punctuation.patch"
3051
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
3052
+ --mode review --stage release --challenge-budget 0 \
3053
+ --cwd "$WORK/repo" --diff-file "$packet" \
3054
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
3055
+ --wording-only-proof-file "$WORK/$invalid_proof.json")"; rc=$?
3056
+ check "$invalid_proof cannot waive release challenge" \
3057
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=wording_only_proof_invalid'
3058
+ done
3059
+
3060
+ python3 - "$WORK/wording-token-proof.json" "$WORK/surrogate-token-wording-proof.json" <<'PY'
3061
+ import json
3062
+ from pathlib import Path
3063
+ import sys
3064
+
3065
+ proof = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
3066
+ proof["check"]["old_token"] = "\ud800"
3067
+ Path(sys.argv[2]).write_text(json.dumps(proof), encoding="utf-8")
3068
+ PY
3069
+ reset_case passed unavailable unavailable
3070
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
3071
+ --mode review --stage release --challenge-budget 0 \
3072
+ --cwd "$WORK/repo" --diff-file "$WORK/wording-token.patch" \
3073
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
3074
+ --wording-only-proof-file "$WORK/surrogate-token-wording-proof.json" 2>/dev/null)"; rc=$?
3075
+ check "a non-UTF-8 token proof fails as a structured gate result" \
3076
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=wording_only_proof_invalid'
3077
+
3078
+ ln -s "$WORK/wording-punctuation-proof.json" "$WORK/linked-wording-proof.json"
3079
+ reset_case passed unavailable unavailable
3080
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
3081
+ --mode review --stage release --challenge-budget 0 \
3082
+ --cwd "$WORK/repo" --diff-file "$WORK/wording-punctuation.patch" \
3083
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
3084
+ --wording-only-proof-file "$WORK/linked-wording-proof.json")"; rc=$?
3085
+ check "a linked wording-only proof cannot waive release challenge" \
3086
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=wording_only_proof_invalid'
3087
+ unlink "$WORK/linked-wording-proof.json"
3088
+
3089
+ python3 - "$WORK/oversized-wording-proof.json" <<'PY'
3090
+ import os
3091
+ from pathlib import Path
3092
+ import sys
3093
+
3094
+ path = Path(sys.argv[1])
3095
+ path.touch()
3096
+ os.truncate(path, 16_001)
3097
+ PY
3098
+ reset_case passed unavailable unavailable
3099
+ out="$(REVIEW_GATE_TEST_STATE="$WORK/state" "$WORK/harness/scripts/review_gate.sh" \
3100
+ --mode review --stage release --challenge-budget 0 \
3101
+ --cwd "$WORK/repo" --diff-file "$WORK/wording-punctuation.patch" \
3102
+ --implementer-family openai --review-plan-file "$WORK/review-plan.json" \
3103
+ --wording-only-proof-file "$WORK/oversized-wording-proof.json")"; rc=$?
3104
+ check "an oversized wording-only proof cannot waive release challenge" \
3105
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=wording_only_proof_invalid'
3106
+
1668
3107
  reset_case passed unavailable unavailable
1669
3108
  out="$(run_gate --stage release --challenge-budget 0)"; rc=$?
1670
- check "release review cannot explicitly waive its first challenge" \
3109
+ check "release review without wording-only proof cannot waive its first challenge" \
1671
3110
  '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=invalid_input next_action=stop_reviewer_lane'
1672
3111
 
1673
3112
  reset_case passed unavailable unavailable
1674
3113
  out="$(run_gate --stage explore --risk-tag shared-gate --review-plan-file "$WORK/high-risk-plan.json" --challenge-budget 0)"; rc=$?
1675
- check "high-risk depth cannot explicitly waive its first challenge" \
3114
+ check "high-risk depth without wording-only proof cannot waive its first challenge" \
1676
3115
  '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=invalid_input next_action=stop_reviewer_lane'
1677
3116
 
1678
3117
  reset_case findings unavailable unavailable
@@ -1926,6 +3365,16 @@ out="$(run_gate --timeout 90)"; rc=$?
1926
3365
  check "an explicit budget still overrides the default" \
1927
3366
  '[ "$rc" = 0 ] && [ "$(cat "$WORK/state/claude_timeout")" = 90 ]'
1928
3367
 
3368
+ reset_case passed unavailable unavailable
3369
+ out="$(run_gate --timeout 1200 --total-timeout 3600)"; rc=$?
3370
+ check "the generic timeout accepts and forwards the documented 1200-second ceiling" \
3371
+ '[ "$rc" = 0 ] && [ "$(cat "$WORK/state/claude_timeout")" = 1200 ] && json_fields "$out" status=passed selected_client=claude'
3372
+
3373
+ reset_case passed unavailable unavailable
3374
+ out="$(run_gate --timeout 1201 --total-timeout 3600)"; rc=$?
3375
+ check "the generic timeout rejects a value above the documented ceiling before client execution" \
3376
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=invalid_input'
3377
+
1929
3378
  # The gate always passes --timeout, which overrides each wrapper's own default,
1930
3379
  # so the assertions above cannot see the direct-invocation defaults at all. Pin
1931
3380
  # each declared default separately or three of the four silently regress.
@@ -1948,6 +3397,41 @@ done
1948
3397
  check "every wrapper declares the same direct-invocation budget as the gate default" \
1949
3398
  '[ "$wrapper_defaults_ok" = 1 ] && [ "$wrapper_defaults_seen" = 4 ]'
1950
3399
 
3400
+ # Direct wrapper invocations share one decimal-string normalizer. Exercise the
3401
+ # boundary itself rather than grepping arithmetic fragments: shell integer
3402
+ # overflow used to make a 50-digit timeout bypass Claude's clamp and fail the
3403
+ # other three clients as invalid input.
3404
+ # shellcheck source=normalize_review_timeout.sh
3405
+ . "$DIR/normalize_review_timeout.sh"
3406
+ wrapper_timeout_inputs_match=1
3407
+ while IFS='|' read -r input expected; do
3408
+ actual="$(normalize_review_timeout "$input")" || actual="invalid"
3409
+ if [ "$actual" != "$expected" ]; then
3410
+ wrapper_timeout_inputs_match=0
3411
+ printf 'wrapper timeout normalization drift: %s -> %s, want %s\n' \
3412
+ "$input" "$actual" "$expected" >&2
3413
+ fi
3414
+ done <<'EOF'
3415
+ 4|invalid
3416
+ 5|5
3417
+ 1200|1200
3418
+ 1201|1200
3419
+ 99999999999999999999999999999999999999999999999999|1200
3420
+ EOF
3421
+ wrapper_timeout_bindings_match=1
3422
+ wrapper_timeout_bindings_seen=0
3423
+ for wrapper_file in claude_review.sh codex_review.sh kimi_review.sh opencode_review.sh; do
3424
+ if grep -qF 'normalize_review_timeout "$' "$DIR/$wrapper_file"; then
3425
+ wrapper_timeout_bindings_seen=$((wrapper_timeout_bindings_seen + 1))
3426
+ else
3427
+ wrapper_timeout_bindings_match=0
3428
+ printf 'wrapper timeout binding drift: %s does not call the shared normalizer\n' \
3429
+ "$wrapper_file" >&2
3430
+ fi
3431
+ done
3432
+ check "every wrapper accepts and clamps the full decimal-string timeout domain" \
3433
+ '[ "$wrapper_timeout_inputs_match" = 1 ] && [ "$wrapper_timeout_bindings_match" = 1 ] && [ "$wrapper_timeout_bindings_seen" = 4 ]'
3434
+
1951
3435
  parser_total_timeout="$(python3 - "$DIR/review_gate.py" "$WORK" <<'PY'
1952
3436
  import importlib.util
1953
3437
  from pathlib import Path
@@ -2612,6 +4096,7 @@ out="$(run_gate --allow-fallback-egress)"; rc=$?
2612
4096
  check "reviewer cannot self-report controller-owned stage or coverage fields" \
2613
4097
  '[ "$rc" = 0 ] && json_fields "$out" stage=build review_depth=build owner_selection_source=implementer-declared selected_skills.0=code-review self_review_gate.required=true self_review_gate.required_triggers.0=before_completion_claim && [ "$(printf "%s" "$out" | python3 -c "import json,sys; print(len(json.load(sys.stdin).get(\"reviewed_skills\", [])))")" = 0 ] && ! grep -q spoofed <<<"$out"'
2614
4098
 
4099
+ rm -rf "$WORK/repo/skills"
2615
4100
  (
2616
4101
  cd "$WORK/repo"
2617
4102
  git init -q
@@ -2628,6 +4113,376 @@ out="$(run_base_gate --allow-fallback-egress)"; rc=$?
2628
4113
  check "base packet freezes tracked and untracked changes once" \
2629
4114
  '[ "$rc" = 0 ] && grep -q tracked.txt "$WORK/state/claude_packet" && grep -q untracked.txt "$WORK/state/claude_packet" && grep -q "Untracked files" "$WORK/state/claude_packet"'
2630
4115
 
4116
+ # The explicit cwd is the only repository identity. Ambient Git variables must
4117
+ # not redirect discovery, objects, refs, index, or worktree to a clean decoy.
4118
+ git clone -q "$WORK/repo" "$WORK/git-env-decoy"
4119
+ git -C "$WORK/git-env-decoy" config core.worktree "$WORK/git-env-decoy"
4120
+ : >"$WORK/empty-grafts"
4121
+ : >"$WORK/empty-shallow"
4122
+
4123
+ reset_case passed unavailable unavailable
4124
+ out="$(GIT_DIR="$WORK/git-env-decoy/.git" run_base_gate --allow-fallback-egress)"; rc=$?
4125
+ check "ambient GIT_DIR cannot redirect the base-mode packet to a clean decoy" \
4126
+ '[ "$rc" = 0 ] && grep -q tracked.txt "$WORK/state/claude_packet" && grep -q -- "+after" "$WORK/state/claude_packet"'
4127
+
4128
+ reset_case passed unavailable unavailable
4129
+ out="$( \
4130
+ GIT_DIR="$WORK/git-env-decoy/.git" \
4131
+ GIT_WORK_TREE="$WORK/git-env-decoy" \
4132
+ GIT_IMPLICIT_WORK_TREE=1 \
4133
+ GIT_INDEX_FILE="$WORK/git-env-decoy/.git/index" \
4134
+ GIT_COMMON_DIR="$WORK/git-env-decoy/.git" \
4135
+ GIT_NAMESPACE=synthetic-clean \
4136
+ GIT_OBJECT_DIRECTORY="$WORK/git-env-decoy/.git/objects" \
4137
+ GIT_ALTERNATE_OBJECT_DIRECTORIES="$WORK/git-env-decoy/.git/objects" \
4138
+ GIT_CEILING_DIRECTORIES="$WORK/git-env-decoy" \
4139
+ GIT_DISCOVERY_ACROSS_FILESYSTEM=0 \
4140
+ GIT_GRAFT_FILE="$WORK/empty-grafts" \
4141
+ GIT_SHALLOW_FILE="$WORK/empty-shallow" \
4142
+ GIT_REPLACE_REF_BASE=refs/synthetic-replace \
4143
+ GIT_PREFIX=synthetic-prefix \
4144
+ GIT_QUARANTINE_PATH="$WORK/git-env-decoy/.git/objects" \
4145
+ run_base_gate --allow-fallback-egress
4146
+ )"; rc=$?
4147
+ check "ambient Git routing matrix cannot replace the requested repository" \
4148
+ '[ "$rc" = 0 ] && grep -q tracked.txt "$WORK/state/claude_packet" && grep -q -- "+after" "$WORK/state/claude_packet" && ! grep -q "$WORK/git-env-decoy" "$WORK/state/claude_packet"'
4149
+
4150
+ git_config_env_probe="$(
4151
+ GIT_CONFIG="$WORK/attacker.cfg" \
4152
+ GIT_CONFIG_GLOBAL="$WORK/attacker-global.cfg" \
4153
+ GIT_CONFIG_SYSTEM="$WORK/attacker-system.cfg" \
4154
+ GIT_CONFIG_NOSYSTEM=1 \
4155
+ GIT_CONFIG_COUNT=2 \
4156
+ GIT_CONFIG_PARAMETERS="'core.fsmonitor=$WORK/fake-fsmonitor.sh'" \
4157
+ GIT_CONFIG_KEY_0=core.worktree \
4158
+ GIT_CONFIG_VALUE_0="$WORK/git-env-decoy" \
4159
+ GIT_CONFIG_KEY_custom=include.path \
4160
+ GIT_CONFIG_VALUE_custom="$WORK/attacker-include.cfg" \
4161
+ PYTHONPATH="$WORK/harness/scripts" python3 - <<'PY' 2>&1
4162
+ import os
4163
+
4164
+ import review_gate
4165
+
4166
+ environment = review_gate.git_environment()
4167
+ for key in (
4168
+ "GIT_CONFIG",
4169
+ "GIT_CONFIG_COUNT",
4170
+ "GIT_CONFIG_PARAMETERS",
4171
+ "GIT_CONFIG_KEY_0",
4172
+ "GIT_CONFIG_VALUE_0",
4173
+ "GIT_CONFIG_KEY_custom",
4174
+ "GIT_CONFIG_VALUE_custom",
4175
+ ):
4176
+ assert key not in environment, (key, environment[key])
4177
+ assert environment["GIT_CONFIG_GLOBAL"] == os.devnull
4178
+ assert environment["GIT_CONFIG_SYSTEM"] == os.devnull
4179
+ assert environment["GIT_CONFIG_NOSYSTEM"] == "1"
4180
+ print("git_config_environment_clean")
4181
+ PY
4182
+ )"; git_config_env_rc=$?
4183
+ check "base-mode Git environment removes fixed and indexed config injection variables" \
4184
+ '[ "$git_config_env_rc" = 0 ] && [ "$git_config_env_probe" = git_config_environment_clean ]'
4185
+
4186
+ cat >"$WORK/fake-fsmonitor.sh" <<'SH'
4187
+ #!/bin/sh
4188
+ : >"$REVIEW_FSMONITOR_MARKER"
4189
+ printf '\n'
4190
+ SH
4191
+ chmod +x "$WORK/fake-fsmonitor.sh"
4192
+ printf '[core]\n\tworktree = %s\n\tfsmonitor = %s\n' \
4193
+ "$WORK/git-env-decoy" "$WORK/fake-fsmonitor.sh" \
4194
+ >"$WORK/attacker-include.cfg"
4195
+ printf '[include]\n\tpath = %s\n' "$WORK/attacker-include.cfg" \
4196
+ >"$WORK/attacker-root.cfg"
4197
+
4198
+ for config_env in GIT_CONFIG_GLOBAL GIT_CONFIG_SYSTEM; do
4199
+ rm -f "$WORK/fsmonitor-marker"
4200
+ reset_case passed unavailable unavailable
4201
+ out="$(
4202
+ export "$config_env=$WORK/attacker-root.cfg"
4203
+ REVIEW_FSMONITOR_MARKER="$WORK/fsmonitor-marker" \
4204
+ run_base_gate --allow-fallback-egress
4205
+ )"; rc=$?
4206
+ check "ambient $config_env include cannot route the worktree or execute fsmonitor" \
4207
+ '[ "$rc" = 0 ] && [ ! -e "$WORK/fsmonitor-marker" ] && grep -q -- "+after" "$WORK/state/claude_packet"'
4208
+ done
4209
+
4210
+ for config_route in indexed parameters; do
4211
+ rm -f "$WORK/fsmonitor-marker"
4212
+ reset_case passed unavailable unavailable
4213
+ if [ "$config_route" = indexed ]; then
4214
+ out="$( \
4215
+ REVIEW_FSMONITOR_MARKER="$WORK/fsmonitor-marker" \
4216
+ GIT_CONFIG_COUNT=1 \
4217
+ GIT_CONFIG_KEY_0=include.path \
4218
+ GIT_CONFIG_VALUE_0="$WORK/attacker-include.cfg" \
4219
+ run_base_gate --allow-fallback-egress
4220
+ )"; rc=$?
4221
+ else
4222
+ out="$( \
4223
+ REVIEW_FSMONITOR_MARKER="$WORK/fsmonitor-marker" \
4224
+ GIT_CONFIG_PARAMETERS="'include.path=$WORK/attacker-include.cfg'" \
4225
+ run_base_gate --allow-fallback-egress
4226
+ )"; rc=$?
4227
+ fi
4228
+ check "ambient $config_route config cannot load executable Git configuration" \
4229
+ '[ "$rc" = 0 ] && [ ! -e "$WORK/fsmonitor-marker" ] && grep -q -- "+after" "$WORK/state/claude_packet"'
4230
+ done
4231
+
4232
+ git -C "$WORK/repo" config include.path "$WORK/attacker-include.cfg"
4233
+ rm -f "$WORK/fsmonitor-marker"
4234
+ reset_case passed unavailable unavailable
4235
+ out="$(REVIEW_FSMONITOR_MARKER="$WORK/fsmonitor-marker" \
4236
+ run_base_gate --allow-fallback-egress)"; rc=$?
4237
+ check "repository-local include cannot execute fsmonitor during packet construction" \
4238
+ '[ "$rc" = 0 ] && [ ! -e "$WORK/fsmonitor-marker" ] && grep -q -- "+after" "$WORK/state/claude_packet"'
4239
+ git -C "$WORK/repo" config --unset-all include.path
4240
+
4241
+ # Neither an ambient external-diff helper nor a repository-configured textconv
4242
+ # may execute or replace the bytes in the tracked packet.
4243
+ cat >"$WORK/fake-external-diff.sh" <<'SH'
4244
+ #!/bin/sh
4245
+ : >"$REVIEW_EXTERNAL_DIFF_MARKER"
4246
+ exit 0
4247
+ SH
4248
+ chmod +x "$WORK/fake-external-diff.sh"
4249
+ rm -f "$WORK/external-diff-marker"
4250
+ reset_case passed unavailable unavailable
4251
+ out="$( \
4252
+ REVIEW_EXTERNAL_DIFF_MARKER="$WORK/external-diff-marker" \
4253
+ GIT_EXTERNAL_DIFF="$WORK/fake-external-diff.sh" \
4254
+ GIT_DIFF_OPTS=--unified=0 \
4255
+ run_base_gate --allow-fallback-egress
4256
+ )"; rc=$?
4257
+ check "tracked packet disables ambient external diff execution and spoofing" \
4258
+ '[ "$rc" = 0 ] && [ ! -e "$WORK/external-diff-marker" ] && grep -q -- "-before" "$WORK/state/claude_packet" && grep -q -- "+after" "$WORK/state/claude_packet"'
4259
+
4260
+ printf '%s\n' 'tracked.txt diff=review-spoof' >"$WORK/repo/.gitattributes"
4261
+ git -C "$WORK/repo" add .gitattributes
4262
+ git -C "$WORK/repo" commit -q -m 'add synthetic diff driver fixture' -- .gitattributes
4263
+ cat >"$WORK/fake-textconv.sh" <<'SH'
4264
+ #!/bin/sh
4265
+ : >"$REVIEW_TEXTCONV_MARKER"
4266
+ printf '%s\n' normalized
4267
+ SH
4268
+ chmod +x "$WORK/fake-textconv.sh"
4269
+ git -C "$WORK/repo" config diff.review-spoof.textconv "$WORK/fake-textconv.sh"
4270
+ rm -f "$WORK/textconv-marker"
4271
+ reset_case passed unavailable unavailable
4272
+ out="$(REVIEW_TEXTCONV_MARKER="$WORK/textconv-marker" run_base_gate --allow-fallback-egress)"; rc=$?
4273
+ check "tracked packet disables configured textconv execution and clean spoofing" \
4274
+ '[ "$rc" = 0 ] && [ ! -e "$WORK/textconv-marker" ] && grep -q -- "-before" "$WORK/state/claude_packet" && grep -q -- "+after" "$WORK/state/claude_packet"'
4275
+ git -C "$WORK/repo" config --unset diff.review-spoof.textconv
4276
+
4277
+ ln -s tracked.txt "$WORK/repo/untracked-link.txt"
4278
+ reset_case passed unavailable unavailable
4279
+ out="$(run_base_gate --allow-fallback-egress)"; rc=$?
4280
+ check "base mode rejects an untracked symlink instead of reviewing a non-exact placeholder" \
4281
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=invalid_input'
4282
+ unlink "$WORK/repo/untracked-link.txt"
4283
+
4284
+ ln "$WORK/repo/tracked.txt" "$WORK/repo/untracked-hardlink.txt"
4285
+ reset_case passed unavailable unavailable
4286
+ out="$(run_base_gate --allow-fallback-egress)"; rc=$?
4287
+ check "base mode rejects an untracked hardlink instead of omitting it" \
4288
+ '[ "$rc" = 2 ] && [ ! -e "$WORK/state/client_sequence" ] && json_fields "$out" reason_code=invalid_input'
4289
+ unlink "$WORK/repo/untracked-hardlink.txt"
4290
+
4291
+ : >"$WORK/repo/empty-untracked.txt"
4292
+ reset_case passed unavailable unavailable
4293
+ out="$(run_base_gate --allow-fallback-egress)"; rc=$?
4294
+ check "base mode binds an empty untracked file without a skipped placeholder" \
4295
+ '[ "$rc" = 0 ] && grep -q empty-untracked.txt "$WORK/state/claude_packet" && ! grep -qE "skipped|not shown as text diff" "$WORK/state/claude_packet"'
4296
+ unlink "$WORK/repo/empty-untracked.txt"
4297
+
4298
+ untracked_exact_probe="$(PYTHONPATH="$WORK/harness/scripts" python3 - "$WORK" <<'PY' 2>&1
4299
+ import os
4300
+ import time
4301
+ from pathlib import Path
4302
+ import sys
4303
+
4304
+ import review_gate
4305
+
4306
+
4307
+ root = Path(sys.argv[1]) / "untracked-exact-probe"
4308
+ repo = root / "repo"
4309
+ outside = root / "outside"
4310
+ repo.mkdir(parents=True)
4311
+ outside.mkdir()
4312
+
4313
+
4314
+ def expect_rejected(fake_git_output, label):
4315
+ original = review_gate.git_output
4316
+ review_gate.git_output = fake_git_output
4317
+ try:
4318
+ try:
4319
+ review_gate.untracked_packet(repo, [], time.monotonic() + 5)
4320
+ except review_gate.GateError as exc:
4321
+ assert exc.reason_code == "invalid_input", (label, exc.reason_code)
4322
+ return exc
4323
+ raise AssertionError(f"{label} produced a placeholder instead of failing closed")
4324
+ finally:
4325
+ review_gate.git_output = original
4326
+
4327
+
4328
+ gone = repo / "gone.txt"
4329
+ gone.write_text("gone\n", encoding="utf-8")
4330
+
4331
+
4332
+ def disappear_after_listing(repo_path, args, *unused, **kwargs):
4333
+ assert args[0] == "ls-files", args
4334
+ gone.unlink()
4335
+ return b"gone.txt\0"
4336
+
4337
+
4338
+ expect_rejected(disappear_after_listing, "disappeared untracked path")
4339
+
4340
+ (outside / "escaped.txt").write_text("outside\n", encoding="utf-8")
4341
+ (repo / "linked-parent").symlink_to(outside, target_is_directory=True)
4342
+
4343
+
4344
+ def parent_symlink_listing(repo_path, args, *unused, **kwargs):
4345
+ assert args[0] == "ls-files", args
4346
+ return b"linked-parent/escaped.txt\0"
4347
+
4348
+
4349
+ expect_rejected(parent_symlink_listing, "symlinked untracked parent")
4350
+
4351
+ fifo = repo / "fifo"
4352
+ os.mkfifo(fifo)
4353
+
4354
+
4355
+ def fifo_listing(repo_path, args, *unused, **kwargs):
4356
+ assert args[0] == "ls-files", args
4357
+ return b"fifo\0"
4358
+
4359
+
4360
+ expect_rejected(fifo_listing, "untracked FIFO")
4361
+ fifo.unlink()
4362
+
4363
+ nul = repo / "nul.bin"
4364
+ nul.write_bytes(b"safe\0unsafe")
4365
+
4366
+
4367
+ def nul_listing(repo_path, args, *unused, **kwargs):
4368
+ assert args[0] == "ls-files", args
4369
+ return b"nul.bin\0"
4370
+
4371
+
4372
+ error = expect_rejected(nul_listing, "NUL-bearing untracked file")
4373
+ assert "NUL-bearing" in error.reason, error.reason
4374
+ nul.unlink()
4375
+
4376
+ non_utf8 = repo / "non-utf8.bin"
4377
+ non_utf8.write_bytes(b"\xff")
4378
+
4379
+
4380
+ def non_utf8_listing(repo_path, args, *unused, **kwargs):
4381
+ assert args[0] == "ls-files", args
4382
+ return b"non-utf8.bin\0"
4383
+
4384
+
4385
+ error = expect_rejected(non_utf8_listing, "non-UTF-8 untracked file")
4386
+ assert "non-UTF-8" in error.reason, error.reason
4387
+ non_utf8.unlink()
4388
+
4389
+ accepted_control_paths = []
4390
+ for label, relative in (
4391
+ ("tab", "tab\tname.txt"),
4392
+ ("newline", "line\nname.txt"),
4393
+ ("carriage-return", "carriage\rname.txt"),
4394
+ ("C0", "unit\x01name.txt"),
4395
+ ("DEL", "delete\x7fname.txt"),
4396
+ ("C1-NEL", "next\u0085line.txt"),
4397
+ ("line-separator", "line\u2028separator.txt"),
4398
+ ("paragraph-separator", "paragraph\u2029separator.txt"),
4399
+ ):
4400
+ control_path = repo / relative
4401
+ control_path.write_text("control path\n", encoding="utf-8")
4402
+ encoded_path = relative.encode("utf-8")
4403
+
4404
+ def control_listing(repo_path, args, *unused, **kwargs):
4405
+ assert args[0] == "ls-files", args
4406
+ return encoded_path + b"\0"
4407
+
4408
+ try:
4409
+ error = expect_rejected(control_listing, f"{label} untracked path")
4410
+ except AssertionError:
4411
+ accepted_control_paths.append(label)
4412
+ else:
4413
+ assert "control-character" in error.reason, error.reason
4414
+ finally:
4415
+ control_path.unlink()
4416
+ assert not accepted_control_paths, accepted_control_paths
4417
+
4418
+ oversized = repo / "oversized.txt"
4419
+ with oversized.open("wb") as handle:
4420
+ handle.truncate(review_gate.MAX_PACKET_BYTES + 1)
4421
+
4422
+
4423
+ def oversized_listing(repo_path, args, *unused, **kwargs):
4424
+ assert args[0] == "ls-files", args
4425
+ return b"oversized.txt\0"
4426
+
4427
+
4428
+ error = expect_rejected(oversized_listing, "oversized untracked file")
4429
+ assert "exceeds" in error.reason, error.reason
4430
+ oversized.unlink()
4431
+
4432
+ aggregate_a = repo / "aggregate-a.txt"
4433
+ aggregate_b = repo / "aggregate-b.txt"
4434
+ aggregate_a.write_bytes(b"a" * (review_gate.MAX_PACKET_BYTES // 2))
4435
+ aggregate_b.write_bytes(b"b" * (review_gate.MAX_PACKET_BYTES // 2))
4436
+
4437
+
4438
+ def aggregate_listing(repo_path, args, *unused, **kwargs):
4439
+ assert args[0] == "ls-files", args
4440
+ return b"aggregate-a.txt\0aggregate-b.txt\0"
4441
+
4442
+
4443
+ error = expect_rejected(aggregate_listing, "aggregate untracked packet overflow")
4444
+ assert "untracked review packet exceeds" in error.reason, error.reason
4445
+ aggregate_a.unlink()
4446
+ aggregate_b.unlink()
4447
+
4448
+ raced = repo / "raced.txt"
4449
+ alternate = root / "alternate.txt"
4450
+ backup = repo / "raced.before-swap"
4451
+ raced.write_text("original-safe-bytes\n", encoding="utf-8")
4452
+ alternate.write_text("alternate-link-bytes\n", encoding="utf-8")
4453
+ calls = 0
4454
+
4455
+
4456
+ def replace_before_old_diff(repo_path, args, *unused, **kwargs):
4457
+ global calls
4458
+ calls += 1
4459
+ if args[0] == "ls-files":
4460
+ return b"raced.txt\0"
4461
+ raced.rename(backup)
4462
+ raced.symlink_to(alternate)
4463
+ return b"diff --git a/raced.txt b/raced.txt\n+alternate-link-bytes\n"
4464
+
4465
+
4466
+ original = review_gate.git_output
4467
+ review_gate.git_output = replace_before_old_diff
4468
+ try:
4469
+ packet = review_gate.untracked_packet(repo, [], time.monotonic() + 5)
4470
+ finally:
4471
+ review_gate.git_output = original
4472
+ if raced.is_symlink():
4473
+ raced.unlink()
4474
+ backup.rename(raced)
4475
+ assert b"original-safe-bytes" in packet, packet
4476
+ assert b"alternate-link-bytes" not in packet, packet
4477
+ assert calls == 1, "untracked bytes were reopened by git diff"
4478
+
4479
+ print("untracked_exact_inputs_ok")
4480
+ PY
4481
+ )"
4482
+ untracked_exact_rc=$?
4483
+ check "untracked unsafe types, bytes, sizes, races, and parent links fail closed" \
4484
+ '[ "$untracked_exact_rc" = 0 ] && [ "$untracked_exact_probe" = untracked_exact_inputs_ok ]'
4485
+
2631
4486
  check "owner selection source has one controller-owned definition" \
2632
4487
  '[ "$(grep -c '\''"implementer-declared"'\'' "$DIR/review_gate.py")" = 1 ]'
2633
4488