@mc-and-his-agents/loom-installer 0.1.10 → 0.1.11

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 +84 -0
  4. package/payload/plugin/loom/skills/shared/scripts/loom_flow.py +259 -0
  5. package/payload/skills/loom-adopt/.loom-runtime/shared/scripts/loom_check.py +84 -0
  6. package/payload/skills/loom-adopt/.loom-runtime/shared/scripts/loom_flow.py +259 -0
  7. package/payload/skills/loom-handoff/.loom-runtime/shared/scripts/loom_check.py +84 -0
  8. package/payload/skills/loom-handoff/.loom-runtime/shared/scripts/loom_flow.py +259 -0
  9. package/payload/skills/loom-init/.loom-runtime/shared/scripts/loom_check.py +84 -0
  10. package/payload/skills/loom-init/.loom-runtime/shared/scripts/loom_flow.py +259 -0
  11. package/payload/skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_check.py +84 -0
  12. package/payload/skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_flow.py +259 -0
  13. package/payload/skills/loom-pre-review/.loom-runtime/shared/scripts/loom_check.py +84 -0
  14. package/payload/skills/loom-pre-review/.loom-runtime/shared/scripts/loom_flow.py +259 -0
  15. package/payload/skills/loom-resume/.loom-runtime/shared/scripts/loom_check.py +84 -0
  16. package/payload/skills/loom-resume/.loom-runtime/shared/scripts/loom_flow.py +259 -0
  17. package/payload/skills/loom-retire/.loom-runtime/shared/scripts/loom_check.py +84 -0
  18. package/payload/skills/loom-retire/.loom-runtime/shared/scripts/loom_flow.py +259 -0
  19. package/payload/skills/loom-review/.loom-runtime/shared/scripts/loom_check.py +84 -0
  20. package/payload/skills/loom-review/.loom-runtime/shared/scripts/loom_flow.py +259 -0
  21. package/payload/skills/loom-spec-review/.loom-runtime/shared/scripts/loom_check.py +84 -0
  22. package/payload/skills/loom-spec-review/.loom-runtime/shared/scripts/loom_flow.py +259 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mc-and-his-agents/loom-installer",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
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": "6ce9eb5b541c9a4d374404c0dd0eac7450eb8572",
5
+ "source_commit": "4f6a4074d02557304018e9f063425f974d6a08cf",
6
6
  "source_ref": "main",
7
- "built_at": "2026-04-24T15:36:53+08:00",
7
+ "built_at": "2026-04-25T11:49:46+08:00",
8
8
  "runtime": {
9
9
  "python_minimum": "3.10",
10
10
  "python_recommended": "3.11+"
@@ -628,13 +628,13 @@
628
628
  },
629
629
  {
630
630
  "path": "plugin/loom/skills/shared/scripts/loom_check.py",
631
- "bytes": 249748,
632
- "sha256": "c109f05fa2f30c90d50980c349af5efcb16f5b493dc9ba03c923626a03ed48b2"
631
+ "bytes": 253737,
632
+ "sha256": "9ada27e8f88d01940e7f70112301f1c6c78ffa362f5159abba2a44ae445266ac"
633
633
  },
634
634
  {
635
635
  "path": "plugin/loom/skills/shared/scripts/loom_flow.py",
636
- "bytes": 248978,
637
- "sha256": "65f8bdc26f9269fa269783e885b7a834bc202361f7b0a40483d69040ee39f67f"
636
+ "bytes": 259768,
637
+ "sha256": "f11062993fe565160cdc2910e981e9a601d0fcafcbe16dd0072d63acf39d67b0"
638
638
  },
639
639
  {
640
640
  "path": "plugin/loom/skills/shared/scripts/loom_init.py",
@@ -1213,13 +1213,13 @@
1213
1213
  },
1214
1214
  {
1215
1215
  "path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_check.py",
1216
- "bytes": 249748,
1217
- "sha256": "c109f05fa2f30c90d50980c349af5efcb16f5b493dc9ba03c923626a03ed48b2"
1216
+ "bytes": 253737,
1217
+ "sha256": "9ada27e8f88d01940e7f70112301f1c6c78ffa362f5159abba2a44ae445266ac"
1218
1218
  },
