@mc-and-his-agents/loom-installer 0.1.43 → 0.1.45

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 (32) hide show
  1. package/package.json +1 -1
  2. package/payload/manifest.json +62 -62
  3. package/payload/plugin/loom/skills/shared/scripts/governance_surface.py +35 -1
  4. package/payload/plugin/loom/skills/shared/scripts/loom_check.py +40 -1
  5. package/payload/plugin/loom/skills/shared/scripts/loom_flow.py +24 -1
  6. package/payload/skills/loom-adopt/.loom-runtime/shared/scripts/governance_surface.py +35 -1
  7. package/payload/skills/loom-adopt/.loom-runtime/shared/scripts/loom_check.py +40 -1
  8. package/payload/skills/loom-adopt/.loom-runtime/shared/scripts/loom_flow.py +24 -1
  9. package/payload/skills/loom-handoff/.loom-runtime/shared/scripts/governance_surface.py +35 -1
  10. package/payload/skills/loom-handoff/.loom-runtime/shared/scripts/loom_check.py +40 -1
  11. package/payload/skills/loom-handoff/.loom-runtime/shared/scripts/loom_flow.py +24 -1
  12. package/payload/skills/loom-init/.loom-runtime/shared/scripts/governance_surface.py +35 -1
  13. package/payload/skills/loom-init/.loom-runtime/shared/scripts/loom_check.py +40 -1
  14. package/payload/skills/loom-init/.loom-runtime/shared/scripts/loom_flow.py +24 -1
  15. package/payload/skills/loom-merge-ready/.loom-runtime/shared/scripts/governance_surface.py +35 -1
  16. package/payload/skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_check.py +40 -1
  17. package/payload/skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_flow.py +24 -1
  18. package/payload/skills/loom-pre-review/.loom-runtime/shared/scripts/governance_surface.py +35 -1
  19. package/payload/skills/loom-pre-review/.loom-runtime/shared/scripts/loom_check.py +40 -1
  20. package/payload/skills/loom-pre-review/.loom-runtime/shared/scripts/loom_flow.py +24 -1
  21. package/payload/skills/loom-resume/.loom-runtime/shared/scripts/governance_surface.py +35 -1
  22. package/payload/skills/loom-resume/.loom-runtime/shared/scripts/loom_check.py +40 -1
  23. package/payload/skills/loom-resume/.loom-runtime/shared/scripts/loom_flow.py +24 -1
  24. package/payload/skills/loom-retire/.loom-runtime/shared/scripts/governance_surface.py +35 -1
  25. package/payload/skills/loom-retire/.loom-runtime/shared/scripts/loom_check.py +40 -1
  26. package/payload/skills/loom-retire/.loom-runtime/shared/scripts/loom_flow.py +24 -1
  27. package/payload/skills/loom-review/.loom-runtime/shared/scripts/governance_surface.py +35 -1
  28. package/payload/skills/loom-review/.loom-runtime/shared/scripts/loom_check.py +40 -1
  29. package/payload/skills/loom-review/.loom-runtime/shared/scripts/loom_flow.py +24 -1
  30. package/payload/skills/loom-spec-review/.loom-runtime/shared/scripts/governance_surface.py +35 -1
  31. package/payload/skills/loom-spec-review/.loom-runtime/shared/scripts/loom_check.py +40 -1
  32. package/payload/skills/loom-spec-review/.loom-runtime/shared/scripts/loom_flow.py +24 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mc-and-his-agents/loom-installer",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
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": "8c3ba44e07d467f15fee186221344f2bef12ed42",
5
+ "source_commit": "1818818f46f7e9791128276f699ef63e64ea8866",
6
6
  "source_ref": "main",
7
- "built_at": "2026-04-27T22:10:31+08:00",
7
+ "built_at": "2026-04-28T01:15:12+08:00",
8
8
  "runtime": {
9
9
  "python_minimum": "3.10",
10
10
  "python_recommended": "3.11+"
@@ -628,18 +628,18 @@
628
628
  },
629
629
  {
630
630
  "path": "plugin/loom/skills/shared/scripts/governance_surface.py",
631
- "bytes": 57136,
632
- "sha256": "02e6f66afcfad5b117ea9a801c7153466f24c6d5721da1546f286aa046c548f1"
631
+ "bytes": 58392,
632
+ "sha256": "543e10c205be62f5be78704fe9bf27e808fc07ffe035382e3d4dd78ffa8c80cc"
633
633
  },
