@mc-and-his-agents/loom-installer 0.1.14 → 0.1.15
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.
- package/package.json +1 -1
- package/payload/manifest.json +42 -42
- package/payload/plugin/loom/skills/shared/scripts/loom_check.py +10 -1
- package/payload/plugin/loom/skills/shared/scripts/loom_flow.py +106 -3
- package/payload/skills/loom-adopt/.loom-runtime/shared/scripts/loom_check.py +10 -1
- package/payload/skills/loom-adopt/.loom-runtime/shared/scripts/loom_flow.py +106 -3
- package/payload/skills/loom-handoff/.loom-runtime/shared/scripts/loom_check.py +10 -1
- package/payload/skills/loom-handoff/.loom-runtime/shared/scripts/loom_flow.py +106 -3
- package/payload/skills/loom-init/.loom-runtime/shared/scripts/loom_check.py +10 -1
- package/payload/skills/loom-init/.loom-runtime/shared/scripts/loom_flow.py +106 -3
- package/payload/skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_check.py +10 -1
- package/payload/skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_flow.py +106 -3
- package/payload/skills/loom-pre-review/.loom-runtime/shared/scripts/loom_check.py +10 -1
- package/payload/skills/loom-pre-review/.loom-runtime/shared/scripts/loom_flow.py +106 -3
- package/payload/skills/loom-resume/.loom-runtime/shared/scripts/loom_check.py +10 -1
- package/payload/skills/loom-resume/.loom-runtime/shared/scripts/loom_flow.py +106 -3
- package/payload/skills/loom-retire/.loom-runtime/shared/scripts/loom_check.py +10 -1
- package/payload/skills/loom-retire/.loom-runtime/shared/scripts/loom_flow.py +106 -3
- package/payload/skills/loom-review/.loom-runtime/shared/scripts/loom_check.py +10 -1
- package/payload/skills/loom-review/.loom-runtime/shared/scripts/loom_flow.py +106 -3
- package/payload/skills/loom-spec-review/.loom-runtime/shared/scripts/loom_check.py +10 -1
- package/payload/skills/loom-spec-review/.loom-runtime/shared/scripts/loom_flow.py +106 -3
package/package.json
CHANGED
package/payload/manifest.json
CHANGED
|
@@ -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": "
|
|
5
|
+
"source_commit": "3e2a33cf1cb7e519d321cb2617b874e285815c5b",
|
|
6
6
|
"source_ref": "main",
|
|
7
|
-
"built_at": "2026-04-
|
|
7
|
+
"built_at": "2026-04-25T13:01:36+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":
|
|
632
|
-
"sha256": "
|
|
631
|
+
"bytes": 265994,
|
|
632
|
+
"sha256": "77334371c586c5e7247e6b781758cbf656594d4d5b8cff33f16b967b43f7811c"
|
|
633
633
|
},
|
|
634
634
|
{
|
|
635
635
|
"path": "plugin/loom/skills/shared/scripts/loom_flow.py",
|
|
636
|
-
"bytes":
|
|
637
|
-
"sha256": "
|
|
636
|
+
"bytes": 279330,
|
|
637
|
+
"sha256": "ad6d6376a665cdfcd9de2316957e8f36e1ec087de59f29cebe6a6496ac0a555e"
|
|
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":
|
|
1217
|
-
"sha256": "
|
|
1216
|
+
"bytes": 265994,
|
|
1217
|
+
"sha256": "77334371c586c5e7247e6b781758cbf656594d4d5b8cff33f16b967b43f7811c"
|
|
1218
1218
|
},
|
|
1219
1219
|
{
|
|
1220
1220
|
"path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_flow.py",
|
|
1221
|
-
"bytes":
|
|
1222
|
-
"sha256": "
|
|
1221
|
+
"bytes": 279330,
|
|
1222
|
+
"sha256": "ad6d6376a665cdfcd9de2316957e8f36e1ec087de59f29cebe6a6496ac0a555e"
|
|
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":
|
|
1832
|
-
"sha256": "
|
|
1831
|
+
"bytes": 265994,
|
|
1832
|
+
"sha256": "77334371c586c5e7247e6b781758cbf656594d4d5b8cff33f16b967b43f7811c"
|
|
1833
1833
|
},
|
|
1834
1834
|
{
|
|
1835
1835
|
"path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_flow.py",
|
|
1836
|
-
"bytes":
|
|
1837
|
-
"sha256": "
|
|
1836
|
+
"bytes": 279330,
|
|
1837
|
+
"sha256": "ad6d6376a665cdfcd9de2316957e8f36e1ec087de59f29cebe6a6496ac0a555e"
|
|
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":
|
|
2447
|
-
"sha256": "
|
|
2446
|
+
"bytes": 265994,
|
|
2447
|
+
"sha256": "77334371c586c5e7247e6b781758cbf656594d4d5b8cff33f16b967b43f7811c"
|
|
2448
2448
|
},
|
|
2449
2449
|
{
|
|
2450
2450
|
"path": "skills/loom-init/.loom-runtime/shared/scripts/loom_flow.py",
|
|
2451
|
-
"bytes":
|
|
2452
|
-
"sha256": "
|
|
2451
|
+
"bytes": 279330,
|
|
2452
|
+
"sha256": "ad6d6376a665cdfcd9de2316957e8f36e1ec087de59f29cebe6a6496ac0a555e"
|
|
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":
|
|
3067
|
-
"sha256": "
|
|
3066
|
+
"bytes": 265994,
|
|
3067
|
+
"sha256": "77334371c586c5e7247e6b781758cbf656594d4d5b8cff33f16b967b43f7811c"
|
|
3068
3068
|
},
|
|
3069
3069
|
{
|
|
3070
3070
|
"path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_flow.py",
|
|
3071
|
-
"bytes":
|
|
3072
|
-
"sha256": "
|
|
3071
|
+
"bytes": 279330,
|
|
3072
|
+
"sha256": "ad6d6376a665cdfcd9de2316957e8f36e1ec087de59f29cebe6a6496ac0a555e"
|
|
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":
|
|
3682
|
-
"sha256": "
|
|
3681
|
+
"bytes": 265994,
|
|
3682
|
+
"sha256": "77334371c586c5e7247e6b781758cbf656594d4d5b8cff33f16b967b43f7811c"
|
|
3683
3683
|
},
|
|
3684
3684
|
{
|
|
3685
3685
|
"path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_flow.py",
|
|
3686
|
-
"bytes":
|
|
3687
|
-
"sha256": "
|
|
3686
|
+
"bytes": 279330,
|
|
3687
|
+
"sha256": "ad6d6376a665cdfcd9de2316957e8f36e1ec087de59f29cebe6a6496ac0a555e"
|
|
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":
|
|
4297
|
-
"sha256": "
|
|
4296
|
+
"bytes": 265994,
|
|
4297
|
+
"sha256": "77334371c586c5e7247e6b781758cbf656594d4d5b8cff33f16b967b43f7811c"
|
|
4298
4298
|
},
|
|
4299
4299
|
{
|
|
4300
4300
|
"path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_flow.py",
|
|
4301
|
-
"bytes":
|
|
4302
|
-
"sha256": "
|
|
4301
|
+
"bytes": 279330,
|
|
4302
|
+
"sha256": "ad6d6376a665cdfcd9de2316957e8f36e1ec087de59f29cebe6a6496ac0a555e"
|
|
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":
|
|
4912
|
-
"sha256": "
|
|
4911
|
+
"bytes": 265994,
|
|
4912
|
+
"sha256": "77334371c586c5e7247e6b781758cbf656594d4d5b8cff33f16b967b43f7811c"
|
|
4913
4913
|
},
|
|
4914
4914
|
{
|
|
4915
4915
|
"path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_flow.py",
|
|
4916
|
-
"bytes":
|
|
4917
|
-
"sha256": "
|
|
4916
|
+
"bytes": 279330,
|
|
4917
|
+
"sha256": "ad6d6376a665cdfcd9de2316957e8f36e1ec087de59f29cebe6a6496ac0a555e"
|
|
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":
|
|
5527
|
-
"sha256": "
|
|
5526
|
+
"bytes": 265994,
|
|
5527
|
+
"sha256": "77334371c586c5e7247e6b781758cbf656594d4d5b8cff33f16b967b43f7811c"
|
|
5528
5528
|
},
|
|
5529
5529
|
{
|
|
5530
5530
|
"path": "skills/loom-review/.loom-runtime/shared/scripts/loom_flow.py",
|
|
5531
|
-
"bytes":
|
|
5532
|
-
"sha256": "
|
|
5531
|
+
"bytes": 279330,
|
|
5532
|
+
"sha256": "ad6d6376a665cdfcd9de2316957e8f36e1ec087de59f29cebe6a6496ac0a555e"
|
|
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":
|
|
6142
|
-
"sha256": "
|
|
6141
|
+
"bytes": 265994,
|
|
6142
|
+
"sha256": "77334371c586c5e7247e6b781758cbf656594d4d5b8cff33f16b967b43f7811c"
|
|
6143
6143
|
},
|
|
6144
6144
|
{
|
|
6145
6145
|
"path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_flow.py",
|
|
6146
|
-
"bytes":
|
|
6147
|
-
"sha256": "
|
|
6146
|
+
"bytes": 279330,
|
|
6147
|
+
"sha256": "ad6d6376a665cdfcd9de2316957e8f36e1ec087de59f29cebe6a6496ac0a555e"
|
|
6148
6148
|
},
|
|
6149
6149
|
{
|
|
6150
6150
|
"path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_init.py",
|
|
@@ -94,6 +94,7 @@ CORE_DOCS = (
|
|
|
94
94
|
"docs/evidence/landing-map.md",
|
|
95
95
|
"docs/evidence/validations/validation-closeout-reconciliation-blocking-gate.md",
|
|
96
96
|
"docs/evidence/validations/validation-github-profile-binding-orchestration.md",
|
|
97
|
+
"docs/evidence/validations/validation-github-profile-drift-reconciliation.md",
|
|
97
98
|
"docs/evidence/validations/validation-loom-core-runtime-parity.md",
|
|
98
99
|
"docs/evidence/validations/validation-shadow-parity-blocking-gate.md",
|
|
99
100
|
"docs/evidence/validations/validation-syvert-strong-governance-parity.md",
|
|
@@ -1138,7 +1139,7 @@ def require_reconciliation_payload(
|
|
|
1138
1139
|
continue
|
|
1139
1140
|
if finding.get("category") not in {"drift", "gate_failure"}:
|
|
1140
1141
|
failures.append(Failure(category, f"{context} reconciliation finding category must stay within the stable taxonomy"))
|
|
1141
|
-
if finding.get("kind") not in {"merged_but_open", "absorbed_but_open", "parent_drift", "project_drift", "host_signal_drift"}:
|
|
1142
|
+
if finding.get("kind") not in {"merged_but_open", "absorbed_but_open", "parent_drift", "project_drift", "host_signal_drift", "binding_failure", "merge_signal_drift"}:
|
|
1142
1143
|
failures.append(Failure(category, f"{context} reconciliation finding kind must stay within the stable contract"))
|
|
1143
1144
|
if finding.get("severity") not in {"warn", "fix-needed", "block"}:
|
|
1144
1145
|
failures.append(Failure(category, f"{context} reconciliation finding severity must stay within the stable contract"))
|
|
@@ -1150,6 +1151,12 @@ def require_reconciliation_payload(
|
|
|
1150
1151
|
failures.append(Failure(category, f"{context} reconciliation findings must include `evidence`"))
|
|
1151
1152
|
if not isinstance(finding.get("recommended_action"), str) or not finding.get("recommended_action"):
|
|
1152
1153
|
failures.append(Failure(category, f"{context} reconciliation findings must include non-empty `recommended_action`"))
|
|
1154
|
+
binding = payload.get("binding")
|
|
1155
|
+
if binding is not None:
|
|
1156
|
+
if not isinstance(binding, dict):
|
|
1157
|
+
failures.append(Failure(category, f"{context} binding must be an object when present"))
|
|
1158
|
+
elif binding.get("schema_version") != "loom-github-binding/v1":
|
|
1159
|
+
failures.append(Failure(category, f"{context} binding must use `loom-github-binding/v1`"))
|
|
1153
1160
|
|
|
1154
1161
|
|
|
1155
1162
|
def require_closeout_reconciliation_contract(
|
|
@@ -4824,6 +4831,8 @@ def check_daily_execution_cli(root: Path) -> list[Failure]:
|
|
|
4824
4831
|
("merged_but_open", "fix-needed", "reconciliation-sync"),
|
|
4825
4832
|
("absorbed_but_open", "fix-needed", "reconciliation-sync"),
|
|
4826
4833
|
("parent_drift", "block", "manual-reconciliation"),
|
|
4834
|
+
("binding_failure", "block", "manual-reconciliation"),
|
|
4835
|
+
("merge_signal_drift", "block", "manual-reconciliation"),
|
|
4827
4836
|
("host_signal_drift", "block", "manual-reconciliation"),
|
|
4828
4837
|
]
|
|
4829
4838
|
for kind, reconciliation_result_value, fallback_to in valid_reconciliation_samples:
|
|
@@ -247,6 +247,9 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
|
|
|
247
247
|
closeout.add_argument("--issue", type=int, help="GitHub issue number to validate or sync")
|
|
248
248
|
closeout.add_argument("--pr", type=int, help="GitHub pull request number to validate or sync")
|
|
249
249
|
closeout.add_argument("--project", type=int, help="GitHub project number to validate or sync")
|
|
250
|
+
closeout.add_argument("--phase", type=int, help="GitHub Phase issue number")
|
|
251
|
+
closeout.add_argument("--fr", type=int, help="GitHub FR issue number")
|
|
252
|
+
closeout.add_argument("--branch", help="GitHub branch name bound to the work item")
|
|
250
253
|
closeout.add_argument("--owner", help="GitHub owner; auto-detected from origin when omitted")
|
|
251
254
|
closeout.add_argument("--repo", dest="repo_name", help="GitHub repository name; auto-detected from origin when omitted")
|
|
252
255
|
closeout.add_argument("--comment", help="Optional closeout comment for issue sync")
|
|
@@ -258,6 +261,9 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
|
|
|
258
261
|
reconciliation.add_argument("--issue", type=int, help="GitHub issue number to audit")
|
|
259
262
|
reconciliation.add_argument("--pr", type=int, help="GitHub pull request number to audit")
|
|
260
263
|
reconciliation.add_argument("--project", type=int, help="GitHub project number to audit")
|
|
264
|
+
reconciliation.add_argument("--phase", type=int, help="GitHub Phase issue number")
|
|
265
|
+
reconciliation.add_argument("--fr", type=int, help="GitHub FR issue number")
|
|
266
|
+
reconciliation.add_argument("--branch", help="GitHub branch name bound to the work item")
|
|
261
267
|
reconciliation.add_argument("--owner", help="GitHub owner; auto-detected from origin when omitted")
|
|
262
268
|
reconciliation.add_argument("--repo", dest="repo_name", help="GitHub repository name; auto-detected from origin when omitted")
|
|
263
269
|
reconciliation.add_argument("--comment", help="Optional closeout comment for issue sync")
|
|
@@ -3586,6 +3592,7 @@ def github_binding_payload(
|
|
|
3586
3592
|
branch_name: str | None,
|
|
3587
3593
|
sync: bool,
|
|
3588
3594
|
dry_run: bool,
|
|
3595
|
+
require_complete_chain: bool = True,
|
|
3589
3596
|
) -> dict[str, Any]:
|
|
3590
3597
|
detected_owner, detected_repo = detect_github_repo(target_root)
|
|
3591
3598
|
owner = owner or detected_owner
|
|
@@ -3744,7 +3751,19 @@ def github_binding_payload(
|
|
|
3744
3751
|
"findings": findings,
|
|
3745
3752
|
"repair_plan": repair_plan if sync or dry_run else [],
|
|
3746
3753
|
}
|
|
3747
|
-
|
|
3754
|
+
if require_complete_chain:
|
|
3755
|
+
chain_complete = all(entry.get("status") == "present" for entry in binding["chain"])
|
|
3756
|
+
else:
|
|
3757
|
+
required_edges = []
|
|
3758
|
+
if issue_number is not None and pr_number is not None:
|
|
3759
|
+
required_edges.append(("work_item", "implementation_pr"))
|
|
3760
|
+
if pr_number is not None and pr_payload is not None and pr_payload.get("state") == "MERGED":
|
|
3761
|
+
required_edges.extend([("implementation_pr", "merge_commit"), ("merge_commit", "target_branch")])
|
|
3762
|
+
chain_complete = all(
|
|
3763
|
+
entry.get("status") == "present"
|
|
3764
|
+
for entry in binding["chain"]
|
|
3765
|
+
if (entry.get("from"), entry.get("to")) in required_edges
|
|
3766
|
+
)
|
|
3748
3767
|
if not chain_complete and "binding_chain" not in missing_inputs:
|
|
3749
3768
|
missing_inputs.append("binding_chain")
|
|
3750
3769
|
result = "pass" if not missing_inputs and not findings and chain_complete else "block"
|
|
@@ -4050,9 +4069,12 @@ def reconciliation_result(findings: list[dict[str, Any]]) -> str:
|
|
|
4050
4069
|
def reconciliation_audit_payload(
|
|
4051
4070
|
*,
|
|
4052
4071
|
target_root: Path,
|
|
4072
|
+
phase_number: int | None,
|
|
4073
|
+
fr_number: int | None,
|
|
4053
4074
|
issue_number: int | None,
|
|
4054
4075
|
pr_number: int | None,
|
|
4055
4076
|
project_number: int | None,
|
|
4077
|
+
branch_name: str | None,
|
|
4056
4078
|
owner: str,
|
|
4057
4079
|
repo_name: str,
|
|
4058
4080
|
) -> tuple[dict[str, Any], list[str]]:
|
|
@@ -4062,6 +4084,36 @@ def reconciliation_audit_payload(
|
|
|
4062
4084
|
if issue_number is None and pr_number is None and project_number is None:
|
|
4063
4085
|
missing_inputs.append("issue/pr/project")
|
|
4064
4086
|
|
|
4087
|
+
binding_payload = github_binding_payload(
|
|
4088
|
+
target_root=target_root,
|
|
4089
|
+
owner=owner,
|
|
4090
|
+
repo_name=repo_name,
|
|
4091
|
+
phase_number=phase_number,
|
|
4092
|
+
fr_number=fr_number,
|
|
4093
|
+
issue_number=issue_number,
|
|
4094
|
+
pr_number=pr_number,
|
|
4095
|
+
branch_name=branch_name,
|
|
4096
|
+
sync=False,
|
|
4097
|
+
dry_run=False,
|
|
4098
|
+
require_complete_chain=False,
|
|
4099
|
+
)
|
|
4100
|
+
binding = binding_payload.get("binding") if isinstance(binding_payload.get("binding"), dict) else None
|
|
4101
|
+
binding_findings = binding.get("findings") if isinstance(binding, dict) else None
|
|
4102
|
+
if isinstance(binding_findings, list):
|
|
4103
|
+
for finding in binding_findings:
|
|
4104
|
+
if isinstance(finding, dict):
|
|
4105
|
+
findings.append(
|
|
4106
|
+
make_reconciliation_finding(
|
|
4107
|
+
kind="binding_failure",
|
|
4108
|
+
severity="block",
|
|
4109
|
+
subject=str(finding.get("subject") or "github profile binding"),
|
|
4110
|
+
evidence={"binding": finding.get("evidence", {}), "binding_result": binding_payload.get("result")},
|
|
4111
|
+
recommended_action="repair the GitHub profile binding chain before reconciliation or closeout.",
|
|
4112
|
+
category="gate_failure",
|
|
4113
|
+
fallback_to="manual-reconciliation",
|
|
4114
|
+
)
|
|
4115
|
+
)
|
|
4116
|
+
|
|
4065
4117
|
issue_payload: dict[str, Any] | None = None
|
|
4066
4118
|
issue_id: str | None = None
|
|
4067
4119
|
parent_payload: dict[str, Any] | None = None
|
|
@@ -4100,6 +4152,22 @@ def reconciliation_audit_payload(
|
|
|
4100
4152
|
if isinstance(oid, str) and oid:
|
|
4101
4153
|
merge_commit_sha = oid
|
|
4102
4154
|
merge_commit_in_main = contains_merged_commit(target_root, merge_commit_sha)
|
|
4155
|
+
if pr_payload.get("state") == "MERGED" and (not merge_commit_sha or not merge_commit_in_main):
|
|
4156
|
+
findings.append(
|
|
4157
|
+
make_reconciliation_finding(
|
|
4158
|
+
kind="merge_signal_drift",
|
|
4159
|
+
severity="block",
|
|
4160
|
+
subject=f"PR #{pr_number} merge signal",
|
|
4161
|
+
evidence={
|
|
4162
|
+
"pr_state": pr_payload.get("state"),
|
|
4163
|
+
"merge_commit": merge_commit_sha,
|
|
4164
|
+
"merge_commit_in_main": merge_commit_in_main,
|
|
4165
|
+
},
|
|
4166
|
+
recommended_action="repair or re-read the merge commit basis before closeout.",
|
|
4167
|
+
category="drift",
|
|
4168
|
+
fallback_to="manual-reconciliation",
|
|
4169
|
+
)
|
|
4170
|
+
)
|
|
4103
4171
|
|
|
4104
4172
|
merged_issue_open = False
|
|
4105
4173
|
if issue_payload is not None and pr_payload is not None:
|
|
@@ -4187,7 +4255,15 @@ def reconciliation_audit_payload(
|
|
|
4187
4255
|
if project_number is not None:
|
|
4188
4256
|
project_context, project_errors = project_status_context(target_root, owner, project_number)
|
|
4189
4257
|
if project_errors:
|
|
4190
|
-
|
|
4258
|
+
if any("unknown owner type" in message for message in project_errors):
|
|
4259
|
+
project_payload = {
|
|
4260
|
+
"number": project_number,
|
|
4261
|
+
"status": "unavailable",
|
|
4262
|
+
"reason": "GitHub ProjectV2 CLI owner resolution is unavailable in this environment.",
|
|
4263
|
+
"errors": project_errors,
|
|
4264
|
+
}
|
|
4265
|
+
else:
|
|
4266
|
+
missing_inputs.extend(f"project: {message}" for message in project_errors)
|
|
4191
4267
|
else:
|
|
4192
4268
|
items = project_context["items"]
|
|
4193
4269
|
issue_item = find_project_item(items, issue_number, "issue") if issue_number is not None else None
|
|
@@ -4304,6 +4380,7 @@ def reconciliation_audit_payload(
|
|
|
4304
4380
|
"parent": parent_payload,
|
|
4305
4381
|
"pr": pr_payload,
|
|
4306
4382
|
"project": project_payload,
|
|
4383
|
+
"binding": binding,
|
|
4307
4384
|
"findings": findings,
|
|
4308
4385
|
},
|
|
4309
4386
|
[],
|
|
@@ -4699,9 +4776,12 @@ def runtime_parity_payload(
|
|
|
4699
4776
|
def closeout_payload(
|
|
4700
4777
|
*,
|
|
4701
4778
|
target_root: Path,
|
|
4779
|
+
phase_number: int | None,
|
|
4780
|
+
fr_number: int | None,
|
|
4702
4781
|
issue_number: int | None,
|
|
4703
4782
|
pr_number: int | None,
|
|
4704
4783
|
project_number: int | None,
|
|
4784
|
+
branch_name: str | None,
|
|
4705
4785
|
owner: str,
|
|
4706
4786
|
repo_name: str,
|
|
4707
4787
|
skip_gate: bool,
|
|
@@ -4733,9 +4813,12 @@ def closeout_payload(
|
|
|
4733
4813
|
if issue_number is not None or pr_number is not None or project_number is not None:
|
|
4734
4814
|
reconciliation_payload, reconciliation_errors = reconciliation_audit_payload(
|
|
4735
4815
|
target_root=target_root,
|
|
4816
|
+
phase_number=phase_number,
|
|
4817
|
+
fr_number=fr_number,
|
|
4736
4818
|
issue_number=issue_number,
|
|
4737
4819
|
pr_number=pr_number,
|
|
4738
4820
|
project_number=project_number,
|
|
4821
|
+
branch_name=branch_name,
|
|
4739
4822
|
owner=owner,
|
|
4740
4823
|
repo_name=repo_name,
|
|
4741
4824
|
)
|
|
@@ -4783,7 +4866,15 @@ def closeout_payload(
|
|
|
4783
4866
|
if project_number is not None:
|
|
4784
4867
|
project_context, project_errors = project_status_context(target_root, owner, project_number)
|
|
4785
4868
|
if project_errors:
|
|
4786
|
-
|
|
4869
|
+
if any("unknown owner type" in message for message in project_errors):
|
|
4870
|
+
project_payload = {
|
|
4871
|
+
"number": project_number,
|
|
4872
|
+
"status": "unavailable",
|
|
4873
|
+
"reason": "GitHub ProjectV2 CLI owner resolution is unavailable in this environment.",
|
|
4874
|
+
"errors": project_errors,
|
|
4875
|
+
}
|
|
4876
|
+
else:
|
|
4877
|
+
missing_inputs.extend(f"project: {message}" for message in project_errors)
|
|
4787
4878
|
else:
|
|
4788
4879
|
items = project_context["items"]
|
|
4789
4880
|
issue_item = find_project_item(items, issue_number, "issue") if issue_number is not None else None
|
|
@@ -4882,9 +4973,12 @@ def handle_closeout(args: argparse.Namespace) -> int:
|
|
|
4882
4973
|
|
|
4883
4974
|
payload, errors = closeout_payload(
|
|
4884
4975
|
target_root=target_root,
|
|
4976
|
+
phase_number=args.phase,
|
|
4977
|
+
fr_number=args.fr,
|
|
4885
4978
|
issue_number=args.issue,
|
|
4886
4979
|
pr_number=args.pr,
|
|
4887
4980
|
project_number=args.project,
|
|
4981
|
+
branch_name=args.branch,
|
|
4888
4982
|
owner=owner,
|
|
4889
4983
|
repo_name=repo_name,
|
|
4890
4984
|
skip_gate=args.skip_gate,
|
|
@@ -4988,9 +5082,12 @@ def handle_closeout(args: argparse.Namespace) -> int:
|
|
|
4988
5082
|
|
|
4989
5083
|
refreshed_payload, errors = closeout_payload(
|
|
4990
5084
|
target_root=target_root,
|
|
5085
|
+
phase_number=args.phase,
|
|
5086
|
+
fr_number=args.fr,
|
|
4991
5087
|
issue_number=args.issue,
|
|
4992
5088
|
pr_number=args.pr,
|
|
4993
5089
|
project_number=args.project,
|
|
5090
|
+
branch_name=args.branch,
|
|
4994
5091
|
owner=owner,
|
|
4995
5092
|
repo_name=repo_name,
|
|
4996
5093
|
skip_gate=args.skip_gate,
|
|
@@ -5070,9 +5167,12 @@ def handle_reconciliation(args: argparse.Namespace) -> int:
|
|
|
5070
5167
|
|
|
5071
5168
|
payload, errors = reconciliation_audit_payload(
|
|
5072
5169
|
target_root=target_root,
|
|
5170
|
+
phase_number=args.phase,
|
|
5171
|
+
fr_number=args.fr,
|
|
5073
5172
|
issue_number=args.issue,
|
|
5074
5173
|
pr_number=args.pr,
|
|
5075
5174
|
project_number=args.project,
|
|
5175
|
+
branch_name=args.branch,
|
|
5076
5176
|
owner=owner,
|
|
5077
5177
|
repo_name=repo_name,
|
|
5078
5178
|
)
|
|
@@ -5230,9 +5330,12 @@ def handle_reconciliation(args: argparse.Namespace) -> int:
|
|
|
5230
5330
|
|
|
5231
5331
|
refreshed_payload, refreshed_errors = reconciliation_audit_payload(
|
|
5232
5332
|
target_root=target_root,
|
|
5333
|
+
phase_number=args.phase,
|
|
5334
|
+
fr_number=args.fr,
|
|
5233
5335
|
issue_number=args.issue,
|
|
5234
5336
|
pr_number=args.pr,
|
|
5235
5337
|
project_number=args.project,
|
|
5338
|
+
branch_name=args.branch,
|
|
5236
5339
|
owner=owner,
|
|
5237
5340
|
repo_name=repo_name,
|
|
5238
5341
|
)
|
|
@@ -94,6 +94,7 @@ CORE_DOCS = (
|
|
|
94
94
|
"docs/evidence/landing-map.md",
|
|
95
95
|
"docs/evidence/validations/validation-closeout-reconciliation-blocking-gate.md",
|
|
96
96
|
"docs/evidence/validations/validation-github-profile-binding-orchestration.md",
|
|
97
|
+
"docs/evidence/validations/validation-github-profile-drift-reconciliation.md",
|
|
97
98
|
"docs/evidence/validations/validation-loom-core-runtime-parity.md",
|
|
98
99
|
"docs/evidence/validations/validation-shadow-parity-blocking-gate.md",
|
|
99
100
|
"docs/evidence/validations/validation-syvert-strong-governance-parity.md",
|
|
@@ -1138,7 +1139,7 @@ def require_reconciliation_payload(
|
|
|
1138
1139
|
continue
|
|
1139
1140
|
if finding.get("category") not in {"drift", "gate_failure"}:
|
|
1140
1141
|
failures.append(Failure(category, f"{context} reconciliation finding category must stay within the stable taxonomy"))
|
|
1141
|
-
if finding.get("kind") not in {"merged_but_open", "absorbed_but_open", "parent_drift", "project_drift", "host_signal_drift"}:
|
|
1142
|
+
if finding.get("kind") not in {"merged_but_open", "absorbed_but_open", "parent_drift", "project_drift", "host_signal_drift", "binding_failure", "merge_signal_drift"}:
|
|
1142
1143
|
failures.append(Failure(category, f"{context} reconciliation finding kind must stay within the stable contract"))
|
|
1143
1144
|
if finding.get("severity") not in {"warn", "fix-needed", "block"}:
|
|
1144
1145
|
failures.append(Failure(category, f"{context} reconciliation finding severity must stay within the stable contract"))
|
|
@@ -1150,6 +1151,12 @@ def require_reconciliation_payload(
|
|
|
1150
1151
|
failures.append(Failure(category, f"{context} reconciliation findings must include `evidence`"))
|
|
1151
1152
|
if not isinstance(finding.get("recommended_action"), str) or not finding.get("recommended_action"):
|
|
1152
1153
|
failures.append(Failure(category, f"{context} reconciliation findings must include non-empty `recommended_action`"))
|
|
1154
|
+
binding = payload.get("binding")
|
|
1155
|
+
if binding is not None:
|
|
1156
|
+
if not isinstance(binding, dict):
|
|
1157
|
+
failures.append(Failure(category, f"{context} binding must be an object when present"))
|
|
1158
|
+
elif binding.get("schema_version") != "loom-github-binding/v1":
|
|
1159
|
+
failures.append(Failure(category, f"{context} binding must use `loom-github-binding/v1`"))
|
|
1153
1160
|
|
|
1154
1161
|
|
|
1155
1162
|
def require_closeout_reconciliation_contract(
|
|
@@ -4824,6 +4831,8 @@ def check_daily_execution_cli(root: Path) -> list[Failure]:
|
|
|
4824
4831
|
("merged_but_open", "fix-needed", "reconciliation-sync"),
|
|
4825
4832
|
("absorbed_but_open", "fix-needed", "reconciliation-sync"),
|
|
4826
4833
|
("parent_drift", "block", "manual-reconciliation"),
|
|
4834
|
+
("binding_failure", "block", "manual-reconciliation"),
|
|
4835
|
+
("merge_signal_drift", "block", "manual-reconciliation"),
|
|
4827
4836
|
("host_signal_drift", "block", "manual-reconciliation"),
|
|
4828
4837
|
]
|
|
4829
4838
|
for kind, reconciliation_result_value, fallback_to in valid_reconciliation_samples:
|