1219
1219
  {
1220
1220
  "path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_flow.py",
1221
- "bytes": 248978,
1222
- "sha256": "65f8bdc26f9269fa269783e885b7a834bc202361f7b0a40483d69040ee39f67f"
1221
+ "bytes": 259768,
1222
+ "sha256": "f11062993fe565160cdc2910e981e9a601d0fcafcbe16dd0072d63acf39d67b0"
1223
1223
  },
1224
1224
  {
1225
1225
  "path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_init.py",
@@ -1828,13 +1828,13 @@
1828
1828
  },
1829
1829
  {
1830
1830
  "path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_check.py",
1831
- "bytes": 249748,
1832
- "sha256": "c109f05fa2f30c90d50980c349af5efcb16f5b493dc9ba03c923626a03ed48b2"
1831
+ "bytes": 253737,
1832
+ "sha256": "9ada27e8f88d01940e7f70112301f1c6c78ffa362f5159abba2a44ae445266ac"
1833
1833
  },
1834
1834
  {
1835
1835
  "path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_flow.py",
1836
- "bytes": 248978,
1837
- "sha256": "65f8bdc26f9269fa269783e885b7a834bc202361f7b0a40483d69040ee39f67f"
1836
+ "bytes": 259768,
1837
+ "sha256": "f11062993fe565160cdc2910e981e9a601d0fcafcbe16dd0072d63acf39d67b0"
1838
1838
  },
1839
1839
  {
1840
1840
  "path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_init.py",
@@ -2443,13 +2443,13 @@
2443
2443
  },
2444
2444
  {
2445
2445
  "path": "skills/loom-init/.loom-runtime/shared/scripts/loom_check.py",
2446
- "bytes": 249748,
2447
- "sha256": "c109f05fa2f30c90d50980c349af5efcb16f5b493dc9ba03c923626a03ed48b2"
2446
+ "bytes": 253737,
2447
+ "sha256": "9ada27e8f88d01940e7f70112301f1c6c78ffa362f5159abba2a44ae445266ac"
2448
2448
  },
2449
2449
  {
2450
2450
  "path": "skills/loom-init/.loom-runtime/shared/scripts/loom_flow.py",
2451
- "bytes": 248978,
2452
- "sha256": "65f8bdc26f9269fa269783e885b7a834bc202361f7b0a40483d69040ee39f67f"
2451
+ "bytes": 259768,
2452
+ "sha256": "f11062993fe565160cdc2910e981e9a601d0fcafcbe16dd0072d63acf39d67b0"
2453
2453
  },
2454
2454
  {
2455
2455
  "path": "skills/loom-init/.loom-runtime/shared/scripts/loom_init.py",
@@ -3063,13 +3063,13 @@
3063
3063
  },
3064
3064
  {
3065
3065
  "path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_check.py",
3066
- "bytes": 249748,
3067
- "sha256": "c109f05fa2f30c90d50980c349af5efcb16f5b493dc9ba03c923626a03ed48b2"
3066
+ "bytes": 253737,
3067
+ "sha256": "9ada27e8f88d01940e7f70112301f1c6c78ffa362f5159abba2a44ae445266ac"
3068
3068
  },
3069
3069
  {
3070
3070
  "path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_flow.py",
3071
- "bytes": 248978,
3072
- "sha256": "65f8bdc26f9269fa269783e885b7a834bc202361f7b0a40483d69040ee39f67f"
3071
+ "bytes": 259768,
3072
+ "sha256": "f11062993fe565160cdc2910e981e9a601d0fcafcbe16dd0072d63acf39d67b0"
3073
3073
  },
3074
3074
  {
3075
3075
  "path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_init.py",
@@ -3678,13 +3678,13 @@
3678
3678
  },