634
634
  {
635
635
  "path": "plugin/loom/skills/shared/scripts/loom_check.py",
636
- "bytes": 356924,
637
- "sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
636
+ "bytes": 359221,
637
+ "sha256": "257e6bb1c2d2c6b6e31741d2183624ae1900d03dc1d2977d5bfd9837a97530ed"
638
638
  },
639
639
  {
640
640
  "path": "plugin/loom/skills/shared/scripts/loom_flow.py",
641
- "bytes": 340366,
642
- "sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
641
+ "bytes": 341321,
642
+ "sha256": "de815cf27623c6529d21ce47966f428d84d5c5f456792f2334e2391ea03831a8"
643
643
  },
644
644
  {
645
645
  "path": "plugin/loom/skills/shared/scripts/loom_init.py",
@@ -1218,18 +1218,18 @@
1218
1218
  },
1219
1219
  {
1220
1220
  "path": "skills/loom-adopt/.loom-runtime/shared/scripts/governance_surface.py",
1221
- "bytes": 57136,
1222
- "sha256": "02e6f66afcfad5b117ea9a801c7153466f24c6d5721da1546f286aa046c548f1"
1221
+ "bytes": 58392,
1222
+ "sha256": "543e10c205be62f5be78704fe9bf27e808fc07ffe035382e3d4dd78ffa8c80cc"
1223
1223
  },
1224
1224
  {
1225
1225
  "path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_check.py",
1226
- "bytes": 356924,
1227
- "sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
1226
+ "bytes": 359221,
1227
+ "sha256": "257e6bb1c2d2c6b6e31741d2183624ae1900d03dc1d2977d5bfd9837a97530ed"
1228
1228
  },
1229
1229
  {
1230
1230
  "path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_flow.py",
1231
- "bytes": 340366,
1232
- "sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
1231
+ "bytes": 341321,
1232
+ "sha256": "de815cf27623c6529d21ce47966f428d84d5c5f456792f2334e2391ea03831a8"
1233
1233
  },
1234
1234
  {
1235
1235
  "path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_init.py",
@@ -1838,18 +1838,18 @@
1838
1838
  },
1839
1839
  {
1840
1840
  "path": "skills/loom-handoff/.loom-runtime/shared/scripts/governance_surface.py",
1841
- "bytes": 57136,
1842
- "sha256": "02e6f66afcfad5b117ea9a801c7153466f24c6d5721da1546f286aa046c548f1"
1841
+ "bytes": 58392,
1842
+ "sha256": "543e10c205be62f5be78704fe9bf27e808fc07ffe035382e3d4dd78ffa8c80cc"
1843
1843
  },
1844
1844
  {
1845
1845
  "path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_check.py",
1846
- "bytes": 356924,
1847
- "sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
1846
+ "bytes": 359221,
1847
+ "sha256": "257e6bb1c2d2c6b6e31741d2183624ae1900d03dc1d2977d5bfd9837a97530ed"
1848
1848
  },
1849
1849
  {
1850
1850
  "path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_flow.py",
1851
- "bytes": 340366,
1852
- "sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
1851
+ "bytes": 341321,
1852
+ "sha256": "de815cf27623c6529d21ce47966f428d84d5c5f456792f2334e2391ea03831a8"
1853
1853
  },
1854
1854
  {
1855
1855
  "path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_init.py",
@@ -2458,18 +2458,18 @@
2458
2458
  },
2459
2459
  {
2460
2460
  "path": "skills/loom-init/.loom-runtime/shared/scripts/governance_surface.py",
2461
- "bytes": 57136,
2462
- "sha256": "02e6f66afcfad5b117ea9a801c7153466f24c6d5721da1546f286aa046c548f1"
2461
+ "bytes": 58392,
2462
+ "sha256": "543e10c205be62f5be78704fe9bf27e808fc07ffe035382e3d4dd78ffa8c80cc"
2463
2463
  },
2464
2464
  {
2465
2465
  "path": "skills/loom-init/.loom-runtime/shared/scripts/loom_check.py",
2466
- "bytes": 356924,
2467
- "sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
2466
+ "bytes": 359221,
2467
+ "sha256": "257e6bb1c2d2c6b6e31741d2183624ae1900d03dc1d2977d5bfd9837a97530ed"
2468
2468
  },
2469
2469
  {
2470
2470
  "path": "skills/loom-init/.loom-runtime/shared/scripts/loom_flow.py",
2471
- "bytes": 340366,
2472
- "sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
2471
+ "bytes": 341321,
2472
+ "sha256": "de815cf27623c6529d21ce47966f428d84d5c5f456792f2334e2391ea03831a8"
2473
2473
  },
