@mc-and-his-agents/loom-installer 0.1.48 → 0.1.49

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 (22) hide show
  1. package/package.json +1 -1
  2. package/payload/manifest.json +42 -42
  3. package/payload/plugin/loom/skills/shared/scripts/loom_check.py +33 -0
  4. package/payload/plugin/loom/skills/shared/scripts/loom_flow.py +21 -6
  5. package/payload/skills/loom-adopt/.loom-runtime/shared/scripts/loom_check.py +33 -0
  6. package/payload/skills/loom-adopt/.loom-runtime/shared/scripts/loom_flow.py +21 -6
  7. package/payload/skills/loom-handoff/.loom-runtime/shared/scripts/loom_check.py +33 -0
  8. package/payload/skills/loom-handoff/.loom-runtime/shared/scripts/loom_flow.py +21 -6
  9. package/payload/skills/loom-init/.loom-runtime/shared/scripts/loom_check.py +33 -0
  10. package/payload/skills/loom-init/.loom-runtime/shared/scripts/loom_flow.py +21 -6
  11. package/payload/skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_check.py +33 -0
  12. package/payload/skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_flow.py +21 -6
  13. package/payload/skills/loom-pre-review/.loom-runtime/shared/scripts/loom_check.py +33 -0
  14. package/payload/skills/loom-pre-review/.loom-runtime/shared/scripts/loom_flow.py +21 -6
  15. package/payload/skills/loom-resume/.loom-runtime/shared/scripts/loom_check.py +33 -0
  16. package/payload/skills/loom-resume/.loom-runtime/shared/scripts/loom_flow.py +21 -6
  17. package/payload/skills/loom-retire/.loom-runtime/shared/scripts/loom_check.py +33 -0
  18. package/payload/skills/loom-retire/.loom-runtime/shared/scripts/loom_flow.py +21 -6
  19. package/payload/skills/loom-review/.loom-runtime/shared/scripts/loom_check.py +33 -0
  20. package/payload/skills/loom-review/.loom-runtime/shared/scripts/loom_flow.py +21 -6
  21. package/payload/skills/loom-spec-review/.loom-runtime/shared/scripts/loom_check.py +33 -0
  22. package/payload/skills/loom-spec-review/.loom-runtime/shared/scripts/loom_flow.py +21 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mc-and-his-agents/loom-installer",
3
- "version": "0.1.48",
3
+ "version": "0.1.49",
4
4
  "description": "Node installer for Loom plugin and single-skill installation surfaces.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,9 +2,9 @@
2
2
  "schema_version": "loom-installer-payload/v1",
3
3
  "loom_version": "0.4.0",
4
4
  "source_repository": "https://github.com/MC-and-his-Agents/Loom",
5
- "source_commit": "e584966cb8ab5b856e9b3588173d436302fa4904",
5
+ "source_commit": "bcb61000e8157a08d51f2817b8768e5be8439343",
6
6
  "source_ref": "main",
7
- "built_at": "2026-04-28T10:12:57+08:00",
7
+ "built_at": "2026-04-28T10:56:14+08:00",
8
8
  "runtime": {
9
9
  "python_minimum": "3.10",
10
10
  "python_recommended": "3.11+"
@@ -633,13 +633,13 @@
633
633
  },
634
634
  {
635
635
  "path": "plugin/loom/skills/shared/scripts/loom_check.py",
636
- "bytes": 362269,
637
- "sha256": "451270ab050bc0b1522ae08d2c151dd704d711a41c5de5ba237d626b6d60ec1d"
636
+ "bytes": 363725,
637
+ "sha256": "a2e8c10cda53568e099b6c9ce242b524d84b7a7b0bbcb8efb4163b92e9150bb4"
638
638
  },
639
639
  {
640
640
  "path": "plugin/loom/skills/shared/scripts/loom_flow.py",
641
- "bytes": 341764,
642
- "sha256": "1d48bec787cdb4a056cd98c0f40b54fadc70711299691c343dcf4db60672246b"
641
+ "bytes": 342513,
642
+ "sha256": "00a5476d3e1a1b6215558d1d840e852be898cd818749c3caaa442bc657dde5eb"
643
643
  },
644
644
  {
645
645
  "path": "plugin/loom/skills/shared/scripts/loom_init.py",
@@ -1223,13 +1223,13 @@
1223
1223
  },
1224
1224
  {
1225
1225
  "path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_check.py",
1226
- "bytes": 362269,
1227
- "sha256": "451270ab050bc0b1522ae08d2c151dd704d711a41c5de5ba237d626b6d60ec1d"
1226
+ "bytes": 363725,
1227
+ "sha256": "a2e8c10cda53568e099b6c9ce242b524d84b7a7b0bbcb8efb4163b92e9150bb4"
1228
1228
  },
1229
1229
  {
1230
1230
  "path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_flow.py",
1231
- "bytes": 341764,
1232
- "sha256": "1d48bec787cdb4a056cd98c0f40b54fadc70711299691c343dcf4db60672246b"
1231
+ "bytes": 342513,
1232
+ "sha256": "00a5476d3e1a1b6215558d1d840e852be898cd818749c3caaa442bc657dde5eb"
1233
1233
  },
1234
1234
  {
1235
1235
  "path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_init.py",
@@ -1843,13 +1843,13 @@
1843
1843
  },
1844
1844
  {
1845
1845
  "path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_check.py",
1846
- "bytes": 362269,
1847
- "sha256": "451270ab050bc0b1522ae08d2c151dd704d711a41c5de5ba237d626b6d60ec1d"
1846
+ "bytes": 363725,
1847
+ "sha256": "a2e8c10cda53568e099b6c9ce242b524d84b7a7b0bbcb8efb4163b92e9150bb4"
1848
1848
  },
1849
1849
  {
1850
1850
  "path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_flow.py",
1851
- "bytes": 341764,
1852
- "sha256": "1d48bec787cdb4a056cd98c0f40b54fadc70711299691c343dcf4db60672246b"
1851
+ "bytes": 342513,
1852
+ "sha256": "00a5476d3e1a1b6215558d1d840e852be898cd818749c3caaa442bc657dde5eb"
1853
1853
  },
1854
1854
  {
1855
1855
  "path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_init.py",
@@ -2463,13 +2463,13 @@
2463
2463
  },
2464
2464
  {
2465
2465
  "path": "skills/loom-init/.loom-runtime/shared/scripts/loom_check.py",
2466
- "bytes": 362269,
2467
- "sha256": "451270ab050bc0b1522ae08d2c151dd704d711a41c5de5ba237d626b6d60ec1d"
2466
+ "bytes": 363725,
2467
+ "sha256": "a2e8c10cda53568e099b6c9ce242b524d84b7a7b0bbcb8efb4163b92e9150bb4"
2468
2468
  },
2469
2469
  {
2470
2470
  "path": "skills/loom-init/.loom-runtime/shared/scripts/loom_flow.py",
2471
- "bytes": 341764,
2472
- "sha256": "1d48bec787cdb4a056cd98c0f40b54fadc70711299691c343dcf4db60672246b"
2471
+ "bytes": 342513,
2472
+ "sha256": "00a5476d3e1a1b6215558d1d840e852be898cd818749c3caaa442bc657dde5eb"
2473
2473
  },
2474
2474
  {
2475
2475
  "path": "skills/loom-init/.loom-runtime/shared/scripts/loom_init.py",
@@ -3088,13 +3088,13 @@
3088
3088
  },
3089
3089
  {
3090
3090
  "path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_check.py",
3091
- "bytes": 362269,
3092
- "sha256": "451270ab050bc0b1522ae08d2c151dd704d711a41c5de5ba237d626b6d60ec1d"
3091
+ "bytes": 363725,
3092
+ "sha256": "a2e8c10cda53568e099b6c9ce242b524d84b7a7b0bbcb8efb4163b92e9150bb4"
3093
3093
  },
3094
3094
  {
3095
3095
  "path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_flow.py",
3096
- "bytes": 341764,
3097
- "sha256": "1d48bec787cdb4a056cd98c0f40b54fadc70711299691c343dcf4db60672246b"
3096
+ "bytes": 342513,
3097
+ "sha256": "00a5476d3e1a1b6215558d1d840e852be898cd818749c3caaa442bc657dde5eb"
3098
3098
  },