3679
3679
  {
3680
3680
  "path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_check.py",
3681
- "bytes": 249748,
3682
- "sha256": "c109f05fa2f30c90d50980c349af5efcb16f5b493dc9ba03c923626a03ed48b2"
3681
+ "bytes": 253737,
3682
+ "sha256": "9ada27e8f88d01940e7f70112301f1c6c78ffa362f5159abba2a44ae445266ac"
3683
3683
  },
3684
3684
  {
3685
3685
  "path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_flow.py",
3686
- "bytes": 248978,
3687
- "sha256": "65f8bdc26f9269fa269783e885b7a834bc202361f7b0a40483d69040ee39f67f"
3686
+ "bytes": 259768,
3687
+ "sha256": "f11062993fe565160cdc2910e981e9a601d0fcafcbe16dd0072d63acf39d67b0"
3688
3688
  },
3689
3689
  {
3690
3690
  "path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_init.py",
@@ -4293,13 +4293,13 @@
4293
4293
  },
4294
4294
  {
4295
4295
  "path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_check.py",
4296
- "bytes": 249748,
4297
- "sha256": "c109f05fa2f30c90d50980c349af5efcb16f5b493dc9ba03c923626a03ed48b2"
4296
+ "bytes": 253737,
4297
+ "sha256": "9ada27e8f88d01940e7f70112301f1c6c78ffa362f5159abba2a44ae445266ac"
4298
4298
  },
4299
4299
  {
4300
4300
  "path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_flow.py",
4301
- "bytes": 248978,
4302
- "sha256": "65f8bdc26f9269fa269783e885b7a834bc202361f7b0a40483d69040ee39f67f"
4301
+ "bytes": 259768,
4302
+ "sha256": "f11062993fe565160cdc2910e981e9a601d0fcafcbe16dd0072d63acf39d67b0"
4303
4303
  },
4304
4304
  {
4305
4305
  "path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_init.py",
@@ -4908,13 +4908,13 @@
4908
4908
  },
4909
4909
  {
4910
4910
  "path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_check.py",
4911
- "bytes": 249748,
4912
- "sha256": "c109f05fa2f30c90d50980c349af5efcb16f5b493dc9ba03c923626a03ed48b2"
4911
+ "bytes": 253737,
4912
+ "sha256": "9ada27e8f88d01940e7f70112301f1c6c78ffa362f5159abba2a44ae445266ac"
4913
4913
  },
4914
4914
  {
4915
4915
  "path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_flow.py",
4916
- "bytes": 248978,
4917
- "sha256": "65f8bdc26f9269fa269783e885b7a834bc202361f7b0a40483d69040ee39f67f"
4916
+ "bytes": 259768,
4917
+ "sha256": "f11062993fe565160cdc2910e981e9a601d0fcafcbe16dd0072d63acf39d67b0"
4918
4918
  },
4919
4919
  {
4920
4920
  "path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_init.py",
@@ -5523,13 +5523,13 @@
5523
5523
  },
5524
5524
  {
5525
5525
  "path": "skills/loom-review/.loom-runtime/shared/scripts/loom_check.py",
5526
- "bytes": 249748,
5527
- "sha256": "c109f05fa2f30c90d50980c349af5efcb16f5b493dc9ba03c923626a03ed48b2"
5526
+ "bytes": 253737,
5527
+ "sha256": "9ada27e8f88d01940e7f70112301f1c6c78ffa362f5159abba2a44ae445266ac"
5528
5528
  },
5529
5529
  {
5530
5530
  "path": "skills/loom-review/.loom-runtime/shared/scripts/loom_flow.py",
5531
- "bytes": 248978,
5532
- "sha256": "65f8bdc26f9269fa269783e885b7a834bc202361f7b0a40483d69040ee39f67f"
5531
+ "bytes": 259768,
5532
+ "sha256": "f11062993fe565160cdc2910e981e9a601d0fcafcbe16dd0072d63acf39d67b0"
5533
5533
  },
5534
5534
  {
5535
5535
  "path": "skills/loom-review/.loom-runtime/shared/scripts/loom_init.py",
@@ -6138,13 +6138,13 @@
6138
6138
  },