2474
2474
  {
2475
2475
  "path": "skills/loom-init/.loom-runtime/shared/scripts/loom_init.py",
@@ -3083,18 +3083,18 @@
3083
3083
  },
3084
3084
  {
3085
3085
  "path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/governance_surface.py",
3086
- "bytes": 57136,
3087
- "sha256": "02e6f66afcfad5b117ea9a801c7153466f24c6d5721da1546f286aa046c548f1"
3086
+ "bytes": 58392,
3087
+ "sha256": "543e10c205be62f5be78704fe9bf27e808fc07ffe035382e3d4dd78ffa8c80cc"
3088
3088
  },
3089
3089
  {
3090
3090
  "path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_check.py",
3091
- "bytes": 356924,
3092
- "sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
3091
+ "bytes": 359221,
3092
+ "sha256": "257e6bb1c2d2c6b6e31741d2183624ae1900d03dc1d2977d5bfd9837a97530ed"
3093
3093
  },
3094
3094
  {
3095
3095
  "path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_flow.py",
3096
- "bytes": 340366,
3097
- "sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
3096
+ "bytes": 341321,
3097
+ "sha256": "de815cf27623c6529d21ce47966f428d84d5c5f456792f2334e2391ea03831a8"
3098
3098
  },
3099
3099
  {
3100
3100
  "path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_init.py",
@@ -3703,18 +3703,18 @@
3703
3703
  },
3704
3704
  {
3705
3705
  "path": "skills/loom-pre-review/.loom-runtime/shared/scripts/governance_surface.py",
3706
- "bytes": 57136,
3707
- "sha256": "02e6f66afcfad5b117ea9a801c7153466f24c6d5721da1546f286aa046c548f1"
3706
+ "bytes": 58392,
3707
+ "sha256": "543e10c205be62f5be78704fe9bf27e808fc07ffe035382e3d4dd78ffa8c80cc"
3708
3708
  },
3709
3709
  {
3710
3710
  "path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_check.py",
3711
- "bytes": 356924,
3712
- "sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
3711
+ "bytes": 359221,
3712
+ "sha256": "257e6bb1c2d2c6b6e31741d2183624ae1900d03dc1d2977d5bfd9837a97530ed"
3713
3713
  },
3714
3714
  {
3715
3715
  "path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_flow.py",
3716
- "bytes": 340366,
3717
- "sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
3716
+ "bytes": 341321,
3717
+ "sha256": "de815cf27623c6529d21ce47966f428d84d5c5f456792f2334e2391ea03831a8"
3718
3718
  },
3719
3719
  {
3720
3720
  "path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_init.py",
@@ -4323,18 +4323,18 @@
4323
4323
  },
4324
4324
  {
4325
4325
  "path": "skills/loom-resume/.loom-runtime/shared/scripts/governance_surface.py",
4326
- "bytes": 57136,
4327
- "sha256": "02e6f66afcfad5b117ea9a801c7153466f24c6d5721da1546f286aa046c548f1"
4326
+ "bytes": 58392,
4327
+ "sha256": "543e10c205be62f5be78704fe9bf27e808fc07ffe035382e3d4dd78ffa8c80cc"
4328
4328
  },
4329
4329
  {
4330
4330
  "path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_check.py",
4331
- "bytes": 356924,
4332
- "sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
4331
+ "bytes": 359221,
4332
+ "sha256": "257e6bb1c2d2c6b6e31741d2183624ae1900d03dc1d2977d5bfd9837a97530ed"
4333
4333
  },
4334
4334
  {
4335
4335
  "path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_flow.py",
4336
- "bytes": 340366,
4337
- "sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
4336
+ "bytes": 341321,
4337
+ "sha256": "de815cf27623c6529d21ce47966f428d84d5c5f456792f2334e2391ea03831a8"
4338
4338
  },
4339
4339
  {
4340
4340
  "path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_init.py",
@@ -4943,18 +4943,18 @@
4943
4943
  },
4944
4944
  {
4945
4945
  "path": "skills/loom-retire/.loom-runtime/shared/scripts/governance_surface.py",
4946
- "bytes": 57136,
4947
- "sha256": "02e6f66afcfad5b117ea9a801c7153466f24c6d5721da1546f286aa046c548f1"
4946
+ "bytes": 58392,
4947
+ "sha256": "543e10c205be62f5be78704fe9bf27e808fc07ffe035382e3d4dd78ffa8c80cc"
4948
4948
  },
4949
4949
  {
4950
4950
  "path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_check.py",
4951
- "bytes": 356924,
4952
- "sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
4951
+ "bytes": 359221,
4952
+ "sha256": "257e6bb1c2d2c6b6e31741d2183624ae1900d03dc1d2977d5bfd9837a97530ed"
4953
4953
  },