3099
3099
  {
3100
3100
  "path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_init.py",
@@ -3708,13 +3708,13 @@
3708
3708
  },
3709
3709
  {
3710
3710
  "path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_check.py",
3711
- "bytes": 362269,
3712
- "sha256": "451270ab050bc0b1522ae08d2c151dd704d711a41c5de5ba237d626b6d60ec1d"
3711
+ "bytes": 363725,
3712
+ "sha256": "a2e8c10cda53568e099b6c9ce242b524d84b7a7b0bbcb8efb4163b92e9150bb4"
3713
3713
  },
3714
3714
  {
3715
3715
  "path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_flow.py",
3716
- "bytes": 341764,
3717
- "sha256": "1d48bec787cdb4a056cd98c0f40b54fadc70711299691c343dcf4db60672246b"
3716
+ "bytes": 342513,
3717
+ "sha256": "00a5476d3e1a1b6215558d1d840e852be898cd818749c3caaa442bc657dde5eb"
3718
3718
  },
3719
3719
  {
3720
3720
  "path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_init.py",
@@ -4328,13 +4328,13 @@
4328
4328
  },
4329
4329
  {
4330
4330
  "path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_check.py",
4331
- "bytes": 362269,
4332
- "sha256": "451270ab050bc0b1522ae08d2c151dd704d711a41c5de5ba237d626b6d60ec1d"
4331
+ "bytes": 363725,
4332
+ "sha256": "a2e8c10cda53568e099b6c9ce242b524d84b7a7b0bbcb8efb4163b92e9150bb4"
4333
4333
  },
4334
4334
  {
4335
4335
  "path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_flow.py",
4336
- "bytes": 341764,
4337
- "sha256": "1d48bec787cdb4a056cd98c0f40b54fadc70711299691c343dcf4db60672246b"
4336
+ "bytes": 342513,
4337
+ "sha256": "00a5476d3e1a1b6215558d1d840e852be898cd818749c3caaa442bc657dde5eb"
4338
4338
  },
4339
4339
  {
4340
4340
  "path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_init.py",
@@ -4948,13 +4948,13 @@
4948
4948
  },
4949
4949
  {
4950
4950
  "path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_check.py",
4951
- "bytes": 362269,
4952
- "sha256": "451270ab050bc0b1522ae08d2c151dd704d711a41c5de5ba237d626b6d60ec1d"
4951
+ "bytes": 363725,
4952
+ "sha256": "a2e8c10cda53568e099b6c9ce242b524d84b7a7b0bbcb8efb4163b92e9150bb4"
4953
4953
  },
4954
4954
  {
4955
4955
  "path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_flow.py",
4956
- "bytes": 341764,
4957
- "sha256": "1d48bec787cdb4a056cd98c0f40b54fadc70711299691c343dcf4db60672246b"
4956
+ "bytes": 342513,
4957
+ "sha256": "00a5476d3e1a1b6215558d1d840e852be898cd818749c3caaa442bc657dde5eb"
4958
4958
  },
4959
4959
  {
4960
4960
  "path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_init.py",
@@ -5568,13 +5568,13 @@
5568
5568
  },
5569
5569
  {
5570
5570
  "path": "skills/loom-review/.loom-runtime/shared/scripts/loom_check.py",
5571
- "bytes": 362269,
5572
- "sha256": "451270ab050bc0b1522ae08d2c151dd704d711a41c5de5ba237d626b6d60ec1d"
5571
+ "bytes": 363725,
5572
+ "sha256": "a2e8c10cda53568e099b6c9ce242b524d84b7a7b0bbcb8efb4163b92e9150bb4"
5573
5573
  },
5574
5574
  {
5575
5575
  "path": "skills/loom-review/.loom-runtime/shared/scripts/loom_flow.py",
5576
- "bytes": 341764,
5577
- "sha256": "1d48bec787cdb4a056cd98c0f40b54fadc70711299691c343dcf4db60672246b"
5576
+ "bytes": 342513,
5577
+ "sha256": "00a5476d3e1a1b6215558d1d840e852be898cd818749c3caaa442bc657dde5eb"
5578
5578
  },
5579
5579
  {
5580
5580
  "path": "skills/loom-review/.loom-runtime/shared/scripts/loom_init.py",
@@ -6188,13 +6188,13 @@
6188
6188
  },
6189
6189
  {
6190
6190
  "path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_check.py",
6191
- "bytes": 362269,
6192
- "sha256": "451270ab050bc0b1522ae08d2c151dd704d711a41c5de5ba237d626b6d60ec1d"
6191
+ "bytes": 363725,
6192
+ "sha256": "a2e8c10cda53568e099b6c9ce242b524d84b7a7b0bbcb8efb4163b92e9150bb4"
6193
6193
  },
6194
6194
  {
6195
6195
  "path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_flow.py",
6196
- "bytes": 341764,
6197
- "sha256": "1d48bec787cdb4a056cd98c0f40b54fadc70711299691c343dcf4db60672246b"
6196
+ "bytes": 342513,
6197
+ "sha256": "00a5476d3e1a1b6215558d1d840e852be898cd818749c3caaa442bc657dde5eb"
6198
6198
  },