6139
6139
  {
6140
6140
  "path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_check.py",
6141
- "bytes": 249748,
6142
- "sha256": "c109f05fa2f30c90d50980c349af5efcb16f5b493dc9ba03c923626a03ed48b2"
6141
+ "bytes": 253737,
6142
+ "sha256": "9ada27e8f88d01940e7f70112301f1c6c78ffa362f5159abba2a44ae445266ac"
6143
6143
  },
6144
6144
  {
6145
6145
  "path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_flow.py",
6146
- "bytes": 248978,
6147
- "sha256": "65f8bdc26f9269fa269783e885b7a834bc202361f7b0a40483d69040ee39f67f"
6146
+ "bytes": 259768,
6147
+ "sha256": "f11062993fe565160cdc2910e981e9a601d0fcafcbe16dd0072d63acf39d67b0"
6148
6148
  },
6149
6149
  {
6150
6150
  "path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_init.py",
@@ -92,6 +92,7 @@ CORE_DOCS = (
92
92
  "docs/methodology/templates/pull-request.md",
93
93
  "docs/evidence/extraction-ledger.md",
94
94
  "docs/evidence/landing-map.md",
95
+ "docs/evidence/validations/validation-loom-core-runtime-parity.md",
95
96
  "docs/evidence/validations/validation-syvert-strong-governance-parity.md",
96
97
  "docs/adoption/rationale.md",
97
98
  "docs/adoption/routing-and-checkpoints.md",
@@ -1162,6 +1163,68 @@ def require_closeout_reconciliation_contract(
1162
1163
  failures.append(Failure(category, f"{context} must point blocked reconciliation drift to `manual-reconciliation`"))
1163
1164
 
1164
1165
 
1166
+ def require_runtime_parity_payload(
1167
+ failures: list[Failure],
1168
+ *,
1169
+ category: str,
1170
+ context: str,
1171
+ payload: object,
1172
+ ) -> None:
1173
+ if not isinstance(payload, dict):
1174
+ failures.append(Failure(category, f"{context} must be an object"))
1175
+ return
1176
+ if payload.get("command") != "runtime-parity":
1177
+ failures.append(Failure(category, f"{context} must report `command: runtime-parity`"))
1178
+ if payload.get("operation") != "validate":
1179
+ failures.append(Failure(category, f"{context} must report `operation: validate`"))
1180
+ if payload.get("schema_version") != "loom-runtime-parity/v1":
1181
+ failures.append(Failure(category, f"{context} schema_version must be `loom-runtime-parity/v1`"))
1182
+ if payload.get("result") not in {"pass", "block"}:
1183
+ failures.append(Failure(category, f"{context} result must be `pass` or `block`"))
1184
+ if not isinstance(payload.get("summary"), str) or not payload.get("summary"):
1185
+ failures.append(Failure(category, f"{context} must include a non-empty `summary`"))
1186
+ if not isinstance(payload.get("missing_inputs"), list):
1187
+ failures.append(Failure(category, f"{context} must include `missing_inputs`"))
1188
+ if payload.get("fallback_to") not in {None, "admission", "merge", "reconciliation-sync", "manual-runtime-reconciliation", "rebootstrap-runtime", "refresh-install", "loom-init"}:
1189
+ failures.append(Failure(category, f"{context} fallback_to must stay within the stable runtime parity contract"))
1190
+ require_runtime_state_payload(
1191
+ failures,
1192
+ category=category,
1193
+ context=context,
1194
+ payload=payload.get("runtime_state"),
1195
+ expected_scene="repo-local-demo",
1196
+ expected_carrier="repo-local-wrapper",
1197
+ allowed_results={"pass"},
1198
+ )
1199
+ checks = payload.get("checks")
1200
+ if not isinstance(checks, list):
1201
+ failures.append(Failure(category, f"{context} must include `checks` as a list"))
1202
+ return
1203
+ required_checks = {
1204
+ "work_item",
1205
+ "status_control_plane",
1206
+ "gate_chain",
1207
+ "controlled_merge_contract",
1208
+ "closeout_reconciliation",
1209
+ "shadow_parity_boundary",
1210
+ }
1211
+ check_names = {check.get("name") for check in checks if isinstance(check, dict)}
1212
+ if not required_checks.issubset(check_names):
1213
+ failures.append(Failure(category, f"{context} must cover the stable runtime parity check set"))
1214
+ for check in checks:
1215
+ if not isinstance(check, dict):
1216
+ failures.append(Failure(category, f"{context} checks must be JSON objects"))
1217
+ continue
1218
+ if check.get("result") not in {"pass", "block"}:
1219
+ failures.append(Failure(category, f"{context} check `{check.get('name')}` result must be `pass` or `block`"))
1220
+ if not isinstance(check.get("summary"), str) or not check.get("summary"):
1221
+ failures.append(Failure(category, f"{context} check `{check.get('name')}` must include non-empty `summary`"))
1222
+ if not isinstance(check.get("missing_inputs"), list):
1223
+ failures.append(Failure(category, f"{context} check `{check.get('name')}` must include `missing_inputs`"))
1224
+ if not isinstance(check.get("evidence"), dict):
1225
+ failures.append(Failure(category, f"{context} check `{check.get('name')}` must include `evidence`"))
1226
+
1227
+
1165
1228
  def require_review_record_contract(
1166
1229
  failures: list[Failure],
1167
1230
  *,
@@ -2124,6 +2187,20 @@ def check_daily_execution_cli(root: Path) -> list[Failure]:
2124
2187
  ["python3", "tools/loom_status.py", "--target", "examples/new-project", "--item", "INIT-0001"],
2125
2188
  {"pass", "block"},
2126
2189
  ),
2190
+ (
2191
+ "runtime-parity",
2192
+ [
2193
+ "python3",
2194
+ "tools/loom_flow.py",
2195
+ "runtime-parity",
2196
+ "validate",
2197
+ "--target",
2198
+ "examples/new-project",
2199
+ "--item",
2200
+ "INIT-0001",
2201
+ ],
2202
+ {"pass"},
2203
+ ),
2127
2204
  (
2128
2205
  "governance-profile-status",
2129
2206
  ["python3", "tools/loom_flow.py", "governance-profile", "status", "--target", "examples/new-project"],
@@ -2361,6 +2438,13 @@ def check_daily_execution_cli(root: Path) -> list[Failure]:
2361
2438
  context="`loom_status`",
2362
2439
  payload=payload,
2363
2440
  )
2441
+ if label == "runtime-parity":
2442
+ require_runtime_parity_payload(
2443
+ failures,
2444
+ category="daily-execution-cli",
2445
+ context="`runtime-parity validate`",
2446
+ payload=payload,
2447
+ )
2364
2448
  if label in {"governance-profile-status", "governance-profile-upgrade-plan"}:
2365
2449
  if payload.get("command") != "governance-profile":
2366
2450
  failures.append(Failure("daily-execution-cli", f"`{label}` must report `command: governance-profile`"))
@@ -277,6 +277,19 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
277
277
  help="Init-result path relative to the target root",
278
278
  )
279
279
 
280
+ runtime_parity = subparsers.add_parser(
281
+ "runtime-parity",
282
+ help="Validate Loom core strong-governance runtime parity without host-specific orchestration",
283
+ )
284
+ runtime_parity.add_argument("operation", choices=("validate",))
285
+ runtime_parity.add_argument("--target", required=True, help="Target repository root")
286
+ runtime_parity.add_argument("--item", help="Expected current item id")
287
+ runtime_parity.add_argument(
288
+ "--output",
289
+ default=".loom/bootstrap/init-result.json",
290
+ help="Init-result path relative to the target root",
291
+ )
292
+
280
293
  governance_profile = subparsers.add_parser(
281
294
  "governance-profile",
282
295
  help="Read Loom governance maturity and upgrade requirements",
@@ -4162,6 +4175,239 @@ def closeout_reconciliation_result(
4162
4175
  return None, None
4163
4176
 
4164
4177
 
4178
+ def runtime_parity_check(
4179
+ name: str,
4180
+ *,
4181
+ result: str,
4182
+ summary: str,
4183
+ evidence: dict[str, Any] | None = None,
4184
+ missing_inputs: list[str] | None = None,
4185
+ fallback_to: str | None = None,
4186
+ ) -> dict[str, Any]:
4187
+ return {
4188
+ "name": name,
4189
+ "result": result,
4190
+ "summary": summary,
4191
+ "evidence": evidence or {},
4192
+ "missing_inputs": missing_inputs or [],
4193
+ "fallback_to": fallback_to,
4194
+ }
4195
+
4196
+
4197
+ def runtime_parity_payload(
4198
+ *,
4199
+ target_root: Path,
4200
+ output_relative: str,
4201
+ expected_item: str | None,
4202
+ ) -> dict[str, Any]:
4203
+ runtime_state = runtime_state_payload(target_root)
4204
+ checks: list[dict[str, Any]] = []
4205
+ if runtime_state["result"] != "pass":
4206
+ checks.append(
4207
+ runtime_parity_check(
4208
+ "runtime_state",
4209
+ result="block",
4210
+ summary="runtime carrier is not consistent enough to prove runtime parity.",
4211
+ missing_inputs=list(runtime_state.get("missing_inputs", [])),
4212
+ fallback_to=runtime_state.get("fallback_to"),
4213
+ evidence={"runtime_state": runtime_state},
4214
+ )
4215
+ )
4216
+ return {
4217
+ "command": "runtime-parity",
4218
+ "operation": "validate",
4219
+ "schema_version": "loom-runtime-parity/v1",
4220
+ "result": "block",
4221
+ "summary": "Loom core runtime parity validation is blocked by runtime-state drift.",
4222
+ "missing_inputs": list(runtime_state.get("missing_inputs", [])),
4223
+ "fallback_to": runtime_state.get("fallback_to"),
4224
+ "runtime_state": runtime_state,
4225
+ "checks": checks,
4226
+ }
4227
+
4228
+ context, context_errors = load_context(target_root, output_relative, expected_item)
4229
+ governance_surface = build_governance_surface(target_root)
4230
+ control_plane = governance_surface.get("governance_control_plane")
4231
+ carrier_summary = governance_surface.get("carrier_summary")
4232
+
4233
+ if context_errors:
4234
+ checks.append(
4235
+ runtime_parity_check(
4236
+ "work_item",
4237
+ result="block",
4238
+ summary="runtime parity could not read the Work Item fact chain.",
4239
+ missing_inputs=[f"fact-chain: {message}" for message in context_errors],
4240
+ fallback_to="admission",
4241
+ )
4242
+ )
4243
+ else:
4244
+ checks.append(
4245
+ runtime_parity_check(
4246
+ "work_item",
4247
+ result="pass",
4248
+ summary="Work Item is readable as the single execution entry.",
4249
+ evidence={
4250
+ "item_id": context["item_id"],
4251
+ "work_item": context["report"]["fact_chain"]["entry_points"]["work_item"],
4252
+ "recovery_entry": context["report"]["fact_chain"]["entry_points"]["recovery_entry"],
4253
+ "status_surface": context["report"]["fact_chain"]["entry_points"]["status_surface"],
4254
+ },
4255
+ )
4256
+ )
4257
+
4258
+ if isinstance(control_plane, dict) and control_plane.get("schema_version") == "loom-governance-control/v1":
4259
+ checks.append(
4260
+ runtime_parity_check(
4261
+ "status_control_plane",
4262
+ result="pass",
4263
+ summary="governance control plane is available as a machine-readable runtime surface.",
4264
+ evidence={
4265
+ "schema_version": control_plane.get("schema_version"),
4266
+ "taxonomy": sorted((control_plane.get("taxonomy") or {}).keys())
4267
+ if isinstance(control_plane.get("taxonomy"), dict)
4268
+ else [],
4269
+ "maturity": (control_plane.get("maturity") or {}).get("current")
4270
+ if isinstance(control_plane.get("maturity"), dict)
4271
+ else None,
4272
+ },
4273
+ )
4274
+ )
4275
+ else:
4276
+ checks.append(
4277
+ runtime_parity_check(
4278
+ "status_control_plane",
4279
+ result="block",
4280
+ summary="governance control plane is missing or unreadable.",
4281
+ missing_inputs=["governance_control_plane"],
4282
+ fallback_to="admission",
4283
+ )
4284
+ )
4285
+
4286
+ expected_gate_order = [
4287
+ "work_item_admission",
4288
+ "spec_gate",
4289
+ "build_gate",
4290
+ "review_gate",
4291
+ "merge_gate",
4292
+ "github_controlled_merge",
4293
+ "closeout",
4294
+ ]
4295
+ gate_chain = control_plane.get("gate_chain") if isinstance(control_plane, dict) else None
4296
+ actual_gate_order = [entry.get("id") for entry in gate_chain if isinstance(entry, dict)] if isinstance(gate_chain, (list, tuple)) else []
4297
+ checks.append(
4298
+ runtime_parity_check(
4299
+ "gate_chain",
4300
+ result="pass" if actual_gate_order == expected_gate_order else "block",
4301
+ summary=(
4302
+ "strong governance gate chain is available in runtime order."
4303
+ if actual_gate_order == expected_gate_order
4304
+ else "strong governance gate chain does not match the runtime parity contract."
4305
+ ),
4306
+ evidence={"gate_order": actual_gate_order, "expected_gate_order": expected_gate_order},
4307
+ missing_inputs=[] if actual_gate_order == expected_gate_order else ["governance_control_plane.gate_chain"],
4308
+ fallback_to=None if actual_gate_order == expected_gate_order else "admission",
4309
+ )
4310
+ )
4311
+
4312
+ host_binding = control_plane.get("host_binding") if isinstance(control_plane, dict) else None
4313
+ required_objects = host_binding.get("required_objects") if isinstance(host_binding, dict) else None
4314
+ controlled_merge_ready = (
4315
+ isinstance(host_binding, dict)
4316
+ and isinstance(required_objects, dict)
4317
+ and {"implementation_pr", "merge_commit", "closeout"}.issubset(required_objects.keys())
4318
+ )
4319
+ checks.append(
4320
+ runtime_parity_check(
4321
+ "controlled_merge_contract",
4322
+ result="pass" if controlled_merge_ready else "block",
4323
+ summary=(
4324
+ "controlled merge contract exposes PR, merge commit, and closeout host-owned bindings."
4325
+ if controlled_merge_ready
4326
+ else "controlled merge contract is missing required host-owned bindings."
4327
+ ),
4328
+ evidence={
4329
+ "host_binding_result": host_binding.get("result") if isinstance(host_binding, dict) else None,
4330
+ "required_objects": sorted(required_objects.keys()) if isinstance(required_objects, dict) else [],
4331
+ },
4332
+ missing_inputs=[] if controlled_merge_ready else ["governance_control_plane.host_binding"],
4333
+ fallback_to=None if controlled_merge_ready else "merge",
4334
+ )
4335
+ )
4336
+
4337
+ closeout_gate = next((entry for entry in gate_chain or [] if isinstance(entry, dict) and entry.get("id") == "closeout"), {})
4338
+ closeout_requires = closeout_gate.get("requires") if isinstance(closeout_gate, dict) else None
4339
+ closeout_ready = isinstance(closeout_requires, (list, tuple)) and "reconciliation_audit" in closeout_requires
4340
+ checks.append(
4341
+ runtime_parity_check(
4342
+ "closeout_reconciliation",
4343
+ result="pass" if closeout_ready else "block",
4344
+ summary=(
4345
+ "closeout gate consumes reconciliation audit as a runtime prerequisite."
4346
+ if closeout_ready
4347
+ else "closeout gate does not expose reconciliation audit as a runtime prerequisite."
4348
+ ),
4349
+ evidence={
4350
+ "closeout_requires": closeout_requires if isinstance(closeout_requires, (list, tuple)) else [],
4351
+ "repo_interop_availability": (governance_surface.get("repo_interop") or {}).get("availability")
4352
+ if isinstance(governance_surface.get("repo_interop"), dict)
4353
+ else None,
4354
+ },
4355
+ missing_inputs=[] if closeout_ready else ["governance_control_plane.gate_chain.closeout"],
4356
+ fallback_to=None if closeout_ready else "reconciliation-sync",
4357
+ )
4358
+ )
4359
+
4360
+ checks.append(
4361
+ runtime_parity_check(
4362
+ "shadow_parity_boundary",
4363
+ result="pass",
4364
+ summary="shadow parity remains validation-only in Loom core runtime parity.",
4365
+ evidence={
4366
+ "default_result_contract": ["pass", "warn"],
4367
+ "blocking_default": False,
4368
+ "surfaces": list(SHADOW_PARITY_SURFACES),
4369
+ },
4370
+ )
4371
+ )
4372
+
4373
+ if not isinstance(carrier_summary, dict):
4374
+ checks.append(
4375
+ runtime_parity_check(
4376
+ "carrier_summary",
4377
+ result="block",
4378
+ summary="carrier summary is missing from governance surface.",
4379
+ missing_inputs=["governance_surface.carrier_summary"],
4380
+ fallback_to="admission",
4381
+ )
4382
+ )
4383
+
4384
+ missing_inputs: list[str] = []
4385
+ fallback_to: str | None = None
4386
+ for check in checks:
4387
+ if check["result"] == "block":
4388
+ fallback_to = fallback_to or check.get("fallback_to")
4389
+ for message in check.get("missing_inputs", []):
4390
+ if message not in missing_inputs:
4391
+ missing_inputs.append(message)
4392
+
4393
+ result = "pass" if not missing_inputs else "block"
4394
+ return {
4395
+ "command": "runtime-parity",
4396
+ "operation": "validate",
4397
+ "schema_version": "loom-runtime-parity/v1",
4398
+ "result": result,
4399
+ "summary": (
4400
+ "Loom core runtime parity is machine-readable across Work Item, status, gates, controlled merge, closeout, and shadow boundary."
4401
+ if result == "pass"
4402
+ else "Loom core runtime parity validation found missing or unreadable runtime surfaces."
4403
+ ),
4404
+ "missing_inputs": missing_inputs,
4405
+ "fallback_to": fallback_to,
4406
+ "runtime_state": runtime_state,
4407
+ "checks": checks,
4408
+ }
4409
+
4410
+
4165
4411
  def closeout_payload(
4166
4412
  *,
4167
4413
  target_root: Path,
@@ -5898,6 +6144,17 @@ def handle_shadow_parity(args: argparse.Namespace) -> int:
5898
6144
  )
5899
6145
 
5900
6146
 
6147
+ def handle_runtime_parity(args: argparse.Namespace) -> int:
6148
+ target_root = Path(args.target).expanduser().resolve()
6149
+ return emit(
6150
+ runtime_parity_payload(
6151
+ target_root=target_root,
6152
+ output_relative=args.output,
6153
+ expected_item=args.item,
6154
+ )
6155
+ )
6156
+
6157
+
5901
6158
  def main(argv: list[str] | None = None) -> int:
5902
6159
  args = parse_args(argv or sys.argv[1:])
5903
6160
  if args.command == "fact-chain":
@@ -5922,6 +6179,8 @@ def main(argv: list[str] | None = None) -> int:
5922
6179
  return handle_reconciliation(args)
5923
6180
  if args.command == "shadow-parity":
5924
6181
  return handle_shadow_parity(args)
6182
+ if args.command == "runtime-parity":
6183
+ return handle_runtime_parity(args)
5925
6184
  if args.command == "governance-profile":
5926
6185
  return handle_governance_profile(args)
5927
6186
  if args.command == "flow":