4954
4954
  {
4955
4955
  "path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_flow.py",
4956
- "bytes": 340366,
4957
- "sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
4956
+ "bytes": 341321,
4957
+ "sha256": "de815cf27623c6529d21ce47966f428d84d5c5f456792f2334e2391ea03831a8"
4958
4958
  },
4959
4959
  {
4960
4960
  "path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_init.py",
@@ -5563,18 +5563,18 @@
5563
5563
  },
5564
5564
  {
5565
5565
  "path": "skills/loom-review/.loom-runtime/shared/scripts/governance_surface.py",
5566
- "bytes": 57136,
5567
- "sha256": "02e6f66afcfad5b117ea9a801c7153466f24c6d5721da1546f286aa046c548f1"
5566
+ "bytes": 58392,
5567
+ "sha256": "543e10c205be62f5be78704fe9bf27e808fc07ffe035382e3d4dd78ffa8c80cc"
5568
5568
  },
5569
5569
  {
5570
5570
  "path": "skills/loom-review/.loom-runtime/shared/scripts/loom_check.py",
5571
- "bytes": 356924,
5572
- "sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
5571
+ "bytes": 359221,
5572
+ "sha256": "257e6bb1c2d2c6b6e31741d2183624ae1900d03dc1d2977d5bfd9837a97530ed"
5573
5573
  },
5574
5574
  {
5575
5575
  "path": "skills/loom-review/.loom-runtime/shared/scripts/loom_flow.py",
5576
- "bytes": 340366,
5577
- "sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
5576
+ "bytes": 341321,
5577
+ "sha256": "de815cf27623c6529d21ce47966f428d84d5c5f456792f2334e2391ea03831a8"
5578
5578
  },
5579
5579
  {
5580
5580
  "path": "skills/loom-review/.loom-runtime/shared/scripts/loom_init.py",
@@ -6183,18 +6183,18 @@
6183
6183
  },
6184
6184
  {
6185
6185
  "path": "skills/loom-spec-review/.loom-runtime/shared/scripts/governance_surface.py",
6186
- "bytes": 57136,
6187
- "sha256": "02e6f66afcfad5b117ea9a801c7153466f24c6d5721da1546f286aa046c548f1"
6186
+ "bytes": 58392,
6187
+ "sha256": "543e10c205be62f5be78704fe9bf27e808fc07ffe035382e3d4dd78ffa8c80cc"
6188
6188
  },
6189
6189
  {
6190
6190
  "path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_check.py",
6191
- "bytes": 356924,
6192
- "sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
6191
+ "bytes": 359221,
6192
+ "sha256": "257e6bb1c2d2c6b6e31741d2183624ae1900d03dc1d2977d5bfd9837a97530ed"
6193
6193
  },
6194
6194
  {
6195
6195
  "path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_flow.py",
6196
- "bytes": 340366,
6197
- "sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
6196
+ "bytes": 341321,
6197
+ "sha256": "de815cf27623c6529d21ce47966f428d84d5c5f456792f2334e2391ea03831a8"
6198
6198
  },