6199
6199
  {
6200
6200
  "path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_init.py",
@@ -7229,6 +7229,39 @@ def check_adversarial_adoption_fixture(root: Path) -> list[Failure]:
7229
7229
  else:
7230
7230
  failures.append(Failure("adversarial-adoption", "review shadow carrier fixture could not load review artifact"))
7231
7231
 
7232
+ unreadable_review_shadow_target = base / "unreadable-review-shadow"
7233
+ shutil.copytree(baseline, unreadable_review_shadow_target)
7234
+ (unreadable_review_shadow_target / ".loom/shadow/review-repo.json").write_text("{not-json", encoding="utf-8")
7235
+ run_command(
7236
+ root,
7237
+ ["git", "add", "-f", ".loom/shadow/review-repo.json"],
7238
+ cwd=unreadable_review_shadow_target,
7239
+ timeout_seconds=30,
7240
+ )
7241
+ run_command(
7242
+ root,
7243
+ ["git", "commit", "-m", "corrupt review shadow evidence"],
7244
+ cwd=unreadable_review_shadow_target,
7245
+ timeout_seconds=30,
7246
+ )
7247
+ unreadable_payload, unreadable_error = load_command_json(
7248
+ root,
7249
+ [
7250
+ "python3",
7251
+ str(unreadable_review_shadow_target / ".loom/bin/loom_flow.py"),
7252
+ "checkpoint",
7253
+ "merge",
7254
+ "--target",
7255
+ str(unreadable_review_shadow_target),
7256
+ "--item",
7257
+ "INIT-0001",
7258
+ ],
7259
+ )
7260
+ if unreadable_error:
7261
+ failures.append(Failure("adversarial-adoption", f"unreadable review shadow evidence must fail closed without crashing: {unreadable_error}"))
7262
+ elif unreadable_payload.get("result") == "pass":
7263
+ failures.append(Failure("adversarial-adoption", "unreadable review shadow evidence must fail closed instead of passing"))
7264
+
7232
7265
  invalid_review_schema_target = base / "invalid-review-schema"
7233
7266
  shutil.copytree(baseline, invalid_review_schema_target)
7234
7267
  invalid_review_payload = load_json_file(invalid_review_schema_target / review_path)
@@ -1489,7 +1489,10 @@ def shadow_evidence_paths_for_sources(target_root: Path, source_paths: set[str])
1489
1489
  relative = evidence_path.relative_to(target_root).as_posix()
1490
1490
  if relative == ".loom/shadow/shadow-parity.json":
1491
1491
  continue
1492
- payload = load_json_file(evidence_path)
1492
+ try:
1493
+ payload = load_json_file(evidence_path)
1494
+ except (OSError, ValueError, json.JSONDecodeError):
1495
+ continue
1493
1496
  if not isinstance(payload, dict):
1494
1497
  continue
1495
1498
  source_files = payload.get("source_files")
@@ -1509,10 +1512,13 @@ def allowed_post_review_carrier_paths(context: dict[str, Any], *review_paths: st
1509
1512
  allowed.update(shadow_evidence_paths_for_sources(context["target_root"], set(review_paths)))
1510
1513
  review_shadow_root = context["target_root"] / ".loom/shadow"
1511
1514
  if review_shadow_root.exists():
1512
- allowed.update(
1513
- evidence_path.relative_to(context["target_root"]).as_posix()
1514
- for evidence_path in sorted(review_shadow_root.glob("review-*.json"))
1515
- )
1515
+ for evidence_path in sorted(review_shadow_root.glob("review-*.json")):
1516
+ try:
1517
+ payload = load_json_file(evidence_path)
1518
+ except (OSError, ValueError, json.JSONDecodeError):
1519
+ continue
1520
+ if isinstance(payload, dict):
1521
+ allowed.add(evidence_path.relative_to(context["target_root"]).as_posix())
1516
1522
  return allowed
1517
1523
 
1518
1524
 
@@ -2107,7 +2113,7 @@ def build_review_flow_payload(
2107
2113
 
2108
2114
  build_payload = checkpoint_payload("build", context)
2109
2115
  governance_surface = build_governance_surface(target_root)
2110
- surface_name = "review" if operation == "review" else "spec_review"
2116
+ surface_name = "review"
2111
2117
  repo_specific_requirements = repo_specific_requirements_payload(
2112
2118
  governance_surface.get("repo_interface"),
2113
2119
  target_root=target_root,
@@ -4237,6 +4243,15 @@ def carrier_refresh_payload(target_root: Path, output_relative: str, expected_it
4237
4243
  for action in actions:
4238
4244
  if action.get("status") == "block":
4239
4245
  missing_inputs.extend(str(message) for message in action.get("missing_inputs", []))
4246
+ if runtime_state.get("result") != "pass":
4247
+ refreshable_runtime_drift = {
4248
+ f"bootstrap runtime artifact `{action.get('path')}` sha256 drifted"
4249
+ for action in actions
4250
+ if action.get("kind") is None and action.get("status") == "refresh-needed" and action.get("path")
4251
+ }
4252
+ for message in runtime_state.get("missing_inputs", []):
4253
+ if str(message) not in refreshable_runtime_drift:
4254
+ missing_inputs.append(f"runtime-state: {message}")
4240
4255
 
4241
4256
  review_status: dict[str, Any] = {"status": "not_checked"}
4242
4257
  if not context_errors:
@@ -7229,6 +7229,39 @@ def check_adversarial_adoption_fixture(root: Path) -> list[Failure]:
7229
7229
  else:
7230
7230
  failures.append(Failure("adversarial-adoption", "review shadow carrier fixture could not load review artifact"))
7231
7231
 
7232
+ unreadable_review_shadow_target = base / "unreadable-review-shadow"
7233
+ shutil.copytree(baseline, unreadable_review_shadow_target)
7234
+ (unreadable_review_shadow_target / ".loom/shadow/review-repo.json").write_text("{not-json", encoding="utf-8")
7235
+ run_command(
7236
+ root,
7237
+ ["git", "add", "-f", ".loom/shadow/review-repo.json"],
7238
+ cwd=unreadable_review_shadow_target,
7239
+ timeout_seconds=30,
7240
+ )
7241
+ run_command(
7242
+ root,
7243
+ ["git", "commit", "-m", "corrupt review shadow evidence"],
7244
+ cwd=unreadable_review_shadow_target,
7245
+ timeout_seconds=30,
7246
+ )
7247
+ unreadable_payload, unreadable_error = load_command_json(
7248
+ root,
7249
+ [
7250
+ "python3",
7251
+ str(unreadable_review_shadow_target / ".loom/bin/loom_flow.py"),
7252
+ "checkpoint",
7253
+ "merge",
7254
+ "--target",
7255
+ str(unreadable_review_shadow_target),
7256
+ "--item",
7257
+ "INIT-0001",
7258
+ ],
7259
+ )
7260
+ if unreadable_error:
7261
+ failures.append(Failure("adversarial-adoption", f"unreadable review shadow evidence must fail closed without crashing: {unreadable_error}"))
7262
+ elif unreadable_payload.get("result") == "pass":
7263
+ failures.append(Failure("adversarial-adoption", "unreadable review shadow evidence must fail closed instead of passing"))
7264
+
7232
7265
  invalid_review_schema_target = base / "invalid-review-schema"
7233
7266
  shutil.copytree(baseline, invalid_review_schema_target)
7234
7267
  invalid_review_payload = load_json_file(invalid_review_schema_target / review_path)
@@ -1489,7 +1489,10 @@ def shadow_evidence_paths_for_sources(target_root: Path, source_paths: set[str])
1489
1489
  relative = evidence_path.relative_to(target_root).as_posix()
1490
1490
  if relative == ".loom/shadow/shadow-parity.json":
1491
1491
  continue
1492
- payload = load_json_file(evidence_path)
1492
+ try:
1493
+ payload = load_json_file(evidence_path)
1494
+ except (OSError, ValueError, json.JSONDecodeError):
1495
+ continue
1493
1496
  if not isinstance(payload, dict):
1494
1497
  continue
1495
1498
  source_files = payload.get("source_files")
@@ -1509,10 +1512,13 @@ def allowed_post_review_carrier_paths(context: dict[str, Any], *review_paths: st
1509
1512
  allowed.update(shadow_evidence_paths_for_sources(context["target_root"], set(review_paths)))
1510
1513
  review_shadow_root = context["target_root"] / ".loom/shadow"
1511
1514
  if review_shadow_root.exists():
1512
- allowed.update(
1513
- evidence_path.relative_to(context["target_root"]).as_posix()
1514
- for evidence_path in sorted(review_shadow_root.glob("review-*.json"))
1515
- )
1515
+ for evidence_path in sorted(review_shadow_root.glob("review-*.json")):
1516
+ try:
1517
+ payload = load_json_file(evidence_path)
1518
+ except (OSError, ValueError, json.JSONDecodeError):
1519
+ continue
1520
+ if isinstance(payload, dict):
1521
+ allowed.add(evidence_path.relative_to(context["target_root"]).as_posix())
1516
1522
  return allowed
1517
1523
 
1518
1524
 
@@ -2107,7 +2113,7 @@ def build_review_flow_payload(
2107
2113
 
2108
2114
  build_payload = checkpoint_payload("build", context)
2109
2115
  governance_surface = build_governance_surface(target_root)
2110
- surface_name = "review" if operation == "review" else "spec_review"
2116
+ surface_name = "review"
2111
2117
  repo_specific_requirements = repo_specific_requirements_payload(
2112
2118
  governance_surface.get("repo_interface"),
2113
2119
  target_root=target_root,
@@ -4237,6 +4243,15 @@ def carrier_refresh_payload(target_root: Path, output_relative: str, expected_it
4237
4243
  for action in actions:
4238
4244
  if action.get("status") == "block":
4239
4245
  missing_inputs.extend(str(message) for message in action.get("missing_inputs", []))
4246
+ if runtime_state.get("result") != "pass":
4247
+ refreshable_runtime_drift = {
4248
+ f"bootstrap runtime artifact `{action.get('path')}` sha256 drifted"
4249
+ for action in actions
4250
+ if action.get("kind") is None and action.get("status") == "refresh-needed" and action.get("path")
4251
+ }
4252
+ for message in runtime_state.get("missing_inputs", []):
4253
+ if str(message) not in refreshable_runtime_drift:
4254
+ missing_inputs.append(f"runtime-state: {message}")
4240
4255
 
4241
4256
  review_status: dict[str, Any] = {"status": "not_checked"}
4242
4257
  if not context_errors:
@@ -7229,6 +7229,39 @@ def check_adversarial_adoption_fixture(root: Path) -> list[Failure]:
7229
7229
  else:
7230
7230
  failures.append(Failure("adversarial-adoption", "review shadow carrier fixture could not load review artifact"))
7231
7231
 
7232
+ unreadable_review_shadow_target = base / "unreadable-review-shadow"
7233
+ shutil.copytree(baseline, unreadable_review_shadow_target)
7234
+ (unreadable_review_shadow_target / ".loom/shadow/review-repo.json").write_text("{not-json", encoding="utf-8")
7235
+ run_command(
7236
+ root,
7237
+ ["git", "add", "-f", ".loom/shadow/review-repo.json"],
7238
+ cwd=unreadable_review_shadow_target,
7239
+ timeout_seconds=30,
7240
+ )
7241
+ run_command(
7242
+ root,
7243
+ ["git", "commit", "-m", "corrupt review shadow evidence"],
7244
+ cwd=unreadable_review_shadow_target,
7245
+ timeout_seconds=30,
7246
+ )
7247
+ unreadable_payload, unreadable_error = load_command_json(
7248
+ root,
7249
+ [
7250
+ "python3",
7251
+ str(unreadable_review_shadow_target / ".loom/bin/loom_flow.py"),
7252
+ "checkpoint",
7253
+ "merge",
7254
+ "--target",
7255
+ str(unreadable_review_shadow_target),
7256
+ "--item",
7257
+ "INIT-0001",
7258
+ ],
7259
+ )
7260
+ if unreadable_error:
7261
+ failures.append(Failure("adversarial-adoption", f"unreadable review shadow evidence must fail closed without crashing: {unreadable_error}"))
7262
+ elif unreadable_payload.get("result") == "pass":
7263
+ failures.append(Failure("adversarial-adoption", "unreadable review shadow evidence must fail closed instead of passing"))
7264
+
7232
7265
  invalid_review_schema_target = base / "invalid-review-schema"
7233
7266
  shutil.copytree(baseline, invalid_review_schema_target)
7234
7267
  invalid_review_payload = load_json_file(invalid_review_schema_target / review_path)
@@ -1489,7 +1489,10 @@ def shadow_evidence_paths_for_sources(target_root: Path, source_paths: set[str])
1489
1489
  relative = evidence_path.relative_to(target_root).as_posix()
1490
1490
  if relative == ".loom/shadow/shadow-parity.json":
1491
1491
  continue
1492
- payload = load_json_file(evidence_path)
1492
+ try:
1493
+ payload = load_json_file(evidence_path)
1494
+ except (OSError, ValueError, json.JSONDecodeError):
1495
+ continue
1493
1496
  if not isinstance(payload, dict):
1494
1497
  continue
1495
1498
  source_files = payload.get("source_files")
@@ -1509,10 +1512,13 @@ def allowed_post_review_carrier_paths(context: dict[str, Any], *review_paths: st
1509
1512
  allowed.update(shadow_evidence_paths_for_sources(context["target_root"], set(review_paths)))
1510
1513
  review_shadow_root = context["target_root"] / ".loom/shadow"
1511
1514
  if review_shadow_root.exists():
1512
- allowed.update(
1513
- evidence_path.relative_to(context["target_root"]).as_posix()
1514
- for evidence_path in sorted(review_shadow_root.glob("review-*.json"))
1515
- )
1515
+ for evidence_path in sorted(review_shadow_root.glob("review-*.json")):
1516
+ try:
1517
+ payload = load_json_file(evidence_path)
1518
+ except (OSError, ValueError, json.JSONDecodeError):
1519
+ continue
1520
+ if isinstance(payload, dict):
1521
+ allowed.add(evidence_path.relative_to(context["target_root"]).as_posix())
1516
1522
  return allowed
1517
1523
 
1518
1524
 
@@ -2107,7 +2113,7 @@ def build_review_flow_payload(
2107
2113
 
2108
2114
  build_payload = checkpoint_payload("build", context)
2109
2115
  governance_surface = build_governance_surface(target_root)
2110
- surface_name = "review" if operation == "review" else "spec_review"
2116
+ surface_name = "review"
2111
2117
  repo_specific_requirements = repo_specific_requirements_payload(
2112
2118
  governance_surface.get("repo_interface"),
2113
2119
  target_root=target_root,
@@ -4237,6 +4243,15 @@ def carrier_refresh_payload(target_root: Path, output_relative: str, expected_it
4237
4243
  for action in actions:
4238
4244
  if action.get("status") == "block":
4239
4245
  missing_inputs.extend(str(message) for message in action.get("missing_inputs", []))
4246
+ if runtime_state.get("result") != "pass":
4247
+ refreshable_runtime_drift = {
4248
+ f"bootstrap runtime artifact `{action.get('path')}` sha256 drifted"
4249
+ for action in actions
4250
+ if action.get("kind") is None and action.get("status") == "refresh-needed" and action.get("path")
4251
+ }
4252
+ for message in runtime_state.get("missing_inputs", []):
4253
+ if str(message) not in refreshable_runtime_drift:
4254
+ missing_inputs.append(f"runtime-state: {message}")
4240
4255
 
4241
4256
  review_status: dict[str, Any] = {"status": "not_checked"}
4242
4257
  if not context_errors:
@@ -7229,6 +7229,39 @@ def check_adversarial_adoption_fixture(root: Path) -> list[Failure]:
7229
7229
  else:
7230
7230
  failures.append(Failure("adversarial-adoption", "review shadow carrier fixture could not load review artifact"))
7231
7231
 
7232
+ unreadable_review_shadow_target = base / "unreadable-review-shadow"
7233
+ shutil.copytree(baseline, unreadable_review_shadow_target)
7234
+ (unreadable_review_shadow_target / ".loom/shadow/review-repo.json").write_text("{not-json", encoding="utf-8")
7235
+ run_command(
7236
+ root,
7237
+ ["git", "add", "-f", ".loom/shadow/review-repo.json"],
7238
+ cwd=unreadable_review_shadow_target,
7239
+ timeout_seconds=30,
7240
+ )
7241
+ run_command(
7242
+ root,
7243
+ ["git", "commit", "-m", "corrupt review shadow evidence"],
7244
+ cwd=unreadable_review_shadow_target,
7245
+ timeout_seconds=30,
7246
+ )
7247
+ unreadable_payload, unreadable_error = load_command_json(
7248
+ root,
7249
+ [
7250
+ "python3",
7251
+ str(unreadable_review_shadow_target / ".loom/bin/loom_flow.py"),
7252
+ "checkpoint",
7253
+ "merge",
7254
+ "--target",
7255
+ str(unreadable_review_shadow_target),
7256
+ "--item",
7257
+ "INIT-0001",
7258
+ ],
7259
+ )
7260
+ if unreadable_error:
7261
+ failures.append(Failure("adversarial-adoption", f"unreadable review shadow evidence must fail closed without crashing: {unreadable_error}"))
7262
+ elif unreadable_payload.get("result") == "pass":
7263
+ failures.append(Failure("adversarial-adoption", "unreadable review shadow evidence must fail closed instead of passing"))
7264
+
7232
7265
  invalid_review_schema_target = base / "invalid-review-schema"
7233
7266
  shutil.copytree(baseline, invalid_review_schema_target)
7234
7267
  invalid_review_payload = load_json_file(invalid_review_schema_target / review_path)
@@ -1489,7 +1489,10 @@ def shadow_evidence_paths_for_sources(target_root: Path, source_paths: set[str])
1489
1489
  relative = evidence_path.relative_to(target_root).as_posix()
1490
1490
  if relative == ".loom/shadow/shadow-parity.json":
1491
1491
  continue
1492
- payload = load_json_file(evidence_path)
1492
+ try:
1493
+ payload = load_json_file(evidence_path)
1494
+ except (OSError, ValueError, json.JSONDecodeError):
1495
+ continue
1493
1496
  if not isinstance(payload, dict):
1494
1497
  continue
1495
1498
  source_files = payload.get("source_files")
@@ -1509,10 +1512,13 @@ def allowed_post_review_carrier_paths(context: dict[str, Any], *review_paths: st
1509
1512
  allowed.update(shadow_evidence_paths_for_sources(context["target_root"], set(review_paths)))
1510
1513
  review_shadow_root = context["target_root"] / ".loom/shadow"
1511
1514
  if review_shadow_root.exists():
1512
- allowed.update(
1513
- evidence_path.relative_to(context["target_root"]).as_posix()
1514
- for evidence_path in sorted(review_shadow_root.glob("review-*.json"))
1515
- )
1515
+ for evidence_path in sorted(review_shadow_root.glob("review-*.json")):
1516
+ try:
1517
+ payload = load_json_file(evidence_path)
1518
+ except (OSError, ValueError, json.JSONDecodeError):
1519
+ continue
1520
+ if isinstance(payload, dict):
1521
+ allowed.add(evidence_path.relative_to(context["target_root"]).as_posix())
1516
1522
  return allowed
1517
1523
 
1518
1524
 
@@ -2107,7 +2113,7 @@ def build_review_flow_payload(
2107
2113
 
2108
2114
  build_payload = checkpoint_payload("build", context)
2109
2115
  governance_surface = build_governance_surface(target_root)
2110
- surface_name = "review" if operation == "review" else "spec_review"
2116
+ surface_name = "review"
2111
2117
  repo_specific_requirements = repo_specific_requirements_payload(
2112
2118
  governance_surface.get("repo_interface"),
2113
2119
  target_root=target_root,
@@ -4237,6 +4243,15 @@ def carrier_refresh_payload(target_root: Path, output_relative: str, expected_it
4237
4243
  for action in actions:
4238
4244
  if action.get("status") == "block":
4239
4245
  missing_inputs.extend(str(message) for message in action.get("missing_inputs", []))
4246
+ if runtime_state.get("result") != "pass":
4247
+ refreshable_runtime_drift = {
4248
+ f"bootstrap runtime artifact `{action.get('path')}` sha256 drifted"
4249
+ for action in actions
4250
+ if action.get("kind") is None and action.get("status") == "refresh-needed" and action.get("path")
4251
+ }
4252
+ for message in runtime_state.get("missing_inputs", []):
4253
+ if str(message) not in refreshable_runtime_drift:
4254
+ missing_inputs.append(f"runtime-state: {message}")
4240
4255
 
4241
4256
  review_status: dict[str, Any] = {"status": "not_checked"}
4242
4257
  if not context_errors:
@@ -7229,6 +7229,39 @@ def check_adversarial_adoption_fixture(root: Path) -> list[Failure]:
7229
7229
  else:
7230
7230
  failures.append(Failure("adversarial-adoption", "review shadow carrier fixture could not load review artifact"))
7231
7231
 
7232
+ unreadable_review_shadow_target = base / "unreadable-review-shadow"
7233
+ shutil.copytree(baseline, unreadable_review_shadow_target)
7234
+ (unreadable_review_shadow_target / ".loom/shadow/review-repo.json").write_text("{not-json", encoding="utf-8")
7235
+ run_command(
7236
+ root,
7237
+ ["git", "add", "-f", ".loom/shadow/review-repo.json"],
7238
+ cwd=unreadable_review_shadow_target,
7239
+ timeout_seconds=30,
7240
+ )
7241
+ run_command(
7242
+ root,
7243
+ ["git", "commit", "-m", "corrupt review shadow evidence"],
7244
+ cwd=unreadable_review_shadow_target,
7245
+ timeout_seconds=30,
7246
+ )
7247
+ unreadable_payload, unreadable_error = load_command_json(
7248
+ root,
7249
+ [
7250
+ "python3",
7251
+ str(unreadable_review_shadow_target / ".loom/bin/loom_flow.py"),
7252
+ "checkpoint",
7253
+ "merge",
7254
+ "--target",
7255
+ str(unreadable_review_shadow_target),
7256
+ "--item",
7257
+ "INIT-0001",
7258
+ ],
7259
+ )
7260
+ if unreadable_error:
7261
+ failures.append(Failure("adversarial-adoption", f"unreadable review shadow evidence must fail closed without crashing: {unreadable_error}"))
7262
+ elif unreadable_payload.get("result") == "pass":
7263
+ failures.append(Failure("adversarial-adoption", "unreadable review shadow evidence must fail closed instead of passing"))
7264
+
7232
7265
  invalid_review_schema_target = base / "invalid-review-schema"
7233
7266
  shutil.copytree(baseline, invalid_review_schema_target)
7234
7267
  invalid_review_payload = load_json_file(invalid_review_schema_target / review_path)
@@ -1489,7 +1489,10 @@ def shadow_evidence_paths_for_sources(target_root: Path, source_paths: set[str])
1489
1489
  relative = evidence_path.relative_to(target_root).as_posix()
1490
1490
  if relative == ".loom/shadow/shadow-parity.json":
1491
1491
  continue
1492
- payload = load_json_file(evidence_path)
1492
+ try:
1493
+ payload = load_json_file(evidence_path)
1494
+ except (OSError, ValueError, json.JSONDecodeError):
1495
+ continue
1493
1496
  if not isinstance(payload, dict):
1494
1497
  continue
1495
1498
  source_files = payload.get("source_files")
@@ -1509,10 +1512,13 @@ def allowed_post_review_carrier_paths(context: dict[str, Any], *review_paths: st
1509
1512
  allowed.update(shadow_evidence_paths_for_sources(context["target_root"], set(review_paths)))
1510
1513
  review_shadow_root = context["target_root"] / ".loom/shadow"
1511
1514
  if review_shadow_root.exists():
1512
- allowed.update(
1513
- evidence_path.relative_to(context["target_root"]).as_posix()
1514
- for evidence_path in sorted(review_shadow_root.glob("review-*.json"))
1515
- )
1515
+ for evidence_path in sorted(review_shadow_root.glob("review-*.json")):
1516
+ try:
1517
+ payload = load_json_file(evidence_path)
1518
+ except (OSError, ValueError, json.JSONDecodeError):
1519
+ continue
1520
+ if isinstance(payload, dict):
1521
+ allowed.add(evidence_path.relative_to(context["target_root"]).as_posix())
1516
1522
  return allowed
1517
1523
 
1518
1524
 
@@ -2107,7 +2113,7 @@ def build_review_flow_payload(
2107
2113
 
2108
2114
  build_payload = checkpoint_payload("build", context)
2109
2115
  governance_surface = build_governance_surface(target_root)
2110
- surface_name = "review" if operation == "review" else "spec_review"
2116
+ surface_name = "review"
2111
2117
  repo_specific_requirements = repo_specific_requirements_payload(
2112
2118
  governance_surface.get("repo_interface"),
2113
2119
  target_root=target_root,
@@ -4237,6 +4243,15 @@ def carrier_refresh_payload(target_root: Path, output_relative: str, expected_it
4237
4243
  for action in actions:
4238
4244
  if action.get("status") == "block":
4239
4245
  missing_inputs.extend(str(message) for message in action.get("missing_inputs", []))
4246
+ if runtime_state.get("result") != "pass":
4247
+ refreshable_runtime_drift = {
4248
+ f"bootstrap runtime artifact `{action.get('path')}` sha256 drifted"
4249
+ for action in actions
4250
+ if action.get("kind") is None and action.get("status") == "refresh-needed" and action.get("path")
4251
+ }
4252
+ for message in runtime_state.get("missing_inputs", []):
4253
+ if str(message) not in refreshable_runtime_drift:
4254
+ missing_inputs.append(f"runtime-state: {message}")
4240
4255
 
4241
4256
  review_status: dict[str, Any] = {"status": "not_checked"}
4242
4257
  if not context_errors:
@@ -7229,6 +7229,39 @@ def check_adversarial_adoption_fixture(root: Path) -> list[Failure]:
7229
7229
  else:
7230
7230
  failures.append(Failure("adversarial-adoption", "review shadow carrier fixture could not load review artifact"))
7231
7231
 
7232
+ unreadable_review_shadow_target = base / "unreadable-review-shadow"
7233
+ shutil.copytree(baseline, unreadable_review_shadow_target)
7234
+ (unreadable_review_shadow_target / ".loom/shadow/review-repo.json").write_text("{not-json", encoding="utf-8")
7235
+ run_command(
7236
+ root,
7237
+ ["git", "add", "-f", ".loom/shadow/review-repo.json"],
7238
+ cwd=unreadable_review_shadow_target,
7239
+ timeout_seconds=30,
7240
+ )
7241
+ run_command(
7242
+ root,
7243
+ ["git", "commit", "-m", "corrupt review shadow evidence"],
7244
+ cwd=unreadable_review_shadow_target,
7245
+ timeout_seconds=30,
7246
+ )
7247
+ unreadable_payload, unreadable_error = load_command_json(
7248
+ root,
7249
+ [
7250
+ "python3",
7251
+ str(unreadable_review_shadow_target / ".loom/bin/loom_flow.py"),
7252
+ "checkpoint",
7253
+ "merge",
7254
+ "--target",
7255
+ str(unreadable_review_shadow_target),
7256
+ "--item",
7257
+ "INIT-0001",
7258
+ ],
7259
+ )
7260
+ if unreadable_error:
7261
+ failures.append(Failure("adversarial-adoption", f"unreadable review shadow evidence must fail closed without crashing: {unreadable_error}"))
7262
+ elif unreadable_payload.get("result") == "pass":
7263
+ failures.append(Failure("adversarial-adoption", "unreadable review shadow evidence must fail closed instead of passing"))
7264
+
7232
7265
  invalid_review_schema_target = base / "invalid-review-schema"
7233
7266
  shutil.copytree(baseline, invalid_review_schema_target)
7234
7267
  invalid_review_payload = load_json_file(invalid_review_schema_target / review_path)
@@ -1489,7 +1489,10 @@ def shadow_evidence_paths_for_sources(target_root: Path, source_paths: set[str])
1489
1489
  relative = evidence_path.relative_to(target_root).as_posix()
1490
1490
  if relative == ".loom/shadow/shadow-parity.json":
1491
1491
  continue
1492
- payload = load_json_file(evidence_path)
1492
+ try:
1493
+ payload = load_json_file(evidence_path)
1494
+ except (OSError, ValueError, json.JSONDecodeError):
1495
+ continue
1493
1496
  if not isinstance(payload, dict):
1494
1497
  continue
1495
1498
  source_files = payload.get("source_files")
@@ -1509,10 +1512,13 @@ def allowed_post_review_carrier_paths(context: dict[str, Any], *review_paths: st
1509
1512
  allowed.update(shadow_evidence_paths_for_sources(context["target_root"], set(review_paths)))
1510
1513
  review_shadow_root = context["target_root"] / ".loom/shadow"
1511
1514
  if review_shadow_root.exists():
1512
- allowed.update(
1513
- evidence_path.relative_to(context["target_root"]).as_posix()
1514
- for evidence_path in sorted(review_shadow_root.glob("review-*.json"))
1515
- )
1515
+ for evidence_path in sorted(review_shadow_root.glob("review-*.json")):
1516
+ try:
1517
+ payload = load_json_file(evidence_path)
1518
+ except (OSError, ValueError, json.JSONDecodeError):
1519
+ continue
1520
+ if isinstance(payload, dict):
1521
+ allowed.add(evidence_path.relative_to(context["target_root"]).as_posix())
1516
1522
  return allowed
1517
1523
 
1518
1524
 
@@ -2107,7 +2113,7 @@ def build_review_flow_payload(
2107
2113
 
2108
2114
  build_payload = checkpoint_payload("build", context)
2109
2115
  governance_surface = build_governance_surface(target_root)
2110
- surface_name = "review" if operation == "review" else "spec_review"
2116
+ surface_name = "review"
2111
2117
  repo_specific_requirements = repo_specific_requirements_payload(
2112
2118
  governance_surface.get("repo_interface"),
2113
2119
  target_root=target_root,
@@ -4237,6 +4243,15 @@ def carrier_refresh_payload(target_root: Path, output_relative: str, expected_it
4237
4243
  for action in actions:
4238
4244
  if action.get("status") == "block":
4239
4245
  missing_inputs.extend(str(message) for message in action.get("missing_inputs", []))
4246
+ if runtime_state.get("result") != "pass":
4247
+ refreshable_runtime_drift = {
4248
+ f"bootstrap runtime artifact `{action.get('path')}` sha256 drifted"
4249
+ for action in actions
4250
+ if action.get("kind") is None and action.get("status") == "refresh-needed" and action.get("path")
4251
+ }
4252
+ for message in runtime_state.get("missing_inputs", []):
4253
+ if str(message) not in refreshable_runtime_drift:
4254
+ missing_inputs.append(f"runtime-state: {message}")
4240
4255
 
4241
4256
  review_status: dict[str, Any] = {"status": "not_checked"}
4242
4257
  if not context_errors:
@@ -7229,6 +7229,39 @@ def check_adversarial_adoption_fixture(root: Path) -> list[Failure]:
7229
7229
  else:
7230
7230
  failures.append(Failure("adversarial-adoption", "review shadow carrier fixture could not load review artifact"))
7231
7231
 
7232
+ unreadable_review_shadow_target = base / "unreadable-review-shadow"
7233
+ shutil.copytree(baseline, unreadable_review_shadow_target)
7234
+ (unreadable_review_shadow_target / ".loom/shadow/review-repo.json").write_text("{not-json", encoding="utf-8")
7235
+ run_command(
7236
+ root,
7237
+ ["git", "add", "-f", ".loom/shadow/review-repo.json"],
7238
+ cwd=unreadable_review_shadow_target,
7239
+ timeout_seconds=30,
7240
+ )
7241
+ run_command(
7242
+ root,
7243
+ ["git", "commit", "-m", "corrupt review shadow evidence"],
7244
+ cwd=unreadable_review_shadow_target,
7245
+ timeout_seconds=30,
7246
+ )
7247
+ unreadable_payload, unreadable_error = load_command_json(
7248
+ root,
7249
+ [
7250
+ "python3",
7251
+ str(unreadable_review_shadow_target / ".loom/bin/loom_flow.py"),
7252
+ "checkpoint",
7253
+ "merge",
7254
+ "--target",
7255
+ str(unreadable_review_shadow_target),
7256
+ "--item",
7257
+ "INIT-0001",
7258
+ ],
7259
+ )
7260
+ if unreadable_error:
7261
+ failures.append(Failure("adversarial-adoption", f"unreadable review shadow evidence must fail closed without crashing: {unreadable_error}"))
7262
+ elif unreadable_payload.get("result") == "pass":
7263
+ failures.append(Failure("adversarial-adoption", "unreadable review shadow evidence must fail closed instead of passing"))
7264
+
7232
7265
  invalid_review_schema_target = base / "invalid-review-schema"
7233
7266
  shutil.copytree(baseline, invalid_review_schema_target)
7234
7267
  invalid_review_payload = load_json_file(invalid_review_schema_target / review_path)
@@ -1489,7 +1489,10 @@ def shadow_evidence_paths_for_sources(target_root: Path, source_paths: set[str])
1489
1489
  relative = evidence_path.relative_to(target_root).as_posix()
1490
1490
  if relative == ".loom/shadow/shadow-parity.json":
1491
1491
  continue
1492
- payload = load_json_file(evidence_path)
1492
+ try:
1493
+ payload = load_json_file(evidence_path)
1494
+ except (OSError, ValueError, json.JSONDecodeError):
1495
+ continue
1493
1496
  if not isinstance(payload, dict):
1494
1497
  continue
1495
1498
  source_files = payload.get("source_files")
@@ -1509,10 +1512,13 @@ def allowed_post_review_carrier_paths(context: dict[str, Any], *review_paths: st
1509
1512
  allowed.update(shadow_evidence_paths_for_sources(context["target_root"], set(review_paths)))
1510
1513
  review_shadow_root = context["target_root"] / ".loom/shadow"
1511
1514
  if review_shadow_root.exists():
1512
- allowed.update(
1513
- evidence_path.relative_to(context["target_root"]).as_posix()
1514
- for evidence_path in sorted(review_shadow_root.glob("review-*.json"))
1515
- )
1515
+ for evidence_path in sorted(review_shadow_root.glob("review-*.json")):
1516
+ try:
1517
+ payload = load_json_file(evidence_path)
1518
+ except (OSError, ValueError, json.JSONDecodeError):
1519
+ continue
1520
+ if isinstance(payload, dict):
1521
+ allowed.add(evidence_path.relative_to(context["target_root"]).as_posix())
1516
1522
  return allowed
1517
1523
 
1518
1524
 
@@ -2107,7 +2113,7 @@ def build_review_flow_payload(
2107
2113
 
2108
2114
  build_payload = checkpoint_payload("build", context)
2109
2115
  governance_surface = build_governance_surface(target_root)
2110
- surface_name = "review" if operation == "review" else "spec_review"
2116
+ surface_name = "review"
2111
2117
  repo_specific_requirements = repo_specific_requirements_payload(
2112
2118
  governance_surface.get("repo_interface"),
2113
2119
  target_root=target_root,
@@ -4237,6 +4243,15 @@ def carrier_refresh_payload(target_root: Path, output_relative: str, expected_it
4237
4243
  for action in actions:
4238
4244
  if action.get("status") == "block":
4239
4245
  missing_inputs.extend(str(message) for message in action.get("missing_inputs", []))
4246
+ if runtime_state.get("result") != "pass":
4247
+ refreshable_runtime_drift = {
4248
+ f"bootstrap runtime artifact `{action.get('path')}` sha256 drifted"
4249
+ for action in actions
4250
+ if action.get("kind") is None and action.get("status") == "refresh-needed" and action.get("path")
4251
+ }
4252
+ for message in runtime_state.get("missing_inputs", []):
4253
+ if str(message) not in refreshable_runtime_drift:
4254
+ missing_inputs.append(f"runtime-state: {message}")
4240
4255
 
4241
4256
  review_status: dict[str, Any] = {"status": "not_checked"}
4242
4257
  if not context_errors:
@@ -7229,6 +7229,39 @@ def check_adversarial_adoption_fixture(root: Path) -> list[Failure]:
7229
7229
  else:
7230
7230
  failures.append(Failure("adversarial-adoption", "review shadow carrier fixture could not load review artifact"))
7231
7231
 
7232
+ unreadable_review_shadow_target = base / "unreadable-review-shadow"
7233
+ shutil.copytree(baseline, unreadable_review_shadow_target)
7234
+ (unreadable_review_shadow_target / ".loom/shadow/review-repo.json").write_text("{not-json", encoding="utf-8")
7235
+ run_command(
7236
+ root,
7237
+ ["git", "add", "-f", ".loom/shadow/review-repo.json"],
7238
+ cwd=unreadable_review_shadow_target,
7239
+ timeout_seconds=30,
7240
+ )
7241
+ run_command(
7242
+ root,
7243
+ ["git", "commit", "-m", "corrupt review shadow evidence"],
7244
+ cwd=unreadable_review_shadow_target,
7245
+ timeout_seconds=30,
7246
+ )
7247
+ unreadable_payload, unreadable_error = load_command_json(
7248
+ root,
7249
+ [
7250
+ "python3",
7251
+ str(unreadable_review_shadow_target / ".loom/bin/loom_flow.py"),
7252
+ "checkpoint",
7253
+ "merge",
7254
+ "--target",
7255
+ str(unreadable_review_shadow_target),
7256
+ "--item",
7257
+ "INIT-0001",
7258
+ ],
7259
+ )
7260
+ if unreadable_error:
7261
+ failures.append(Failure("adversarial-adoption", f"unreadable review shadow evidence must fail closed without crashing: {unreadable_error}"))
7262
+ elif unreadable_payload.get("result") == "pass":
7263
+ failures.append(Failure("adversarial-adoption", "unreadable review shadow evidence must fail closed instead of passing"))
7264
+
7232
7265
  invalid_review_schema_target = base / "invalid-review-schema"
7233
7266
  shutil.copytree(baseline, invalid_review_schema_target)
7234
7267
  invalid_review_payload = load_json_file(invalid_review_schema_target / review_path)
@@ -1489,7 +1489,10 @@ def shadow_evidence_paths_for_sources(target_root: Path, source_paths: set[str])
1489
1489
  relative = evidence_path.relative_to(target_root).as_posix()
1490
1490
  if relative == ".loom/shadow/shadow-parity.json":
1491
1491
  continue
1492
- payload = load_json_file(evidence_path)
1492
+ try:
1493
+ payload = load_json_file(evidence_path)
1494
+ except (OSError, ValueError, json.JSONDecodeError):
1495
+ continue
1493
1496
  if not isinstance(payload, dict):
1494
1497
  continue
1495
1498
  source_files = payload.get("source_files")
@@ -1509,10 +1512,13 @@ def allowed_post_review_carrier_paths(context: dict[str, Any], *review_paths: st
1509
1512
  allowed.update(shadow_evidence_paths_for_sources(context["target_root"], set(review_paths)))
1510
1513
  review_shadow_root = context["target_root"] / ".loom/shadow"
1511
1514
  if review_shadow_root.exists():
1512
- allowed.update(
1513
- evidence_path.relative_to(context["target_root"]).as_posix()
1514
- for evidence_path in sorted(review_shadow_root.glob("review-*.json"))
1515
- )
1515
+ for evidence_path in sorted(review_shadow_root.glob("review-*.json")):
1516
+ try:
1517
+ payload = load_json_file(evidence_path)
1518
+ except (OSError, ValueError, json.JSONDecodeError):
1519
+ continue
1520
+ if isinstance(payload, dict):
1521
+ allowed.add(evidence_path.relative_to(context["target_root"]).as_posix())
1516
1522
  return allowed
1517
1523
 
1518
1524
 
@@ -2107,7 +2113,7 @@ def build_review_flow_payload(
2107
2113
 
2108
2114
  build_payload = checkpoint_payload("build", context)
2109
2115
  governance_surface = build_governance_surface(target_root)
2110
- surface_name = "review" if operation == "review" else "spec_review"
2116
+ surface_name = "review"
2111
2117
  repo_specific_requirements = repo_specific_requirements_payload(
2112
2118
  governance_surface.get("repo_interface"),
2113
2119
  target_root=target_root,
@@ -4237,6 +4243,15 @@ def carrier_refresh_payload(target_root: Path, output_relative: str, expected_it
4237
4243
  for action in actions:
4238
4244
  if action.get("status") == "block":
4239
4245
  missing_inputs.extend(str(message) for message in action.get("missing_inputs", []))
4246
+ if runtime_state.get("result") != "pass":
4247
+ refreshable_runtime_drift = {
4248
+ f"bootstrap runtime artifact `{action.get('path')}` sha256 drifted"
4249
+ for action in actions
4250
+ if action.get("kind") is None and action.get("status") == "refresh-needed" and action.get("path")
4251
+ }
4252
+ for message in runtime_state.get("missing_inputs", []):
4253
+ if str(message) not in refreshable_runtime_drift:
4254
+ missing_inputs.append(f"runtime-state: {message}")
4240
4255
 
4241
4256
  review_status: dict[str, Any] = {"status": "not_checked"}
4242
4257
  if not context_errors:
@@ -7229,6 +7229,39 @@ def check_adversarial_adoption_fixture(root: Path) -> list[Failure]:
7229
7229
  else:
7230
7230
  failures.append(Failure("adversarial-adoption", "review shadow carrier fixture could not load review artifact"))
7231
7231
 
7232
+ unreadable_review_shadow_target = base / "unreadable-review-shadow"
7233
+ shutil.copytree(baseline, unreadable_review_shadow_target)
7234
+ (unreadable_review_shadow_target / ".loom/shadow/review-repo.json").write_text("{not-json", encoding="utf-8")
7235
+ run_command(
7236
+ root,
7237
+ ["git", "add", "-f", ".loom/shadow/review-repo.json"],
7238
+ cwd=unreadable_review_shadow_target,
7239
+ timeout_seconds=30,
7240
+ )
7241
+ run_command(
7242
+ root,
7243
+ ["git", "commit", "-m", "corrupt review shadow evidence"],
7244
+ cwd=unreadable_review_shadow_target,
7245
+ timeout_seconds=30,
7246
+ )
7247
+ unreadable_payload, unreadable_error = load_command_json(
7248
+ root,
7249
+ [
7250
+ "python3",
7251
+ str(unreadable_review_shadow_target / ".loom/bin/loom_flow.py"),
7252
+ "checkpoint",
7253
+ "merge",
7254
+ "--target",
7255
+ str(unreadable_review_shadow_target),
7256
+ "--item",
7257
+ "INIT-0001",
7258
+ ],
7259
+ )
7260
+ if unreadable_error:
7261
+ failures.append(Failure("adversarial-adoption", f"unreadable review shadow evidence must fail closed without crashing: {unreadable_error}"))
7262
+ elif unreadable_payload.get("result") == "pass":
7263
+ failures.append(Failure("adversarial-adoption", "unreadable review shadow evidence must fail closed instead of passing"))
7264
+
7232
7265
  invalid_review_schema_target = base / "invalid-review-schema"
7233
7266
  shutil.copytree(baseline, invalid_review_schema_target)
7234
7267
  invalid_review_payload = load_json_file(invalid_review_schema_target / review_path)
@@ -1489,7 +1489,10 @@ def shadow_evidence_paths_for_sources(target_root: Path, source_paths: set[str])
1489
1489
  relative = evidence_path.relative_to(target_root).as_posix()
1490
1490
  if relative == ".loom/shadow/shadow-parity.json":
1491
1491
  continue
1492
- payload = load_json_file(evidence_path)
1492
+ try:
1493
+ payload = load_json_file(evidence_path)
1494
+ except (OSError, ValueError, json.JSONDecodeError):
1495
+ continue
1493
1496
  if not isinstance(payload, dict):
1494
1497
  continue
1495
1498
  source_files = payload.get("source_files")
@@ -1509,10 +1512,13 @@ def allowed_post_review_carrier_paths(context: dict[str, Any], *review_paths: st
1509
1512
  allowed.update(shadow_evidence_paths_for_sources(context["target_root"], set(review_paths)))
1510
1513
  review_shadow_root = context["target_root"] / ".loom/shadow"
1511
1514
  if review_shadow_root.exists():
1512
- allowed.update(
1513
- evidence_path.relative_to(context["target_root"]).as_posix()
1514
- for evidence_path in sorted(review_shadow_root.glob("review-*.json"))
1515
- )
1515
+ for evidence_path in sorted(review_shadow_root.glob("review-*.json")):
1516
+ try:
1517
+ payload = load_json_file(evidence_path)
1518
+ except (OSError, ValueError, json.JSONDecodeError):
1519
+ continue
1520
+ if isinstance(payload, dict):
1521
+ allowed.add(evidence_path.relative_to(context["target_root"]).as_posix())
1516
1522
  return allowed
1517
1523
 
1518
1524
 
@@ -2107,7 +2113,7 @@ def build_review_flow_payload(
2107
2113
 
2108
2114
  build_payload = checkpoint_payload("build", context)
2109
2115
  governance_surface = build_governance_surface(target_root)
2110
- surface_name = "review" if operation == "review" else "spec_review"
2116
+ surface_name = "review"
2111
2117
  repo_specific_requirements = repo_specific_requirements_payload(
2112
2118
  governance_surface.get("repo_interface"),
2113
2119
  target_root=target_root,
@@ -4237,6 +4243,15 @@ def carrier_refresh_payload(target_root: Path, output_relative: str, expected_it
4237
4243
  for action in actions:
4238
4244
  if action.get("status") == "block":
4239
4245
  missing_inputs.extend(str(message) for message in action.get("missing_inputs", []))
4246
+ if runtime_state.get("result") != "pass":
4247
+ refreshable_runtime_drift = {
4248
+ f"bootstrap runtime artifact `{action.get('path')}` sha256 drifted"
4249
+ for action in actions
4250
+ if action.get("kind") is None and action.get("status") == "refresh-needed" and action.get("path")
4251
+ }
4252
+ for message in runtime_state.get("missing_inputs", []):
4253
+ if str(message) not in refreshable_runtime_drift:
4254
+ missing_inputs.append(f"runtime-state: {message}")
4240
4255
 
4241
4256
  review_status: dict[str, Any] = {"status": "not_checked"}
4242
4257
  if not context_errors:
@@ -7229,6 +7229,39 @@ def check_adversarial_adoption_fixture(root: Path) -> list[Failure]:
7229
7229
  else:
7230
7230
  failures.append(Failure("adversarial-adoption", "review shadow carrier fixture could not load review artifact"))
7231
7231
 
7232
+ unreadable_review_shadow_target = base / "unreadable-review-shadow"
7233
+ shutil.copytree(baseline, unreadable_review_shadow_target)
7234
+ (unreadable_review_shadow_target / ".loom/shadow/review-repo.json").write_text("{not-json", encoding="utf-8")
7235
+ run_command(
7236
+ root,
7237
+ ["git", "add", "-f", ".loom/shadow/review-repo.json"],
7238
+ cwd=unreadable_review_shadow_target,
7239
+ timeout_seconds=30,
7240
+ )
7241
+ run_command(
7242
+ root,
7243
+ ["git", "commit", "-m", "corrupt review shadow evidence"],
7244
+ cwd=unreadable_review_shadow_target,
7245
+ timeout_seconds=30,
7246
+ )
7247
+ unreadable_payload, unreadable_error = load_command_json(
7248
+ root,
7249
+ [
7250
+ "python3",
7251
+ str(unreadable_review_shadow_target / ".loom/bin/loom_flow.py"),
7252
+ "checkpoint",
7253
+ "merge",
7254
+ "--target",
7255
+ str(unreadable_review_shadow_target),
7256
+ "--item",
7257
+ "INIT-0001",
7258
+ ],
7259
+ )
7260
+ if unreadable_error:
7261
+ failures.append(Failure("adversarial-adoption", f"unreadable review shadow evidence must fail closed without crashing: {unreadable_error}"))
7262
+ elif unreadable_payload.get("result") == "pass":
7263
+ failures.append(Failure("adversarial-adoption", "unreadable review shadow evidence must fail closed instead of passing"))
7264
+
7232
7265
  invalid_review_schema_target = base / "invalid-review-schema"
7233
7266
  shutil.copytree(baseline, invalid_review_schema_target)
7234
7267
  invalid_review_payload = load_json_file(invalid_review_schema_target / review_path)
@@ -1489,7 +1489,10 @@ def shadow_evidence_paths_for_sources(target_root: Path, source_paths: set[str])
1489
1489
  relative = evidence_path.relative_to(target_root).as_posix()
1490
1490
  if relative == ".loom/shadow/shadow-parity.json":
1491
1491
  continue
1492
- payload = load_json_file(evidence_path)
1492
+ try:
1493
+ payload = load_json_file(evidence_path)
1494
+ except (OSError, ValueError, json.JSONDecodeError):
1495
+ continue
1493
1496
  if not isinstance(payload, dict):
1494
1497
  continue
1495
1498
  source_files = payload.get("source_files")
@@ -1509,10 +1512,13 @@ def allowed_post_review_carrier_paths(context: dict[str, Any], *review_paths: st
1509
1512
  allowed.update(shadow_evidence_paths_for_sources(context["target_root"], set(review_paths)))
1510
1513
  review_shadow_root = context["target_root"] / ".loom/shadow"
1511
1514
  if review_shadow_root.exists():
1512
- allowed.update(
1513
- evidence_path.relative_to(context["target_root"]).as_posix()
1514
- for evidence_path in sorted(review_shadow_root.glob("review-*.json"))
1515
- )
1515
+ for evidence_path in sorted(review_shadow_root.glob("review-*.json")):
1516
+ try:
1517
+ payload = load_json_file(evidence_path)
1518
+ except (OSError, ValueError, json.JSONDecodeError):
1519
+ continue
1520
+ if isinstance(payload, dict):
1521
+ allowed.add(evidence_path.relative_to(context["target_root"]).as_posix())
1516
1522
  return allowed
1517
1523
 
1518
1524
 
@@ -2107,7 +2113,7 @@ def build_review_flow_payload(
2107
2113
 
2108
2114
  build_payload = checkpoint_payload("build", context)
2109
2115
  governance_surface = build_governance_surface(target_root)
2110
- surface_name = "review" if operation == "review" else "spec_review"
2116
+ surface_name = "review"
2111
2117
  repo_specific_requirements = repo_specific_requirements_payload(
2112
2118
  governance_surface.get("repo_interface"),
2113
2119
  target_root=target_root,
@@ -4237,6 +4243,15 @@ def carrier_refresh_payload(target_root: Path, output_relative: str, expected_it
4237
4243
  for action in actions:
4238
4244
  if action.get("status") == "block":
4239
4245
  missing_inputs.extend(str(message) for message in action.get("missing_inputs", []))
4246
+ if runtime_state.get("result") != "pass":
4247
+ refreshable_runtime_drift = {
4248
+ f"bootstrap runtime artifact `{action.get('path')}` sha256 drifted"
4249
+ for action in actions
4250
+ if action.get("kind") is None and action.get("status") == "refresh-needed" and action.get("path")
4251
+ }
4252
+ for message in runtime_state.get("missing_inputs", []):
4253
+ if str(message) not in refreshable_runtime_drift:
4254
+ missing_inputs.append(f"runtime-state: {message}")
4240
4255
 
4241
4256
  review_status: dict[str, Any] = {"status": "not_checked"}
4242
4257
  if not context_errors: