@mc-and-his-agents/loom-installer 0.1.42 → 0.1.43
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 +114 -0
- package/payload/plugin/loom/skills/shared/scripts/loom_flow.py +58 -26
- package/payload/skills/loom-adopt/.loom-runtime/shared/scripts/loom_check.py +114 -0
- package/payload/skills/loom-adopt/.loom-runtime/shared/scripts/loom_flow.py +58 -26
- package/payload/skills/loom-handoff/.loom-runtime/shared/scripts/loom_check.py +114 -0
- package/payload/skills/loom-handoff/.loom-runtime/shared/scripts/loom_flow.py +58 -26
- package/payload/skills/loom-init/.loom-runtime/shared/scripts/loom_check.py +114 -0
- package/payload/skills/loom-init/.loom-runtime/shared/scripts/loom_flow.py +58 -26
- package/payload/skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_check.py +114 -0
- package/payload/skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_flow.py +58 -26
- package/payload/skills/loom-pre-review/.loom-runtime/shared/scripts/loom_check.py +114 -0
- package/payload/skills/loom-pre-review/.loom-runtime/shared/scripts/loom_flow.py +58 -26
- package/payload/skills/loom-resume/.loom-runtime/shared/scripts/loom_check.py +114 -0
- package/payload/skills/loom-resume/.loom-runtime/shared/scripts/loom_flow.py +58 -26
- package/payload/skills/loom-retire/.loom-runtime/shared/scripts/loom_check.py +114 -0
- package/payload/skills/loom-retire/.loom-runtime/shared/scripts/loom_flow.py +58 -26
- package/payload/skills/loom-review/.loom-runtime/shared/scripts/loom_check.py +114 -0
- package/payload/skills/loom-review/.loom-runtime/shared/scripts/loom_flow.py +58 -26
- package/payload/skills/loom-spec-review/.loom-runtime/shared/scripts/loom_check.py +114 -0
- package/payload/skills/loom-spec-review/.loom-runtime/shared/scripts/loom_flow.py +58 -26
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": "8c3ba44e07d467f15fee186221344f2bef12ed42",
|
|
6
6
|
"source_ref": "main",
|
|
7
|
-
"built_at": "2026-04-
|
|
7
|
+
"built_at": "2026-04-27T22:10:31+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":
|
|
637
|
-
"sha256": "
|
|
636
|
+
"bytes": 356924,
|
|
637
|
+
"sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
|
|
638
638
|
},
|
|
639
639
|
{
|
|
640
640
|
"path": "plugin/loom/skills/shared/scripts/loom_flow.py",
|
|
641
|
-
"bytes":
|
|
642
|
-
"sha256": "
|
|
641
|
+
"bytes": 340366,
|
|
642
|
+
"sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
|
|
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":
|
|
1227
|
-
"sha256": "
|
|
1226
|
+
"bytes": 356924,
|
|
1227
|
+
"sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
|
|
1228
1228
|
},
|
|
1229
1229
|
{
|
|
1230
1230
|
"path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_flow.py",
|
|
1231
|
-
"bytes":
|
|
1232
|
-
"sha256": "
|
|
1231
|
+
"bytes": 340366,
|
|
1232
|
+
"sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
|
|
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":
|
|
1847
|
-
"sha256": "
|
|
1846
|
+
"bytes": 356924,
|
|
1847
|
+
"sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
|
|
1848
1848
|
},
|
|
1849
1849
|
{
|
|
1850
1850
|
"path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_flow.py",
|
|
1851
|
-
"bytes":
|
|
1852
|
-
"sha256": "
|
|
1851
|
+
"bytes": 340366,
|
|
1852
|
+
"sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
|
|
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":
|
|
2467
|
-
"sha256": "
|
|
2466
|
+
"bytes": 356924,
|
|
2467
|
+
"sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
|
|
2468
2468
|
},
|
|
2469
2469
|
{
|
|
2470
2470
|
"path": "skills/loom-init/.loom-runtime/shared/scripts/loom_flow.py",
|
|
2471
|
-
"bytes":
|
|
2472
|
-
"sha256": "
|
|
2471
|
+
"bytes": 340366,
|
|
2472
|
+
"sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
|
|
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":
|
|
3092
|
-
"sha256": "
|
|
3091
|
+
"bytes": 356924,
|
|
3092
|
+
"sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
|
|
3093
3093
|
},
|
|
3094
3094
|
{
|
|
3095
3095
|
"path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_flow.py",
|
|
3096
|
-
"bytes":
|
|
3097
|
-
"sha256": "
|
|
3096
|
+
"bytes": 340366,
|
|
3097
|
+
"sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
|
|
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":
|
|
3712
|
-
"sha256": "
|
|
3711
|
+
"bytes": 356924,
|
|
3712
|
+
"sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
|
|
3713
3713
|
},
|
|
3714
3714
|
{
|
|
3715
3715
|
"path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_flow.py",
|
|
3716
|
-
"bytes":
|
|
3717
|
-
"sha256": "
|
|
3716
|
+
"bytes": 340366,
|
|
3717
|
+
"sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
|
|
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":
|
|
4332
|
-
"sha256": "
|
|
4331
|
+
"bytes": 356924,
|
|
4332
|
+
"sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
|
|
4333
4333
|
},
|
|
4334
4334
|
{
|
|
4335
4335
|
"path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_flow.py",
|
|
4336
|
-
"bytes":
|
|
4337
|
-
"sha256": "
|
|
4336
|
+
"bytes": 340366,
|
|
4337
|
+
"sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
|
|
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":
|
|
4952
|
-
"sha256": "
|
|
4951
|
+
"bytes": 356924,
|
|
4952
|
+
"sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
|
|
4953
4953
|
},
|
|
4954
4954
|
{
|
|
4955
4955
|
"path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_flow.py",
|
|
4956
|
-
"bytes":
|
|
4957
|
-
"sha256": "
|
|
4956
|
+
"bytes": 340366,
|
|
4957
|
+
"sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
|
|
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":
|
|
5572
|
-
"sha256": "
|
|
5571
|
+
"bytes": 356924,
|
|
5572
|
+
"sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
|
|
5573
5573
|
},
|
|
5574
5574
|
{
|
|
5575
5575
|
"path": "skills/loom-review/.loom-runtime/shared/scripts/loom_flow.py",
|
|
5576
|
-
"bytes":
|
|
5577
|
-
"sha256": "
|
|
5576
|
+
"bytes": 340366,
|
|
5577
|
+
"sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
|
|
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":
|
|
6192
|
-
"sha256": "
|
|
6191
|
+
"bytes": 356924,
|
|
6192
|
+
"sha256": "71f0b9a7d55526b119af75b06c9787d875c27e19bbea70853ebc3df11f43eee7"
|
|
6193
6193
|
},
|
|
6194
6194
|
{
|
|
6195
6195
|
"path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_flow.py",
|
|
6196
|
-
"bytes":
|
|
6197
|
-
"sha256": "
|
|
6196
|
+
"bytes": 340366,
|
|
6197
|
+
"sha256": "334a8df4ef8400125b6e7c311d386ac98c15f34c5be91befb9499215ee4ba0c4"
|
|
6198
6198
|
},
|
|
6199
6199
|
{
|
|
6200
6200
|
"path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_init.py",
|
|
@@ -6915,6 +6915,120 @@ def check_adversarial_adoption_fixture(root: Path) -> list[Failure]:
|
|
|
6915
6915
|
elif after_payload.get("refresh_needed"):
|
|
6916
6916
|
failures.append(Failure("adversarial-adoption", "carrier refresh --write must clear runtime provenance drift"))
|
|
6917
6917
|
|
|
6918
|
+
outside_comment = base / "outside-comment.md"
|
|
6919
|
+
outside_comment.write_text("sensitive local text\n", encoding="utf-8")
|
|
6920
|
+
comment_escape_payload, error = load_command_json(
|
|
6921
|
+
root,
|
|
6922
|
+
[
|
|
6923
|
+
"python3",
|
|
6924
|
+
"tools/loom_flow.py",
|
|
6925
|
+
"reconciliation",
|
|
6926
|
+
"sync",
|
|
6927
|
+
"--target",
|
|
6928
|
+
str(baseline),
|
|
6929
|
+
"--comment-file",
|
|
6930
|
+
str(outside_comment),
|
|
6931
|
+
"--dry-run",
|
|
6932
|
+
],
|
|
6933
|
+
)
|
|
6934
|
+
if error:
|
|
6935
|
+
failures.append(Failure("adversarial-adoption", f"reconciliation comment-file boundary sample failed: {error}"))
|
|
6936
|
+
elif comment_escape_payload.get("result") != "block":
|
|
6937
|
+
failures.append(Failure("adversarial-adoption", "reconciliation --comment-file must block absolute paths outside the target root"))
|
|
6938
|
+
else:
|
|
6939
|
+
missing_inputs = comment_escape_payload.get("missing_inputs")
|
|
6940
|
+
if not isinstance(missing_inputs, list) or not any(
|
|
6941
|
+
"reconciliation comment file" in str(item) and "must be repo-relative" in str(item)
|
|
6942
|
+
for item in missing_inputs
|
|
6943
|
+
):
|
|
6944
|
+
failures.append(Failure("adversarial-adoption", "reconciliation --comment-file boundary block must report the comment file locator boundary error"))
|
|
6945
|
+
|
|
6946
|
+
original_pr_payload = loom_flow_module.github_pr_payload
|
|
6947
|
+
original_issue_payload = loom_flow_module.github_issue_payload
|
|
6948
|
+
original_reconciliation_audit = loom_flow_module.reconciliation_audit_payload
|
|
6949
|
+
original_contains = loom_flow_module.contains_merged_commit
|
|
6950
|
+
seen_target_branches: list[str] = []
|
|
6951
|
+
try:
|
|
6952
|
+
loom_flow_module.github_pr_payload = lambda *_args, **_kwargs: (
|
|
6953
|
+
{
|
|
6954
|
+
"state": "MERGED",
|
|
6955
|
+
"baseRefName": "release/main",
|
|
6956
|
+
"mergeCommit": {"oid": "abc123"},
|
|
6957
|
+
},
|
|
6958
|
+
[],
|
|
6959
|
+
)
|
|
6960
|
+
loom_flow_module.github_issue_payload = lambda *_args, **_kwargs: (
|
|
6961
|
+
{"state": "CLOSED", "id": "issue-id"},
|
|
6962
|
+
[],
|
|
6963
|
+
)
|
|
6964
|
+
loom_flow_module.reconciliation_audit_payload = lambda **_kwargs: (
|
|
6965
|
+
{
|
|
6966
|
+
"result": "pass",
|
|
6967
|
+
"findings": [],
|
|
6968
|
+
"missing_inputs": [],
|
|
6969
|
+
"fallback_to": None,
|
|
6970
|
+
},
|
|
6971
|
+
[],
|
|
6972
|
+
)
|
|
6973
|
+
|
|
6974
|
+
def fake_contains(_root: Path, _merge_commit_sha: str, target_branch: str = "main") -> bool:
|
|
6975
|
+
seen_target_branches.append(target_branch)
|
|
6976
|
+
return target_branch == "release/main"
|
|
6977
|
+
|
|
6978
|
+
loom_flow_module.contains_merged_commit = fake_contains
|
|
6979
|
+
closeout_target_branch_payload, closeout_errors = loom_flow_module.closeout_payload(
|
|
6980
|
+
target_root=baseline,
|
|
6981
|
+
phase_number=None,
|
|
6982
|
+
fr_number=None,
|
|
6983
|
+
issue_number=1,
|
|
6984
|
+
pr_number=2,
|
|
6985
|
+
project_number=None,
|
|
6986
|
+
branch_name=None,
|
|
6987
|
+
owner="owner",
|
|
6988
|
+
repo_name="repo",
|
|
6989
|
+
skip_gate=True,
|
|
6990
|
+
)
|
|
6991
|
+
if closeout_errors:
|
|
6992
|
+
failures.append(Failure("adversarial-adoption", f"closeout target branch fixture failed: {closeout_errors}"))
|
|
6993
|
+
elif closeout_target_branch_payload.get("result") != "pass":
|
|
6994
|
+
failures.append(Failure("adversarial-adoption", "closeout must pass when the merge commit is contained in the PR base branch"))
|
|
6995
|
+
if seen_target_branches != ["release/main"]:
|
|
6996
|
+
failures.append(Failure("adversarial-adoption", "closeout must check merge commit containment against the PR base branch, including slash branches"))
|
|
6997
|
+
|
|
6998
|
+
seen_target_branches.clear()
|
|
6999
|
+
loom_flow_module.github_pr_payload = lambda *_args, **_kwargs: (
|
|
7000
|
+
{
|
|
7001
|
+
"state": "MERGED",
|
|
7002
|
+
"mergeCommit": {"oid": "abc123"},
|
|
7003
|
+
},
|
|
7004
|
+
[],
|
|
7005
|
+
)
|
|
7006
|
+
closeout_missing_base_payload, closeout_errors = loom_flow_module.closeout_payload(
|
|
7007
|
+
target_root=baseline,
|
|
7008
|
+
phase_number=None,
|
|
7009
|
+
fr_number=None,
|
|
7010
|
+
issue_number=1,
|
|
7011
|
+
pr_number=2,
|
|
7012
|
+
project_number=None,
|
|
7013
|
+
branch_name=None,
|
|
7014
|
+
owner="owner",
|
|
7015
|
+
repo_name="repo",
|
|
7016
|
+
skip_gate=True,
|
|
7017
|
+
)
|
|
7018
|
+
if closeout_errors:
|
|
7019
|
+
failures.append(Failure("adversarial-adoption", f"closeout missing baseRefName fixture failed: {closeout_errors}"))
|
|
7020
|
+
else:
|
|
7021
|
+
missing_inputs = closeout_missing_base_payload.get("missing_inputs")
|
|
7022
|
+
if closeout_missing_base_payload.get("result") != "block" or "pr baseRefName is missing" not in missing_inputs:
|
|
7023
|
+
failures.append(Failure("adversarial-adoption", "closeout must block when PR baseRefName is missing instead of falling back to main"))
|
|
7024
|
+
if seen_target_branches:
|
|
7025
|
+
failures.append(Failure("adversarial-adoption", "closeout must not check origin/main when PR baseRefName is missing"))
|
|
7026
|
+
finally:
|
|
7027
|
+
loom_flow_module.github_pr_payload = original_pr_payload
|
|
7028
|
+
loom_flow_module.github_issue_payload = original_issue_payload
|
|
7029
|
+
loom_flow_module.reconciliation_audit_payload = original_reconciliation_audit
|
|
7030
|
+
loom_flow_module.contains_merged_commit = original_contains
|
|
7031
|
+
|
|
6918
7032
|
rollover_target = base / "active-rollover"
|
|
6919
7033
|
shutil.copytree(baseline, rollover_target)
|
|
6920
7034
|
payload, error = load_command_json(
|
|
@@ -1040,6 +1040,21 @@ def read_text_file(path_str: str) -> tuple[str | None, list[str]]:
|
|
|
1040
1040
|
return text, []
|
|
1041
1041
|
|
|
1042
1042
|
|
|
1043
|
+
def read_repo_relative_text_file(root: Path, path_str: str, *, label: str) -> tuple[str | None, list[str]]:
|
|
1044
|
+
path, errors = resolve_repo_relative_path(root, path_str, label=label)
|
|
1045
|
+
if errors:
|
|
1046
|
+
return None, errors
|
|
1047
|
+
if path is None:
|
|
1048
|
+
return None, [f"{label} is unavailable"]
|
|
1049
|
+
if not path.exists() or not path.is_file():
|
|
1050
|
+
return None, [f"{label} points to a missing file: {path_str}"]
|
|
1051
|
+
try:
|
|
1052
|
+
text = path.read_text(encoding="utf-8")
|
|
1053
|
+
except OSError as exc:
|
|
1054
|
+
return None, [f"failed to read {path_str}: {exc.strerror or exc}"]
|
|
1055
|
+
return text, []
|
|
1056
|
+
|
|
1057
|
+
|
|
1043
1058
|
def write_json_file(path: Path, payload: Any) -> None:
|
|
1044
1059
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
1045
1060
|
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
@@ -5322,8 +5337,24 @@ def reconciliation_audit_payload(
|
|
|
5322
5337
|
if isinstance(oid, str) and oid:
|
|
5323
5338
|
merge_commit_sha = oid
|
|
5324
5339
|
base_ref = pr_payload.get("baseRefName")
|
|
5325
|
-
|
|
5326
|
-
|
|
5340
|
+
if isinstance(base_ref, str) and base_ref:
|
|
5341
|
+
merge_commit_in_main = contains_merged_commit(target_root, merge_commit_sha, base_ref)
|
|
5342
|
+
else:
|
|
5343
|
+
findings.append(
|
|
5344
|
+
make_reconciliation_finding(
|
|
5345
|
+
kind="merge_signal_drift",
|
|
5346
|
+
severity="block",
|
|
5347
|
+
subject=f"PR #{pr_number} merge signal",
|
|
5348
|
+
evidence={
|
|
5349
|
+
"pr_state": pr_payload.get("state"),
|
|
5350
|
+
"merge_commit": merge_commit_sha,
|
|
5351
|
+
"baseRefName": base_ref,
|
|
5352
|
+
},
|
|
5353
|
+
recommended_action="re-read the PR base branch before closeout or reconciliation.",
|
|
5354
|
+
category="binding_failure",
|
|
5355
|
+
fallback_to="manual-reconciliation",
|
|
5356
|
+
)
|
|
5357
|
+
)
|
|
5327
5358
|
if pr_payload.get("state") == "MERGED" and (not merge_commit_sha or not merge_commit_in_main):
|
|
5328
5359
|
findings.append(
|
|
5329
5360
|
make_reconciliation_finding(
|
|
@@ -6036,10 +6067,12 @@ def closeout_payload(
|
|
|
6036
6067
|
if pr_payload.get("state") != "MERGED":
|
|
6037
6068
|
missing_inputs.append("pr is not merged")
|
|
6038
6069
|
if merge_commit_sha:
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6070
|
+
base_ref = pr_payload.get("baseRefName")
|
|
6071
|
+
if isinstance(base_ref, str) and base_ref:
|
|
6072
|
+
if not contains_merged_commit(target_root, merge_commit_sha, base_ref):
|
|
6073
|
+
missing_inputs.append(f"origin/{base_ref} does not contain the merged PR commit")
|
|
6074
|
+
else:
|
|
6075
|
+
missing_inputs.append("pr baseRefName is missing")
|
|
6043
6076
|
|
|
6044
6077
|
project_payload: dict[str, Any] | None = None
|
|
6045
6078
|
if project_number is not None:
|
|
@@ -6303,25 +6336,6 @@ def handle_reconciliation(args: argparse.Namespace) -> int:
|
|
|
6303
6336
|
summary="reconciliation is blocked because the Loom runtime state is inconsistent.",
|
|
6304
6337
|
)
|
|
6305
6338
|
)
|
|
6306
|
-
owner = args.owner
|
|
6307
|
-
repo_name = args.repo_name
|
|
6308
|
-
if not owner or not repo_name:
|
|
6309
|
-
detected_owner, detected_repo = detect_github_repo(target_root)
|
|
6310
|
-
owner = owner or detected_owner
|
|
6311
|
-
repo_name = repo_name or detected_repo
|
|
6312
|
-
if not owner or not repo_name:
|
|
6313
|
-
return emit(
|
|
6314
|
-
{
|
|
6315
|
-
"command": "reconciliation",
|
|
6316
|
-
"operation": args.operation,
|
|
6317
|
-
"result": "block",
|
|
6318
|
-
"summary": "reconciliation could not determine the GitHub repository.",
|
|
6319
|
-
"missing_inputs": ["owner/repo"],
|
|
6320
|
-
"fallback_to": "manual-reconciliation",
|
|
6321
|
-
"runtime_state": runtime_state,
|
|
6322
|
-
}
|
|
6323
|
-
)
|
|
6324
|
-
|
|
6325
6339
|
if args.comment and args.comment_file:
|
|
6326
6340
|
return emit(
|
|
6327
6341
|
{
|
|
@@ -6337,7 +6351,7 @@ def handle_reconciliation(args: argparse.Namespace) -> int:
|
|
|
6337
6351
|
|
|
6338
6352
|
comment_body = args.comment
|
|
6339
6353
|
if args.comment_file:
|
|
6340
|
-
comment_body, comment_errors =
|
|
6354
|
+
comment_body, comment_errors = read_repo_relative_text_file(target_root, args.comment_file, label="reconciliation comment file")
|
|
6341
6355
|
if comment_errors:
|
|
6342
6356
|
return emit(
|
|
6343
6357
|
{
|
|
@@ -6350,6 +6364,24 @@ def handle_reconciliation(args: argparse.Namespace) -> int:
|
|
|
6350
6364
|
"runtime_state": runtime_state,
|
|
6351
6365
|
}
|
|
6352
6366
|
)
|
|
6367
|
+
owner = args.owner
|
|
6368
|
+
repo_name = args.repo_name
|
|
6369
|
+
if not owner or not repo_name:
|
|
6370
|
+
detected_owner, detected_repo = detect_github_repo(target_root)
|
|
6371
|
+
owner = owner or detected_owner
|
|
6372
|
+
repo_name = repo_name or detected_repo
|
|
6373
|
+
if not owner or not repo_name:
|
|
6374
|
+
return emit(
|
|
6375
|
+
{
|
|
6376
|
+
"command": "reconciliation",
|
|
6377
|
+
"operation": args.operation,
|
|
6378
|
+
"result": "block",
|
|
6379
|
+
"summary": "reconciliation could not determine the GitHub repository.",
|
|
6380
|
+
"missing_inputs": ["owner/repo"],
|
|
6381
|
+
"fallback_to": "manual-reconciliation",
|
|
6382
|
+
"runtime_state": runtime_state,
|
|
6383
|
+
}
|
|
6384
|
+
)
|
|
6353
6385
|
|
|
6354
6386
|
payload, errors = reconciliation_audit_payload(
|
|
6355
6387
|
target_root=target_root,
|
|
@@ -6915,6 +6915,120 @@ def check_adversarial_adoption_fixture(root: Path) -> list[Failure]:
|
|
|
6915
6915
|
elif after_payload.get("refresh_needed"):
|
|
6916
6916
|
failures.append(Failure("adversarial-adoption", "carrier refresh --write must clear runtime provenance drift"))
|
|
6917
6917
|
|
|
6918
|
+
outside_comment = base / "outside-comment.md"
|
|
6919
|
+
outside_comment.write_text("sensitive local text\n", encoding="utf-8")
|
|
6920
|
+
comment_escape_payload, error = load_command_json(
|
|
6921
|
+
root,
|
|
6922
|
+
[
|
|
6923
|
+
"python3",
|
|
6924
|
+
"tools/loom_flow.py",
|
|
6925
|
+
"reconciliation",
|
|
6926
|
+
"sync",
|
|
6927
|
+
"--target",
|
|
6928
|
+
str(baseline),
|
|
6929
|
+
"--comment-file",
|
|
6930
|
+
str(outside_comment),
|
|
6931
|
+
"--dry-run",
|
|
6932
|
+
],
|
|
6933
|
+
)
|
|
6934
|
+
if error:
|
|
6935
|
+
failures.append(Failure("adversarial-adoption", f"reconciliation comment-file boundary sample failed: {error}"))
|
|
6936
|
+
elif comment_escape_payload.get("result") != "block":
|
|
6937
|
+
failures.append(Failure("adversarial-adoption", "reconciliation --comment-file must block absolute paths outside the target root"))
|
|
6938
|
+
else:
|
|
6939
|
+
missing_inputs = comment_escape_payload.get("missing_inputs")
|
|
6940
|
+
if not isinstance(missing_inputs, list) or not any(
|
|
6941
|
+
"reconciliation comment file" in str(item) and "must be repo-relative" in str(item)
|
|
6942
|
+
for item in missing_inputs
|
|
6943
|
+
):
|
|
6944
|
+
failures.append(Failure("adversarial-adoption", "reconciliation --comment-file boundary block must report the comment file locator boundary error"))
|
|
6945
|
+
|
|
6946
|
+
original_pr_payload = loom_flow_module.github_pr_payload
|
|
6947
|
+
original_issue_payload = loom_flow_module.github_issue_payload
|
|
6948
|
+
original_reconciliation_audit = loom_flow_module.reconciliation_audit_payload
|
|
6949
|
+
original_contains = loom_flow_module.contains_merged_commit
|
|
6950
|
+
seen_target_branches: list[str] = []
|
|
6951
|
+
try:
|
|
6952
|
+
loom_flow_module.github_pr_payload = lambda *_args, **_kwargs: (
|
|
6953
|
+
{
|
|
6954
|
+
"state": "MERGED",
|
|
6955
|
+
"baseRefName": "release/main",
|
|
6956
|
+
"mergeCommit": {"oid": "abc123"},
|
|
6957
|
+
},
|
|
6958
|
+
[],
|
|
6959
|
+
)
|
|
6960
|
+
loom_flow_module.github_issue_payload = lambda *_args, **_kwargs: (
|
|
6961
|
+
{"state": "CLOSED", "id": "issue-id"},
|
|
6962
|
+
[],
|
|
6963
|
+
)
|
|
6964
|
+
loom_flow_module.reconciliation_audit_payload = lambda **_kwargs: (
|
|
6965
|
+
{
|
|
6966
|
+
"result": "pass",
|
|
6967
|
+
"findings": [],
|
|
6968
|
+
"missing_inputs": [],
|
|
6969
|
+
"fallback_to": None,
|
|
6970
|
+
},
|
|
6971
|
+
[],
|
|
6972
|
+
)
|
|
6973
|
+
|
|
6974
|
+
def fake_contains(_root: Path, _merge_commit_sha: str, target_branch: str = "main") -> bool:
|
|
6975
|
+
seen_target_branches.append(target_branch)
|
|
6976
|
+
return target_branch == "release/main"
|
|
6977
|
+
|
|
6978
|
+
loom_flow_module.contains_merged_commit = fake_contains
|
|
6979
|
+
closeout_target_branch_payload, closeout_errors = loom_flow_module.closeout_payload(
|
|
6980
|
+
target_root=baseline,
|
|
6981
|
+
phase_number=None,
|
|
6982
|
+
fr_number=None,
|
|
6983
|
+
issue_number=1,
|
|
6984
|
+
pr_number=2,
|
|
6985
|
+
project_number=None,
|
|
6986
|
+
branch_name=None,
|
|
6987
|
+
owner="owner",
|
|
6988
|
+
repo_name="repo",
|
|
6989
|
+
skip_gate=True,
|
|
6990
|
+
)
|
|
6991
|
+
if closeout_errors:
|
|
6992
|
+
failures.append(Failure("adversarial-adoption", f"closeout target branch fixture failed: {closeout_errors}"))
|
|
6993
|
+
elif closeout_target_branch_payload.get("result") != "pass":
|
|
6994
|
+
failures.append(Failure("adversarial-adoption", "closeout must pass when the merge commit is contained in the PR base branch"))
|
|
6995
|
+
if seen_target_branches != ["release/main"]:
|
|
6996
|
+
failures.append(Failure("adversarial-adoption", "closeout must check merge commit containment against the PR base branch, including slash branches"))
|
|
6997
|
+
|
|
6998
|
+
seen_target_branches.clear()
|
|
6999
|
+
loom_flow_module.github_pr_payload = lambda *_args, **_kwargs: (
|
|
7000
|
+
{
|
|
7001
|
+
"state": "MERGED",
|
|
7002
|
+
"mergeCommit": {"oid": "abc123"},
|
|
7003
|
+
},
|
|
7004
|
+
[],
|
|
7005
|
+
)
|
|
7006
|
+
closeout_missing_base_payload, closeout_errors = loom_flow_module.closeout_payload(
|
|
7007
|
+
target_root=baseline,
|
|
7008
|
+
phase_number=None,
|
|
7009
|
+
fr_number=None,
|
|
7010
|
+
issue_number=1,
|
|
7011
|
+
pr_number=2,
|
|
7012
|
+
project_number=None,
|
|
7013
|
+
branch_name=None,
|
|
7014
|
+
owner="owner",
|
|
7015
|
+
repo_name="repo",
|
|
7016
|
+
skip_gate=True,
|
|
7017
|
+
)
|
|
7018
|
+
if closeout_errors:
|
|
7019
|
+
failures.append(Failure("adversarial-adoption", f"closeout missing baseRefName fixture failed: {closeout_errors}"))
|
|
7020
|
+
else:
|
|
7021
|
+
missing_inputs = closeout_missing_base_payload.get("missing_inputs")
|
|
7022
|
+
if closeout_missing_base_payload.get("result") != "block" or "pr baseRefName is missing" not in missing_inputs:
|
|
7023
|
+
failures.append(Failure("adversarial-adoption", "closeout must block when PR baseRefName is missing instead of falling back to main"))
|
|
7024
|
+
if seen_target_branches:
|
|
7025
|
+
failures.append(Failure("adversarial-adoption", "closeout must not check origin/main when PR baseRefName is missing"))
|
|
7026
|
+
finally:
|
|
7027
|
+
loom_flow_module.github_pr_payload = original_pr_payload
|
|
7028
|
+
loom_flow_module.github_issue_payload = original_issue_payload
|
|
7029
|
+
loom_flow_module.reconciliation_audit_payload = original_reconciliation_audit
|
|
7030
|
+
loom_flow_module.contains_merged_commit = original_contains
|
|
7031
|
+
|
|
6918
7032
|
rollover_target = base / "active-rollover"
|
|
6919
7033
|
shutil.copytree(baseline, rollover_target)
|
|
6920
7034
|
payload, error = load_command_json(
|