6199
6199
  {
6200
6200
  "path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_init.py",
@@ -1004,6 +1004,35 @@ def active_entry_points(root: Path) -> dict[str, str]:
1004
1004
  return active
1005
1005
 
1006
1006
 
1007
+ def bootstrap_host_binding_branch(root: Path) -> str:
1008
+ init_result = root / ".loom/bootstrap/init-result.json"
1009
+ try:
1010
+ payload = json.loads(init_result.read_text(encoding="utf-8"))
1011
+ except (OSError, json.JSONDecodeError):
1012
+ return ""
1013
+ if not isinstance(payload, dict):
1014
+ return ""
1015
+ governance_surface = payload.get("governance_surface")
1016
+ if not isinstance(governance_surface, dict):
1017
+ return ""
1018
+ control_plane = governance_surface.get("governance_control_plane")
1019
+ if not isinstance(control_plane, dict):
1020
+ return ""
1021
+ host_binding = control_plane.get("host_binding")
1022
+ if not isinstance(host_binding, dict):
1023
+ return ""
1024
+ required_objects = host_binding.get("required_objects")
1025
+ if not isinstance(required_objects, dict):
1026
+ return ""
1027
+ branch = required_objects.get("branch")
1028
+ if not isinstance(branch, dict):
1029
+ return ""
1030
+ locator = branch.get("locator")
1031
+ if isinstance(locator, str) and locator.strip() and locator != "unknown":
1032
+ return locator.strip()
1033
+ return ""
1034
+
1035
+
1007
1036
  def detect_carrier_summary(root: Path, *, repository_mode: str, planning_mode: bool) -> dict[str, dict[str, str]]:
1008
1037
  active = active_entry_points(root)
1009
1038
  active_item_id = active.get("current_item_id") or "INIT-0001"
@@ -1140,6 +1169,11 @@ def detect_host_binding_surface(
1140
1169
  ) -> dict[str, Any]:
1141
1170
  branch_result = run_process(["git", "branch", "--show-current"], root)
1142
1171
  branch = branch_result.stdout.strip() if branch_result.returncode == 0 else ""
1172
+ branch_authority = "git"
1173
+ if not branch:
1174
+ branch = bootstrap_host_binding_branch(root)
1175
+ if branch:
1176
+ branch_authority = "bootstrap host binding"
1143
1177
  worktree_result = run_process(["git", "rev-parse", "--show-toplevel"], root)
1144
1178
  worktree = worktree_result.stdout.strip() if worktree_result.returncode == 0 else ""
1145
1179
  default_branch = github_control_plane.get("default_branch")
@@ -1152,7 +1186,7 @@ def detect_host_binding_surface(
1152
1186
  "branch": {
1153
1187
  "status": "present" if branch else "missing",
1154
1188
  "locator": branch or "unknown",
1155
- "authority": "git",
1189
+ "authority": branch_authority,
1156
1190
  },
1157
1191
  "worktree": {
1158
1192
  "status": "present" if worktree else "missing",
@@ -21,7 +21,7 @@ import governance_surface as governance_surface_module
21
21
  import loom_flow as loom_flow_module
22
22
  import runtime_state as runtime_state_module
23
23
  from governance_surface import build_governance_surface
24
- from loom_flow import repo_specific_requirements_payload, review_head_binding
24
+ from loom_flow import allowed_post_review_carrier_paths, repo_specific_requirements_payload, review_head_binding
25
25
  from runtime_paths import repo_local_root
26
26
 
27
27
  TOP_LEVEL_DIRS = (
@@ -7149,6 +7149,45 @@ def check_adversarial_adoption_fixture(root: Path) -> list[Failure]:
7149
7149
  assert_broken_shadow_evidence("hash-drift", drift_source_hash)
7150
7150
  assert_broken_shadow_evidence("undeclared", undeclared_shadow_evidence, expect_validation_warn=False)
7151
7151
 
7152
+ review_shadow_target = base / "review-shadow-carrier"
7153
+ shutil.copytree(baseline, review_shadow_target)
7154
+ reviewed_head = run_command(root, ["git", "rev-parse", "HEAD"], cwd=review_shadow_target, timeout_seconds=30).stdout.strip()
7155
+ review_path = ".loom/reviews/INIT-0001.json"
7156
+ review_payload = load_json_file(review_shadow_target / review_path)
7157
+ if isinstance(review_payload, dict):
7158
+ review_payload["summary"] = "Carrier-only review artifact refresh."
7159
+ write_json(review_shadow_target / review_path, review_payload)
7160
+ write_json(
7161
+ review_shadow_target / ".loom/shadow/review-loom.json",
7162
+ {
7163
+ "decision": "allow",
7164
+ "source_files": [review_path],
7165
+ "source_sha256": {review_path: sha256_file(review_shadow_target / review_path)},
7166
+ },
7167
+ )
7168
+ run_command(root, ["git", "add", "-f", review_path, ".loom/shadow/review-loom.json"], cwd=review_shadow_target, timeout_seconds=30)
7169
+ run_command(root, ["git", "commit", "-m", "refresh review carrier evidence"], cwd=review_shadow_target, timeout_seconds=30)
7170
+ carrier_context = {
7171
+ "target_root": review_shadow_target,
7172
+ "report": {
7173
+ "fact_chain": {
7174
+ "entry_points": {
7175
+ "recovery_entry": ".loom/progress/INIT-0001.md",
7176
+ "status_surface": ".loom/status/current.md",
7177
+ }
7178
+ }
7179
+ },
7180
+ }
7181
+ binding_payload, binding_errors = review_head_binding(
7182
+ review_shadow_target,
7183
+ reviewed_head=reviewed_head,
7184
+ allowed_paths=allowed_post_review_carrier_paths(carrier_context, review_path),
7185
+ )
7186
+ if binding_errors or binding_payload.get("status") != "carrier-only":
7187
+ failures.append(Failure("adversarial-adoption", "review shadow evidence tied to a review artifact must be carrier-only after review refresh"))
7188
+ else:
7189
+ failures.append(Failure("adversarial-adoption", "review shadow carrier fixture could not load review artifact"))
7190
+
7152
7191
  head_before_drift = run_command(root, ["git", "rev-parse", "HEAD"], cwd=baseline, timeout_seconds=30).stdout.strip()
7153
7192
  (baseline / "implementation-drift.txt").write_text("changed after review\n", encoding="utf-8")
7154
7193
  run_command(root, ["git", "add", "implementation-drift.txt"], cwd=baseline, timeout_seconds=30)
@@ -1479,12 +1479,35 @@ def default_spec_review_path(item_id: str) -> str:
1479
1479
  return f".loom/reviews/{item_id}.spec.json"
1480
1480
 
1481
1481
 
1482
+ def shadow_evidence_paths_for_sources(target_root: Path, source_paths: set[str]) -> set[str]:
1483
+ shadow_root = target_root / ".loom/shadow"
1484
+ if not shadow_root.exists():
1485
+ return set()
1486
+
1487
+ evidence_paths: set[str] = set()
1488
+ for evidence_path in sorted(shadow_root.glob("*.json")):
1489
+ relative = evidence_path.relative_to(target_root).as_posix()
1490
+ if relative == ".loom/shadow/shadow-parity.json":
1491
+ continue
1492
+ payload = load_json_file(evidence_path)
1493
+ if not isinstance(payload, dict):
1494
+ continue
1495
+ source_files = payload.get("source_files")
1496
+ if not isinstance(source_files, list):
1497
+ continue
1498
+ if any(isinstance(source, str) and source in source_paths for source in source_files):
1499
+ evidence_paths.add(relative)
1500
+ return evidence_paths
1501
+
1502
+
1482
1503
  def allowed_post_review_carrier_paths(context: dict[str, Any], *review_paths: str) -> set[str]:
1483
- return {
1504
+ allowed = {
1484
1505
  *review_paths,
1485
1506
  str(context["report"]["fact_chain"]["entry_points"]["recovery_entry"]),
1486
1507
  str(context["report"]["fact_chain"]["entry_points"]["status_surface"]),
1487
1508
  }
1509
+ allowed.update(shadow_evidence_paths_for_sources(context["target_root"], set(review_paths)))
1510
+ return allowed
1488
1511
 
1489
1512
 
1490
1513
  def formal_spec_path(context: dict[str, Any]) -> str | None:
@@ -1004,6 +1004,35 @@ def active_entry_points(root: Path) -> dict[str, str]:
1004
1004
  return active
1005
1005
 
1006
1006
 
1007
+ def bootstrap_host_binding_branch(root: Path) -> str:
1008
+ init_result = root / ".loom/bootstrap/init-result.json"
1009
+ try:
1010
+ payload = json.loads(init_result.read_text(encoding="utf-8"))
1011
+ except (OSError, json.JSONDecodeError):
1012
+ return ""
1013
+ if not isinstance(payload, dict):
1014
+ return ""
1015
+ governance_surface = payload.get("governance_surface")
1016
+ if not isinstance(governance_surface, dict):
1017
+ return ""
1018
+ control_plane = governance_surface.get("governance_control_plane")
1019
+ if not isinstance(control_plane, dict):
1020
+ return ""
1021
+ host_binding = control_plane.get("host_binding")
1022
+ if not isinstance(host_binding, dict):
1023
+ return ""
1024
+ required_objects = host_binding.get("required_objects")
1025
+ if not isinstance(required_objects, dict):
1026
+ return ""
1027
+ branch = required_objects.get("branch")
1028
+ if not isinstance(branch, dict):
1029
+ return ""
1030
+ locator = branch.get("locator")
1031
+ if isinstance(locator, str) and locator.strip() and locator != "unknown":
1032
+ return locator.strip()
1033
+ return ""
1034
+
1035
+
1007
1036
  def detect_carrier_summary(root: Path, *, repository_mode: str, planning_mode: bool) -> dict[str, dict[str, str]]:
1008
1037
  active = active_entry_points(root)
1009
1038
  active_item_id = active.get("current_item_id") or "INIT-0001"
@@ -1140,6 +1169,11 @@ def detect_host_binding_surface(
1140
1169
  ) -> dict[str, Any]:
1141
1170
  branch_result = run_process(["git", "branch", "--show-current"], root)
1142
1171
  branch = branch_result.stdout.strip() if branch_result.returncode == 0 else ""
1172
+ branch_authority = "git"
1173
+ if not branch:
1174
+ branch = bootstrap_host_binding_branch(root)
1175
+ if branch:
1176
+ branch_authority = "bootstrap host binding"
1143
1177
  worktree_result = run_process(["git", "rev-parse", "--show-toplevel"], root)
1144
1178
  worktree = worktree_result.stdout.strip() if worktree_result.returncode == 0 else ""
1145
1179
  default_branch = github_control_plane.get("default_branch")
@@ -1152,7 +1186,7 @@ def detect_host_binding_surface(
1152
1186
  "branch": {
1153
1187
  "status": "present" if branch else "missing",
1154
1188
  "locator": branch or "unknown",
1155
- "authority": "git",
1189
+ "authority": branch_authority,
1156
1190
  },
1157
1191
  "worktree": {
1158
1192
  "status": "present" if worktree else "missing",
@@ -21,7 +21,7 @@ import governance_surface as governance_surface_module
21
21
  import loom_flow as loom_flow_module
22
22
  import runtime_state as runtime_state_module
23
23
  from governance_surface import build_governance_surface
24
- from loom_flow import repo_specific_requirements_payload, review_head_binding
24
+ from loom_flow import allowed_post_review_carrier_paths, repo_specific_requirements_payload, review_head_binding
25
25
  from runtime_paths import repo_local_root
26
26
 
27
27
  TOP_LEVEL_DIRS = (
@@ -7149,6 +7149,45 @@ def check_adversarial_adoption_fixture(root: Path) -> list[Failure]:
7149
7149
  assert_broken_shadow_evidence("hash-drift", drift_source_hash)
7150
7150
  assert_broken_shadow_evidence("undeclared", undeclared_shadow_evidence, expect_validation_warn=False)
7151
7151
 
7152
+ review_shadow_target = base / "review-shadow-carrier"
7153
+ shutil.copytree(baseline, review_shadow_target)
7154
+ reviewed_head = run_command(root, ["git", "rev-parse", "HEAD"], cwd=review_shadow_target, timeout_seconds=30).stdout.strip()
7155
+ review_path = ".loom/reviews/INIT-0001.json"
7156
+ review_payload = load_json_file(review_shadow_target / review_path)
7157
+ if isinstance(review_payload, dict):
7158
+ review_payload["summary"] = "Carrier-only review artifact refresh."
7159
+ write_json(review_shadow_target / review_path, review_payload)
7160
+ write_json(
7161
+ review_shadow_target / ".loom/shadow/review-loom.json",
7162
+ {
7163
+ "decision": "allow",
7164
+ "source_files": [review_path],
7165
+ "source_sha256": {review_path: sha256_file(review_shadow_target / review_path)},
7166
+ },
7167
+ )
7168
+ run_command(root, ["git", "add", "-f", review_path, ".loom/shadow/review-loom.json"], cwd=review_shadow_target, timeout_seconds=30)
7169
+ run_command(root, ["git", "commit", "-m", "refresh review carrier evidence"], cwd=review_shadow_target, timeout_seconds=30)
7170
+ carrier_context = {
7171
+ "target_root": review_shadow_target,
7172
+ "report": {
7173
+ "fact_chain": {
7174
+ "entry_points": {
7175
+ "recovery_entry": ".loom/progress/INIT-0001.md",
7176
+ "status_surface": ".loom/status/current.md",
7177
+ }
7178
+ }
7179
+ },
7180
+ }
7181
+ binding_payload, binding_errors = review_head_binding(
7182
+ review_shadow_target,
7183
+ reviewed_head=reviewed_head,
7184
+ allowed_paths=allowed_post_review_carrier_paths(carrier_context, review_path),
7185
+ )
7186
+ if binding_errors or binding_payload.get("status") != "carrier-only":
7187
+ failures.append(Failure("adversarial-adoption", "review shadow evidence tied to a review artifact must be carrier-only after review refresh"))
7188
+ else:
7189
+ failures.append(Failure("adversarial-adoption", "review shadow carrier fixture could not load review artifact"))
7190
+
7152
7191
  head_before_drift = run_command(root, ["git", "rev-parse", "HEAD"], cwd=baseline, timeout_seconds=30).stdout.strip()
7153
7192
  (baseline / "implementation-drift.txt").write_text("changed after review\n", encoding="utf-8")
7154
7193
  run_command(root, ["git", "add", "implementation-drift.txt"], cwd=baseline, timeout_seconds=30)
@@ -1479,12 +1479,35 @@ def default_spec_review_path(item_id: str) -> str:
1479
1479
  return f".loom/reviews/{item_id}.spec.json"
1480
1480
 
1481
1481
 
1482
+ def shadow_evidence_paths_for_sources(target_root: Path, source_paths: set[str]) -> set[str]:
1483
+ shadow_root = target_root / ".loom/shadow"
1484
+ if not shadow_root.exists():
1485
+ return set()
1486
+
1487
+ evidence_paths: set[str] = set()
1488
+ for evidence_path in sorted(shadow_root.glob("*.json")):
1489
+ relative = evidence_path.relative_to(target_root).as_posix()
1490
+ if relative == ".loom/shadow/shadow-parity.json":
1491
+ continue
1492
+ payload = load_json_file(evidence_path)
1493
+ if not isinstance(payload, dict):
1494
+ continue
1495
+ source_files = payload.get("source_files")
1496
+ if not isinstance(source_files, list):
1497
+ continue
1498
+ if any(isinstance(source, str) and source in source_paths for source in source_files):
1499
+ evidence_paths.add(relative)
1500
+ return evidence_paths
1501
+
1502
+
1482
1503
  def allowed_post_review_carrier_paths(context: dict[str, Any], *review_paths: str) -> set[str]:
1483
- return {
1504
+ allowed = {
1484
1505
  *review_paths,
1485
1506
  str(context["report"]["fact_chain"]["entry_points"]["recovery_entry"]),
1486
1507
  str(context["report"]["fact_chain"]["entry_points"]["status_surface"]),
1487
1508
  }
1509
+ allowed.update(shadow_evidence_paths_for_sources(context["target_root"], set(review_paths)))
1510
+ return allowed
1488
1511
 
1489
1512
 
1490
1513
  def formal_spec_path(context: dict[str, Any]) -> str | None:
@@ -1004,6 +1004,35 @@ def active_entry_points(root: Path) -> dict[str, str]:
1004
1004
  return active
1005
1005
 
1006
1006
 
1007
+ def bootstrap_host_binding_branch(root: Path) -> str:
1008
+ init_result = root / ".loom/bootstrap/init-result.json"
1009
+ try:
1010
+ payload = json.loads(init_result.read_text(encoding="utf-8"))
1011
+ except (OSError, json.JSONDecodeError):
1012
+ return ""
1013
+ if not isinstance(payload, dict):
1014
+ return ""
1015
+ governance_surface = payload.get("governance_surface")
1016
+ if not isinstance(governance_surface, dict):
1017
+ return ""
1018
+ control_plane = governance_surface.get("governance_control_plane")
1019
+ if not isinstance(control_plane, dict):
1020
+ return ""
1021
+ host_binding = control_plane.get("host_binding")
1022
+ if not isinstance(host_binding, dict):
1023
+ return ""
1024
+ required_objects = host_binding.get("required_objects")
1025
+ if not isinstance(required_objects, dict):
1026
+ return ""
1027
+ branch = required_objects.get("branch")
1028
+ if not isinstance(branch, dict):
1029
+ return ""
1030
+ locator = branch.get("locator")
1031
+ if isinstance(locator, str) and locator.strip() and locator != "unknown":
1032
+ return locator.strip()
1033
+ return ""
1034
+
1035
+
1007
1036
  def detect_carrier_summary(root: Path, *, repository_mode: str, planning_mode: bool) -> dict[str, dict[str, str]]:
1008
1037
  active = active_entry_points(root)
1009
1038
  active_item_id = active.get("current_item_id") or "INIT-0001"
@@ -1140,6 +1169,11 @@ def detect_host_binding_surface(
1140
1169
  ) -> dict[str, Any]:
1141
1170
  branch_result = run_process(["git", "branch", "--show-current"], root)
1142
1171
  branch = branch_result.stdout.strip() if branch_result.returncode == 0 else ""
1172
+ branch_authority = "git"
1173
+ if not branch:
1174
+ branch = bootstrap_host_binding_branch(root)
1175
+ if branch:
1176
+ branch_authority = "bootstrap host binding"
1143
1177
  worktree_result = run_process(["git", "rev-parse", "--show-toplevel"], root)
1144
1178
  worktree = worktree_result.stdout.strip() if worktree_result.returncode == 0 else ""
1145
1179
  default_branch = github_control_plane.get("default_branch")
@@ -1152,7 +1186,7 @@ def detect_host_binding_surface(
1152
1186
  "branch": {
1153
1187
  "status": "present" if branch else "missing",
1154
1188
  "locator": branch or "unknown",
1155
- "authority": "git",
1189
+ "authority": branch_authority,
1156
1190
  },
1157
1191
  "worktree": {
1158
1192
  "status": "present" if worktree else "missing",