@mc-and-his-agents/loom-installer 0.1.24 → 0.1.25
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 +62 -62
- package/payload/plugin/loom/skills/shared/scripts/fact_chain_support.py +10 -1
- package/payload/plugin/loom/skills/shared/scripts/loom_check.py +37 -0
- package/payload/plugin/loom/skills/shared/scripts/loom_flow.py +1 -1
- package/payload/skills/loom-adopt/.loom-runtime/shared/scripts/fact_chain_support.py +10 -1
- package/payload/skills/loom-adopt/.loom-runtime/shared/scripts/loom_check.py +37 -0
- package/payload/skills/loom-adopt/.loom-runtime/shared/scripts/loom_flow.py +1 -1
- package/payload/skills/loom-handoff/.loom-runtime/shared/scripts/fact_chain_support.py +10 -1
- package/payload/skills/loom-handoff/.loom-runtime/shared/scripts/loom_check.py +37 -0
- package/payload/skills/loom-handoff/.loom-runtime/shared/scripts/loom_flow.py +1 -1
- package/payload/skills/loom-init/.loom-runtime/shared/scripts/fact_chain_support.py +10 -1
- package/payload/skills/loom-init/.loom-runtime/shared/scripts/loom_check.py +37 -0
- package/payload/skills/loom-init/.loom-runtime/shared/scripts/loom_flow.py +1 -1
- package/payload/skills/loom-merge-ready/.loom-runtime/shared/scripts/fact_chain_support.py +10 -1
- package/payload/skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_check.py +37 -0
- package/payload/skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_flow.py +1 -1
- package/payload/skills/loom-pre-review/.loom-runtime/shared/scripts/fact_chain_support.py +10 -1
- package/payload/skills/loom-pre-review/.loom-runtime/shared/scripts/loom_check.py +37 -0
- package/payload/skills/loom-pre-review/.loom-runtime/shared/scripts/loom_flow.py +1 -1
- package/payload/skills/loom-resume/.loom-runtime/shared/scripts/fact_chain_support.py +10 -1
- package/payload/skills/loom-resume/.loom-runtime/shared/scripts/loom_check.py +37 -0
- package/payload/skills/loom-resume/.loom-runtime/shared/scripts/loom_flow.py +1 -1
- package/payload/skills/loom-retire/.loom-runtime/shared/scripts/fact_chain_support.py +10 -1
- package/payload/skills/loom-retire/.loom-runtime/shared/scripts/loom_check.py +37 -0
- package/payload/skills/loom-retire/.loom-runtime/shared/scripts/loom_flow.py +1 -1
- package/payload/skills/loom-review/.loom-runtime/shared/scripts/fact_chain_support.py +10 -1
- package/payload/skills/loom-review/.loom-runtime/shared/scripts/loom_check.py +37 -0
- package/payload/skills/loom-review/.loom-runtime/shared/scripts/loom_flow.py +1 -1
- package/payload/skills/loom-spec-review/.loom-runtime/shared/scripts/fact_chain_support.py +10 -1
- package/payload/skills/loom-spec-review/.loom-runtime/shared/scripts/loom_check.py +37 -0
- package/payload/skills/loom-spec-review/.loom-runtime/shared/scripts/loom_flow.py +1 -1
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": "b3b72c7a74674319f0a7432487fc0e3cbf012b4f",
|
|
6
6
|
"source_ref": "main",
|
|
7
|
-
"built_at": "2026-04-26T23:
|
|
7
|
+
"built_at": "2026-04-26T23:56:30+08:00",
|
|
8
8
|
"runtime": {
|
|
9
9
|
"python_minimum": "3.10",
|
|
10
10
|
"python_recommended": "3.11+"
|
|
@@ -618,8 +618,8 @@
|
|
|
618
618
|
},
|
|
619
619
|
{
|
|
620
620
|
"path": "plugin/loom/skills/shared/scripts/fact_chain_support.py",
|
|
621
|
-
"bytes":
|
|
622
|
-
"sha256": "
|
|
621
|
+
"bytes": 19933,
|
|
622
|
+
"sha256": "0e215e1c83443c8688c984241a8008e5b419f68b8838eecbcfb695857e212923"
|
|
623
623
|
},
|
|
624
624
|
{
|
|
625
625
|
"path": "plugin/loom/skills/shared/scripts/governance_surface.py",
|
|
@@ -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": 287428,
|
|
632
|
+
"sha256": "48bf635cb9fb4f28b38309a81ecb67a48b489d72f83c4907d972230481848ddb"
|
|
633
633
|
},
|
|
634
634
|
{
|
|
635
635
|
"path": "plugin/loom/skills/shared/scripts/loom_flow.py",
|
|
636
|
-
"bytes":
|
|
637
|
-
"sha256": "
|
|
636
|
+
"bytes": 296151,
|
|
637
|
+
"sha256": "f74afa96460de82ef279ec78a0468906ca1db26b867a4fc810025164ae8f7146"
|
|
638
638
|
},
|
|
639
639
|
{
|
|
640
640
|
"path": "plugin/loom/skills/shared/scripts/loom_init.py",
|
|
@@ -1203,8 +1203,8 @@
|
|
|
1203
1203
|
},
|
|
1204
1204
|
{
|
|
1205
1205
|
"path": "skills/loom-adopt/.loom-runtime/shared/scripts/fact_chain_support.py",
|
|
1206
|
-
"bytes":
|
|
1207
|
-
"sha256": "
|
|
1206
|
+
"bytes": 19933,
|
|
1207
|
+
"sha256": "0e215e1c83443c8688c984241a8008e5b419f68b8838eecbcfb695857e212923"
|
|
1208
1208
|
},
|
|
1209
1209
|
{
|
|
1210
1210
|
"path": "skills/loom-adopt/.loom-runtime/shared/scripts/governance_surface.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": 287428,
|
|
1217
|
+
"sha256": "48bf635cb9fb4f28b38309a81ecb67a48b489d72f83c4907d972230481848ddb"
|
|
1218
1218
|
},
|
|
1219
1219
|
{
|
|
1220
1220
|
"path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_flow.py",
|
|
1221
|
-
"bytes":
|
|
1222
|
-
"sha256": "
|
|
1221
|
+
"bytes": 296151,
|
|
1222
|
+
"sha256": "f74afa96460de82ef279ec78a0468906ca1db26b867a4fc810025164ae8f7146"
|
|
1223
1223
|
},
|
|
1224
1224
|
{
|
|
1225
1225
|
"path": "skills/loom-adopt/.loom-runtime/shared/scripts/loom_init.py",
|
|
@@ -1818,8 +1818,8 @@
|
|
|
1818
1818
|
},
|
|
1819
1819
|
{
|
|
1820
1820
|
"path": "skills/loom-handoff/.loom-runtime/shared/scripts/fact_chain_support.py",
|
|
1821
|
-
"bytes":
|
|
1822
|
-
"sha256": "
|
|
1821
|
+
"bytes": 19933,
|
|
1822
|
+
"sha256": "0e215e1c83443c8688c984241a8008e5b419f68b8838eecbcfb695857e212923"
|
|
1823
1823
|
},
|
|
1824
1824
|
{
|
|
1825
1825
|
"path": "skills/loom-handoff/.loom-runtime/shared/scripts/governance_surface.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": 287428,
|
|
1832
|
+
"sha256": "48bf635cb9fb4f28b38309a81ecb67a48b489d72f83c4907d972230481848ddb"
|
|
1833
1833
|
},
|
|
1834
1834
|
{
|
|
1835
1835
|
"path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_flow.py",
|
|
1836
|
-
"bytes":
|
|
1837
|
-
"sha256": "
|
|
1836
|
+
"bytes": 296151,
|
|
1837
|
+
"sha256": "f74afa96460de82ef279ec78a0468906ca1db26b867a4fc810025164ae8f7146"
|
|
1838
1838
|
},
|
|
1839
1839
|
{
|
|
1840
1840
|
"path": "skills/loom-handoff/.loom-runtime/shared/scripts/loom_init.py",
|
|
@@ -2433,8 +2433,8 @@
|
|
|
2433
2433
|
},
|
|
2434
2434
|
{
|
|
2435
2435
|
"path": "skills/loom-init/.loom-runtime/shared/scripts/fact_chain_support.py",
|
|
2436
|
-
"bytes":
|
|
2437
|
-
"sha256": "
|
|
2436
|
+
"bytes": 19933,
|
|
2437
|
+
"sha256": "0e215e1c83443c8688c984241a8008e5b419f68b8838eecbcfb695857e212923"
|
|
2438
2438
|
},
|
|
2439
2439
|
{
|
|
2440
2440
|
"path": "skills/loom-init/.loom-runtime/shared/scripts/governance_surface.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": 287428,
|
|
2447
|
+
"sha256": "48bf635cb9fb4f28b38309a81ecb67a48b489d72f83c4907d972230481848ddb"
|
|
2448
2448
|
},
|
|
2449
2449
|
{
|
|
2450
2450
|
"path": "skills/loom-init/.loom-runtime/shared/scripts/loom_flow.py",
|
|
2451
|
-
"bytes":
|
|
2452
|
-
"sha256": "
|
|
2451
|
+
"bytes": 296151,
|
|
2452
|
+
"sha256": "f74afa96460de82ef279ec78a0468906ca1db26b867a4fc810025164ae8f7146"
|
|
2453
2453
|
},
|
|
2454
2454
|
{
|
|
2455
2455
|
"path": "skills/loom-init/.loom-runtime/shared/scripts/loom_init.py",
|
|
@@ -3053,8 +3053,8 @@
|
|
|
3053
3053
|
},
|
|
3054
3054
|
{
|
|
3055
3055
|
"path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/fact_chain_support.py",
|
|
3056
|
-
"bytes":
|
|
3057
|
-
"sha256": "
|
|
3056
|
+
"bytes": 19933,
|
|
3057
|
+
"sha256": "0e215e1c83443c8688c984241a8008e5b419f68b8838eecbcfb695857e212923"
|
|
3058
3058
|
},
|
|
3059
3059
|
{
|
|
3060
3060
|
"path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/governance_surface.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": 287428,
|
|
3067
|
+
"sha256": "48bf635cb9fb4f28b38309a81ecb67a48b489d72f83c4907d972230481848ddb"
|
|
3068
3068
|
},
|
|
3069
3069
|
{
|
|
3070
3070
|
"path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_flow.py",
|
|
3071
|
-
"bytes":
|
|
3072
|
-
"sha256": "
|
|
3071
|
+
"bytes": 296151,
|
|
3072
|
+
"sha256": "f74afa96460de82ef279ec78a0468906ca1db26b867a4fc810025164ae8f7146"
|
|
3073
3073
|
},
|
|
3074
3074
|
{
|
|
3075
3075
|
"path": "skills/loom-merge-ready/.loom-runtime/shared/scripts/loom_init.py",
|
|
@@ -3668,8 +3668,8 @@
|
|
|
3668
3668
|
},
|
|
3669
3669
|
{
|
|
3670
3670
|
"path": "skills/loom-pre-review/.loom-runtime/shared/scripts/fact_chain_support.py",
|
|
3671
|
-
"bytes":
|
|
3672
|
-
"sha256": "
|
|
3671
|
+
"bytes": 19933,
|
|
3672
|
+
"sha256": "0e215e1c83443c8688c984241a8008e5b419f68b8838eecbcfb695857e212923"
|
|
3673
3673
|
},
|
|
3674
3674
|
{
|
|
3675
3675
|
"path": "skills/loom-pre-review/.loom-runtime/shared/scripts/governance_surface.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": 287428,
|
|
3682
|
+
"sha256": "48bf635cb9fb4f28b38309a81ecb67a48b489d72f83c4907d972230481848ddb"
|
|
3683
3683
|
},
|
|
3684
3684
|
{
|
|
3685
3685
|
"path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_flow.py",
|
|
3686
|
-
"bytes":
|
|
3687
|
-
"sha256": "
|
|
3686
|
+
"bytes": 296151,
|
|
3687
|
+
"sha256": "f74afa96460de82ef279ec78a0468906ca1db26b867a4fc810025164ae8f7146"
|
|
3688
3688
|
},
|
|
3689
3689
|
{
|
|
3690
3690
|
"path": "skills/loom-pre-review/.loom-runtime/shared/scripts/loom_init.py",
|
|
@@ -4283,8 +4283,8 @@
|
|
|
4283
4283
|
},
|
|
4284
4284
|
{
|
|
4285
4285
|
"path": "skills/loom-resume/.loom-runtime/shared/scripts/fact_chain_support.py",
|
|
4286
|
-
"bytes":
|
|
4287
|
-
"sha256": "
|
|
4286
|
+
"bytes": 19933,
|
|
4287
|
+
"sha256": "0e215e1c83443c8688c984241a8008e5b419f68b8838eecbcfb695857e212923"
|
|
4288
4288
|
},
|
|
4289
4289
|
{
|
|
4290
4290
|
"path": "skills/loom-resume/.loom-runtime/shared/scripts/governance_surface.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": 287428,
|
|
4297
|
+
"sha256": "48bf635cb9fb4f28b38309a81ecb67a48b489d72f83c4907d972230481848ddb"
|
|
4298
4298
|
},
|
|
4299
4299
|
{
|
|
4300
4300
|
"path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_flow.py",
|
|
4301
|
-
"bytes":
|
|
4302
|
-
"sha256": "
|
|
4301
|
+
"bytes": 296151,
|
|
4302
|
+
"sha256": "f74afa96460de82ef279ec78a0468906ca1db26b867a4fc810025164ae8f7146"
|
|
4303
4303
|
},
|
|
4304
4304
|
{
|
|
4305
4305
|
"path": "skills/loom-resume/.loom-runtime/shared/scripts/loom_init.py",
|
|
@@ -4898,8 +4898,8 @@
|
|
|
4898
4898
|
},
|
|
4899
4899
|
{
|
|
4900
4900
|
"path": "skills/loom-retire/.loom-runtime/shared/scripts/fact_chain_support.py",
|
|
4901
|
-
"bytes":
|
|
4902
|
-
"sha256": "
|
|
4901
|
+
"bytes": 19933,
|
|
4902
|
+
"sha256": "0e215e1c83443c8688c984241a8008e5b419f68b8838eecbcfb695857e212923"
|
|
4903
4903
|
},
|
|
4904
4904
|
{
|
|
4905
4905
|
"path": "skills/loom-retire/.loom-runtime/shared/scripts/governance_surface.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": 287428,
|
|
4912
|
+
"sha256": "48bf635cb9fb4f28b38309a81ecb67a48b489d72f83c4907d972230481848ddb"
|
|
4913
4913
|
},
|
|
4914
4914
|
{
|
|
4915
4915
|
"path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_flow.py",
|
|
4916
|
-
"bytes":
|
|
4917
|
-
"sha256": "
|
|
4916
|
+
"bytes": 296151,
|
|
4917
|
+
"sha256": "f74afa96460de82ef279ec78a0468906ca1db26b867a4fc810025164ae8f7146"
|
|
4918
4918
|
},
|
|
4919
4919
|
{
|
|
4920
4920
|
"path": "skills/loom-retire/.loom-runtime/shared/scripts/loom_init.py",
|
|
@@ -5513,8 +5513,8 @@
|
|
|
5513
5513
|
},
|
|
5514
5514
|
{
|
|
5515
5515
|
"path": "skills/loom-review/.loom-runtime/shared/scripts/fact_chain_support.py",
|
|
5516
|
-
"bytes":
|
|
5517
|
-
"sha256": "
|
|
5516
|
+
"bytes": 19933,
|
|
5517
|
+
"sha256": "0e215e1c83443c8688c984241a8008e5b419f68b8838eecbcfb695857e212923"
|
|
5518
5518
|
},
|
|
5519
5519
|
{
|
|
5520
5520
|
"path": "skills/loom-review/.loom-runtime/shared/scripts/governance_surface.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": 287428,
|
|
5527
|
+
"sha256": "48bf635cb9fb4f28b38309a81ecb67a48b489d72f83c4907d972230481848ddb"
|
|
5528
5528
|
},
|
|
5529
5529
|
{
|
|
5530
5530
|
"path": "skills/loom-review/.loom-runtime/shared/scripts/loom_flow.py",
|
|
5531
|
-
"bytes":
|
|
5532
|
-
"sha256": "
|
|
5531
|
+
"bytes": 296151,
|
|
5532
|
+
"sha256": "f74afa96460de82ef279ec78a0468906ca1db26b867a4fc810025164ae8f7146"
|
|
5533
5533
|
},
|
|
5534
5534
|
{
|
|
5535
5535
|
"path": "skills/loom-review/.loom-runtime/shared/scripts/loom_init.py",
|
|
@@ -6128,8 +6128,8 @@
|
|
|
6128
6128
|
},
|
|
6129
6129
|
{
|
|
6130
6130
|
"path": "skills/loom-spec-review/.loom-runtime/shared/scripts/fact_chain_support.py",
|
|
6131
|
-
"bytes":
|
|
6132
|
-
"sha256": "
|
|
6131
|
+
"bytes": 19933,
|
|
6132
|
+
"sha256": "0e215e1c83443c8688c984241a8008e5b419f68b8838eecbcfb695857e212923"
|
|
6133
6133
|
},
|
|
6134
6134
|
{
|
|
6135
6135
|
"path": "skills/loom-spec-review/.loom-runtime/shared/scripts/governance_surface.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": 287428,
|
|
6142
|
+
"sha256": "48bf635cb9fb4f28b38309a81ecb67a48b489d72f83c4907d972230481848ddb"
|
|
6143
6143
|
},
|
|
6144
6144
|
{
|
|
6145
6145
|
"path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_flow.py",
|
|
6146
|
-
"bytes":
|
|
6147
|
-
"sha256": "
|
|
6146
|
+
"bytes": 296151,
|
|
6147
|
+
"sha256": "f74afa96460de82ef279ec78a0468906ca1db26b867a4fc810025164ae8f7146"
|
|
6148
6148
|
},
|
|
6149
6149
|
{
|
|
6150
6150
|
"path": "skills/loom-spec-review/.loom-runtime/shared/scripts/loom_init.py",
|
|
@@ -131,6 +131,7 @@ def parse_key_value_section(
|
|
|
131
131
|
return {}, [f"{relative_path}: missing section `{section_name}`"]
|
|
132
132
|
|
|
133
133
|
values: dict[str, str] = {}
|
|
134
|
+
seen: dict[str, str] = {}
|
|
134
135
|
for raw_line in lines:
|
|
135
136
|
stripped = raw_line.strip()
|
|
136
137
|
if not stripped:
|
|
@@ -143,7 +144,15 @@ def parse_key_value_section(
|
|
|
143
144
|
if label not in field_map:
|
|
144
145
|
errors.append(f"{relative_path}: unexpected field `{label}` in `{section_name}`")
|
|
145
146
|
continue
|
|
146
|
-
|
|
147
|
+
canonical = field_map[label]
|
|
148
|
+
if canonical in seen:
|
|
149
|
+
errors.append(
|
|
150
|
+
f"{relative_path}: duplicate field `{label}` in `{section_name}` "
|
|
151
|
+
f"(canonical `{canonical}` already set by `{seen[canonical]}`)"
|
|
152
|
+
)
|
|
153
|
+
continue
|
|
154
|
+
seen[canonical] = label
|
|
155
|
+
values[canonical] = _clean_value(match.group(2))
|
|
147
156
|
|
|
148
157
|
for label, canonical in field_map.items():
|
|
149
158
|
if canonical not in values:
|
|
@@ -2211,6 +2211,43 @@ def check_demo_fact_chain(root: Path) -> list[Failure]:
|
|
|
2211
2211
|
failures.append(Failure("demo-fact-chain", detail))
|
|
2212
2212
|
if report and report.get("fact_chain", {}).get("entry_points", {}).get("status_surface") != ".loom/status/current.md":
|
|
2213
2213
|
failures.append(Failure("demo-fact-chain", "demo fact chain must point status_surface to `.loom/status/current.md`"))
|
|
2214
|
+
with tempfile.TemporaryDirectory(prefix="loom-check-metadata-spoof-") as tmp:
|
|
2215
|
+
spoof_target = Path(tmp) / "new-project"
|
|
2216
|
+
shutil.copytree(target, spoof_target)
|
|
2217
|
+
work_item_path = spoof_target / ".loom/work-items/INIT-0001.md"
|
|
2218
|
+
work_item_text = work_item_path.read_text(encoding="utf-8")
|
|
2219
|
+
work_item_path.write_text(
|
|
2220
|
+
work_item_text.replace(
|
|
2221
|
+
"- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2222
|
+
"- Goal: wrong spoofed goal\n- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2223
|
+
1,
|
|
2224
|
+
),
|
|
2225
|
+
encoding="utf-8",
|
|
2226
|
+
)
|
|
2227
|
+
_, spoof_errors = inspect_fact_chain(spoof_target)
|
|
2228
|
+
if not any("duplicate field `Goal`" in error for error in spoof_errors):
|
|
2229
|
+
failures.append(Failure("demo-fact-chain", "fact-chain parser must reject duplicate canonical metadata fields"))
|
|
2230
|
+
|
|
2231
|
+
runtime_spoof_target = Path(tmp) / "runtime-spoof"
|
|
2232
|
+
shutil.copytree(target, runtime_spoof_target)
|
|
2233
|
+
status_path = runtime_spoof_target / ".loom/status/current.md"
|
|
2234
|
+
status_text = status_path.read_text(encoding="utf-8")
|
|
2235
|
+
status_path.write_text(
|
|
2236
|
+
status_text.replace(
|
|
2237
|
+
"- Run Entry: not_applicable\n",
|
|
2238
|
+
"- Run Entry: wrong-spoofed-run\n- Run Entry: not_applicable\n",
|
|
2239
|
+
1,
|
|
2240
|
+
),
|
|
2241
|
+
encoding="utf-8",
|
|
2242
|
+
)
|
|
2243
|
+
payload, error = load_command_json(
|
|
2244
|
+
root,
|
|
2245
|
+
["python3", "tools/loom_flow.py", "fact-chain", "--target", str(runtime_spoof_target)],
|
|
2246
|
+
)
|
|
2247
|
+
if error:
|
|
2248
|
+
failures.append(Failure("demo-fact-chain", f"`flow fact-chain` metadata spoof sample failed unexpectedly: {error}"))
|
|
2249
|
+
elif payload.get("result") != "block":
|
|
2250
|
+
failures.append(Failure("demo-fact-chain", "`flow fact-chain` must not legacy-fallback past duplicate Runtime Evidence fields"))
|
|
2214
2251
|
return failures
|
|
2215
2252
|
|
|
2216
2253
|
|
|
@@ -2643,7 +2643,7 @@ def build_default_review_prompt(
|
|
|
2643
2643
|
|
|
2644
2644
|
def load_fact_chain_report(target_root: Path, output_relative: str) -> tuple[dict[str, Any], list[str]]:
|
|
2645
2645
|
report, errors = inspect_fact_chain(target_root, output_relative)
|
|
2646
|
-
if errors and all("Runtime Evidence" in message for message in errors):
|
|
2646
|
+
if errors and all("missing section `Runtime Evidence`" in message for message in errors):
|
|
2647
2647
|
report, errors = inspect_fact_chain_legacy(target_root, output_relative)
|
|
2648
2648
|
if errors:
|
|
2649
2649
|
return {}, errors
|
|
@@ -131,6 +131,7 @@ def parse_key_value_section(
|
|
|
131
131
|
return {}, [f"{relative_path}: missing section `{section_name}`"]
|
|
132
132
|
|
|
133
133
|
values: dict[str, str] = {}
|
|
134
|
+
seen: dict[str, str] = {}
|
|
134
135
|
for raw_line in lines:
|
|
135
136
|
stripped = raw_line.strip()
|
|
136
137
|
if not stripped:
|
|
@@ -143,7 +144,15 @@ def parse_key_value_section(
|
|
|
143
144
|
if label not in field_map:
|
|
144
145
|
errors.append(f"{relative_path}: unexpected field `{label}` in `{section_name}`")
|
|
145
146
|
continue
|
|
146
|
-
|
|
147
|
+
canonical = field_map[label]
|
|
148
|
+
if canonical in seen:
|
|
149
|
+
errors.append(
|
|
150
|
+
f"{relative_path}: duplicate field `{label}` in `{section_name}` "
|
|
151
|
+
f"(canonical `{canonical}` already set by `{seen[canonical]}`)"
|
|
152
|
+
)
|
|
153
|
+
continue
|
|
154
|
+
seen[canonical] = label
|
|
155
|
+
values[canonical] = _clean_value(match.group(2))
|
|
147
156
|
|
|
148
157
|
for label, canonical in field_map.items():
|
|
149
158
|
if canonical not in values:
|
|
@@ -2211,6 +2211,43 @@ def check_demo_fact_chain(root: Path) -> list[Failure]:
|
|
|
2211
2211
|
failures.append(Failure("demo-fact-chain", detail))
|
|
2212
2212
|
if report and report.get("fact_chain", {}).get("entry_points", {}).get("status_surface") != ".loom/status/current.md":
|
|
2213
2213
|
failures.append(Failure("demo-fact-chain", "demo fact chain must point status_surface to `.loom/status/current.md`"))
|
|
2214
|
+
with tempfile.TemporaryDirectory(prefix="loom-check-metadata-spoof-") as tmp:
|
|
2215
|
+
spoof_target = Path(tmp) / "new-project"
|
|
2216
|
+
shutil.copytree(target, spoof_target)
|
|
2217
|
+
work_item_path = spoof_target / ".loom/work-items/INIT-0001.md"
|
|
2218
|
+
work_item_text = work_item_path.read_text(encoding="utf-8")
|
|
2219
|
+
work_item_path.write_text(
|
|
2220
|
+
work_item_text.replace(
|
|
2221
|
+
"- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2222
|
+
"- Goal: wrong spoofed goal\n- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2223
|
+
1,
|
|
2224
|
+
),
|
|
2225
|
+
encoding="utf-8",
|
|
2226
|
+
)
|
|
2227
|
+
_, spoof_errors = inspect_fact_chain(spoof_target)
|
|
2228
|
+
if not any("duplicate field `Goal`" in error for error in spoof_errors):
|
|
2229
|
+
failures.append(Failure("demo-fact-chain", "fact-chain parser must reject duplicate canonical metadata fields"))
|
|
2230
|
+
|
|
2231
|
+
runtime_spoof_target = Path(tmp) / "runtime-spoof"
|
|
2232
|
+
shutil.copytree(target, runtime_spoof_target)
|
|
2233
|
+
status_path = runtime_spoof_target / ".loom/status/current.md"
|
|
2234
|
+
status_text = status_path.read_text(encoding="utf-8")
|
|
2235
|
+
status_path.write_text(
|
|
2236
|
+
status_text.replace(
|
|
2237
|
+
"- Run Entry: not_applicable\n",
|
|
2238
|
+
"- Run Entry: wrong-spoofed-run\n- Run Entry: not_applicable\n",
|
|
2239
|
+
1,
|
|
2240
|
+
),
|
|
2241
|
+
encoding="utf-8",
|
|
2242
|
+
)
|
|
2243
|
+
payload, error = load_command_json(
|
|
2244
|
+
root,
|
|
2245
|
+
["python3", "tools/loom_flow.py", "fact-chain", "--target", str(runtime_spoof_target)],
|
|
2246
|
+
)
|
|
2247
|
+
if error:
|
|
2248
|
+
failures.append(Failure("demo-fact-chain", f"`flow fact-chain` metadata spoof sample failed unexpectedly: {error}"))
|
|
2249
|
+
elif payload.get("result") != "block":
|
|
2250
|
+
failures.append(Failure("demo-fact-chain", "`flow fact-chain` must not legacy-fallback past duplicate Runtime Evidence fields"))
|
|
2214
2251
|
return failures
|
|
2215
2252
|
|
|
2216
2253
|
|
|
@@ -2643,7 +2643,7 @@ def build_default_review_prompt(
|
|
|
2643
2643
|
|
|
2644
2644
|
def load_fact_chain_report(target_root: Path, output_relative: str) -> tuple[dict[str, Any], list[str]]:
|
|
2645
2645
|
report, errors = inspect_fact_chain(target_root, output_relative)
|
|
2646
|
-
if errors and all("Runtime Evidence" in message for message in errors):
|
|
2646
|
+
if errors and all("missing section `Runtime Evidence`" in message for message in errors):
|
|
2647
2647
|
report, errors = inspect_fact_chain_legacy(target_root, output_relative)
|
|
2648
2648
|
if errors:
|
|
2649
2649
|
return {}, errors
|
|
@@ -131,6 +131,7 @@ def parse_key_value_section(
|
|
|
131
131
|
return {}, [f"{relative_path}: missing section `{section_name}`"]
|
|
132
132
|
|
|
133
133
|
values: dict[str, str] = {}
|
|
134
|
+
seen: dict[str, str] = {}
|
|
134
135
|
for raw_line in lines:
|
|
135
136
|
stripped = raw_line.strip()
|
|
136
137
|
if not stripped:
|
|
@@ -143,7 +144,15 @@ def parse_key_value_section(
|
|
|
143
144
|
if label not in field_map:
|
|
144
145
|
errors.append(f"{relative_path}: unexpected field `{label}` in `{section_name}`")
|
|
145
146
|
continue
|
|
146
|
-
|
|
147
|
+
canonical = field_map[label]
|
|
148
|
+
if canonical in seen:
|
|
149
|
+
errors.append(
|
|
150
|
+
f"{relative_path}: duplicate field `{label}` in `{section_name}` "
|
|
151
|
+
f"(canonical `{canonical}` already set by `{seen[canonical]}`)"
|
|
152
|
+
)
|
|
153
|
+
continue
|
|
154
|
+
seen[canonical] = label
|
|
155
|
+
values[canonical] = _clean_value(match.group(2))
|
|
147
156
|
|
|
148
157
|
for label, canonical in field_map.items():
|
|
149
158
|
if canonical not in values:
|
|
@@ -2211,6 +2211,43 @@ def check_demo_fact_chain(root: Path) -> list[Failure]:
|
|
|
2211
2211
|
failures.append(Failure("demo-fact-chain", detail))
|
|
2212
2212
|
if report and report.get("fact_chain", {}).get("entry_points", {}).get("status_surface") != ".loom/status/current.md":
|
|
2213
2213
|
failures.append(Failure("demo-fact-chain", "demo fact chain must point status_surface to `.loom/status/current.md`"))
|
|
2214
|
+
with tempfile.TemporaryDirectory(prefix="loom-check-metadata-spoof-") as tmp:
|
|
2215
|
+
spoof_target = Path(tmp) / "new-project"
|
|
2216
|
+
shutil.copytree(target, spoof_target)
|
|
2217
|
+
work_item_path = spoof_target / ".loom/work-items/INIT-0001.md"
|
|
2218
|
+
work_item_text = work_item_path.read_text(encoding="utf-8")
|
|
2219
|
+
work_item_path.write_text(
|
|
2220
|
+
work_item_text.replace(
|
|
2221
|
+
"- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2222
|
+
"- Goal: wrong spoofed goal\n- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2223
|
+
1,
|
|
2224
|
+
),
|
|
2225
|
+
encoding="utf-8",
|
|
2226
|
+
)
|
|
2227
|
+
_, spoof_errors = inspect_fact_chain(spoof_target)
|
|
2228
|
+
if not any("duplicate field `Goal`" in error for error in spoof_errors):
|
|
2229
|
+
failures.append(Failure("demo-fact-chain", "fact-chain parser must reject duplicate canonical metadata fields"))
|
|
2230
|
+
|
|
2231
|
+
runtime_spoof_target = Path(tmp) / "runtime-spoof"
|
|
2232
|
+
shutil.copytree(target, runtime_spoof_target)
|
|
2233
|
+
status_path = runtime_spoof_target / ".loom/status/current.md"
|
|
2234
|
+
status_text = status_path.read_text(encoding="utf-8")
|
|
2235
|
+
status_path.write_text(
|
|
2236
|
+
status_text.replace(
|
|
2237
|
+
"- Run Entry: not_applicable\n",
|
|
2238
|
+
"- Run Entry: wrong-spoofed-run\n- Run Entry: not_applicable\n",
|
|
2239
|
+
1,
|
|
2240
|
+
),
|
|
2241
|
+
encoding="utf-8",
|
|
2242
|
+
)
|
|
2243
|
+
payload, error = load_command_json(
|
|
2244
|
+
root,
|
|
2245
|
+
["python3", "tools/loom_flow.py", "fact-chain", "--target", str(runtime_spoof_target)],
|
|
2246
|
+
)
|
|
2247
|
+
if error:
|
|
2248
|
+
failures.append(Failure("demo-fact-chain", f"`flow fact-chain` metadata spoof sample failed unexpectedly: {error}"))
|
|
2249
|
+
elif payload.get("result") != "block":
|
|
2250
|
+
failures.append(Failure("demo-fact-chain", "`flow fact-chain` must not legacy-fallback past duplicate Runtime Evidence fields"))
|
|
2214
2251
|
return failures
|
|
2215
2252
|
|
|
2216
2253
|
|
|
@@ -2643,7 +2643,7 @@ def build_default_review_prompt(
|
|
|
2643
2643
|
|
|
2644
2644
|
def load_fact_chain_report(target_root: Path, output_relative: str) -> tuple[dict[str, Any], list[str]]:
|
|
2645
2645
|
report, errors = inspect_fact_chain(target_root, output_relative)
|
|
2646
|
-
if errors and all("Runtime Evidence" in message for message in errors):
|
|
2646
|
+
if errors and all("missing section `Runtime Evidence`" in message for message in errors):
|
|
2647
2647
|
report, errors = inspect_fact_chain_legacy(target_root, output_relative)
|
|
2648
2648
|
if errors:
|
|
2649
2649
|
return {}, errors
|
|
@@ -131,6 +131,7 @@ def parse_key_value_section(
|
|
|
131
131
|
return {}, [f"{relative_path}: missing section `{section_name}`"]
|
|
132
132
|
|
|
133
133
|
values: dict[str, str] = {}
|
|
134
|
+
seen: dict[str, str] = {}
|
|
134
135
|
for raw_line in lines:
|
|
135
136
|
stripped = raw_line.strip()
|
|
136
137
|
if not stripped:
|
|
@@ -143,7 +144,15 @@ def parse_key_value_section(
|
|
|
143
144
|
if label not in field_map:
|
|
144
145
|
errors.append(f"{relative_path}: unexpected field `{label}` in `{section_name}`")
|
|
145
146
|
continue
|
|
146
|
-
|
|
147
|
+
canonical = field_map[label]
|
|
148
|
+
if canonical in seen:
|
|
149
|
+
errors.append(
|
|
150
|
+
f"{relative_path}: duplicate field `{label}` in `{section_name}` "
|
|
151
|
+
f"(canonical `{canonical}` already set by `{seen[canonical]}`)"
|
|
152
|
+
)
|
|
153
|
+
continue
|
|
154
|
+
seen[canonical] = label
|
|
155
|
+
values[canonical] = _clean_value(match.group(2))
|
|
147
156
|
|
|
148
157
|
for label, canonical in field_map.items():
|
|
149
158
|
if canonical not in values:
|
|
@@ -2211,6 +2211,43 @@ def check_demo_fact_chain(root: Path) -> list[Failure]:
|
|
|
2211
2211
|
failures.append(Failure("demo-fact-chain", detail))
|
|
2212
2212
|
if report and report.get("fact_chain", {}).get("entry_points", {}).get("status_surface") != ".loom/status/current.md":
|
|
2213
2213
|
failures.append(Failure("demo-fact-chain", "demo fact chain must point status_surface to `.loom/status/current.md`"))
|
|
2214
|
+
with tempfile.TemporaryDirectory(prefix="loom-check-metadata-spoof-") as tmp:
|
|
2215
|
+
spoof_target = Path(tmp) / "new-project"
|
|
2216
|
+
shutil.copytree(target, spoof_target)
|
|
2217
|
+
work_item_path = spoof_target / ".loom/work-items/INIT-0001.md"
|
|
2218
|
+
work_item_text = work_item_path.read_text(encoding="utf-8")
|
|
2219
|
+
work_item_path.write_text(
|
|
2220
|
+
work_item_text.replace(
|
|
2221
|
+
"- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2222
|
+
"- Goal: wrong spoofed goal\n- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2223
|
+
1,
|
|
2224
|
+
),
|
|
2225
|
+
encoding="utf-8",
|
|
2226
|
+
)
|
|
2227
|
+
_, spoof_errors = inspect_fact_chain(spoof_target)
|
|
2228
|
+
if not any("duplicate field `Goal`" in error for error in spoof_errors):
|
|
2229
|
+
failures.append(Failure("demo-fact-chain", "fact-chain parser must reject duplicate canonical metadata fields"))
|
|
2230
|
+
|
|
2231
|
+
runtime_spoof_target = Path(tmp) / "runtime-spoof"
|
|
2232
|
+
shutil.copytree(target, runtime_spoof_target)
|
|
2233
|
+
status_path = runtime_spoof_target / ".loom/status/current.md"
|
|
2234
|
+
status_text = status_path.read_text(encoding="utf-8")
|
|
2235
|
+
status_path.write_text(
|
|
2236
|
+
status_text.replace(
|
|
2237
|
+
"- Run Entry: not_applicable\n",
|
|
2238
|
+
"- Run Entry: wrong-spoofed-run\n- Run Entry: not_applicable\n",
|
|
2239
|
+
1,
|
|
2240
|
+
),
|
|
2241
|
+
encoding="utf-8",
|
|
2242
|
+
)
|
|
2243
|
+
payload, error = load_command_json(
|
|
2244
|
+
root,
|
|
2245
|
+
["python3", "tools/loom_flow.py", "fact-chain", "--target", str(runtime_spoof_target)],
|
|
2246
|
+
)
|
|
2247
|
+
if error:
|
|
2248
|
+
failures.append(Failure("demo-fact-chain", f"`flow fact-chain` metadata spoof sample failed unexpectedly: {error}"))
|
|
2249
|
+
elif payload.get("result") != "block":
|
|
2250
|
+
failures.append(Failure("demo-fact-chain", "`flow fact-chain` must not legacy-fallback past duplicate Runtime Evidence fields"))
|
|
2214
2251
|
return failures
|
|
2215
2252
|
|
|
2216
2253
|
|
|
@@ -2643,7 +2643,7 @@ def build_default_review_prompt(
|
|
|
2643
2643
|
|
|
2644
2644
|
def load_fact_chain_report(target_root: Path, output_relative: str) -> tuple[dict[str, Any], list[str]]:
|
|
2645
2645
|
report, errors = inspect_fact_chain(target_root, output_relative)
|
|
2646
|
-
if errors and all("Runtime Evidence" in message for message in errors):
|
|
2646
|
+
if errors and all("missing section `Runtime Evidence`" in message for message in errors):
|
|
2647
2647
|
report, errors = inspect_fact_chain_legacy(target_root, output_relative)
|
|
2648
2648
|
if errors:
|
|
2649
2649
|
return {}, errors
|
|
@@ -131,6 +131,7 @@ def parse_key_value_section(
|
|
|
131
131
|
return {}, [f"{relative_path}: missing section `{section_name}`"]
|
|
132
132
|
|
|
133
133
|
values: dict[str, str] = {}
|
|
134
|
+
seen: dict[str, str] = {}
|
|
134
135
|
for raw_line in lines:
|
|
135
136
|
stripped = raw_line.strip()
|
|
136
137
|
if not stripped:
|
|
@@ -143,7 +144,15 @@ def parse_key_value_section(
|
|
|
143
144
|
if label not in field_map:
|
|
144
145
|
errors.append(f"{relative_path}: unexpected field `{label}` in `{section_name}`")
|
|
145
146
|
continue
|
|
146
|
-
|
|
147
|
+
canonical = field_map[label]
|
|
148
|
+
if canonical in seen:
|
|
149
|
+
errors.append(
|
|
150
|
+
f"{relative_path}: duplicate field `{label}` in `{section_name}` "
|
|
151
|
+
f"(canonical `{canonical}` already set by `{seen[canonical]}`)"
|
|
152
|
+
)
|
|
153
|
+
continue
|
|
154
|
+
seen[canonical] = label
|
|
155
|
+
values[canonical] = _clean_value(match.group(2))
|
|
147
156
|
|
|
148
157
|
for label, canonical in field_map.items():
|
|
149
158
|
if canonical not in values:
|
|
@@ -2211,6 +2211,43 @@ def check_demo_fact_chain(root: Path) -> list[Failure]:
|
|
|
2211
2211
|
failures.append(Failure("demo-fact-chain", detail))
|
|
2212
2212
|
if report and report.get("fact_chain", {}).get("entry_points", {}).get("status_surface") != ".loom/status/current.md":
|
|
2213
2213
|
failures.append(Failure("demo-fact-chain", "demo fact chain must point status_surface to `.loom/status/current.md`"))
|
|
2214
|
+
with tempfile.TemporaryDirectory(prefix="loom-check-metadata-spoof-") as tmp:
|
|
2215
|
+
spoof_target = Path(tmp) / "new-project"
|
|
2216
|
+
shutil.copytree(target, spoof_target)
|
|
2217
|
+
work_item_path = spoof_target / ".loom/work-items/INIT-0001.md"
|
|
2218
|
+
work_item_text = work_item_path.read_text(encoding="utf-8")
|
|
2219
|
+
work_item_path.write_text(
|
|
2220
|
+
work_item_text.replace(
|
|
2221
|
+
"- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2222
|
+
"- Goal: wrong spoofed goal\n- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2223
|
+
1,
|
|
2224
|
+
),
|
|
2225
|
+
encoding="utf-8",
|
|
2226
|
+
)
|
|
2227
|
+
_, spoof_errors = inspect_fact_chain(spoof_target)
|
|
2228
|
+
if not any("duplicate field `Goal`" in error for error in spoof_errors):
|
|
2229
|
+
failures.append(Failure("demo-fact-chain", "fact-chain parser must reject duplicate canonical metadata fields"))
|
|
2230
|
+
|
|
2231
|
+
runtime_spoof_target = Path(tmp) / "runtime-spoof"
|
|
2232
|
+
shutil.copytree(target, runtime_spoof_target)
|
|
2233
|
+
status_path = runtime_spoof_target / ".loom/status/current.md"
|
|
2234
|
+
status_text = status_path.read_text(encoding="utf-8")
|
|
2235
|
+
status_path.write_text(
|
|
2236
|
+
status_text.replace(
|
|
2237
|
+
"- Run Entry: not_applicable\n",
|
|
2238
|
+
"- Run Entry: wrong-spoofed-run\n- Run Entry: not_applicable\n",
|
|
2239
|
+
1,
|
|
2240
|
+
),
|
|
2241
|
+
encoding="utf-8",
|
|
2242
|
+
)
|
|
2243
|
+
payload, error = load_command_json(
|
|
2244
|
+
root,
|
|
2245
|
+
["python3", "tools/loom_flow.py", "fact-chain", "--target", str(runtime_spoof_target)],
|
|
2246
|
+
)
|
|
2247
|
+
if error:
|
|
2248
|
+
failures.append(Failure("demo-fact-chain", f"`flow fact-chain` metadata spoof sample failed unexpectedly: {error}"))
|
|
2249
|
+
elif payload.get("result") != "block":
|
|
2250
|
+
failures.append(Failure("demo-fact-chain", "`flow fact-chain` must not legacy-fallback past duplicate Runtime Evidence fields"))
|
|
2214
2251
|
return failures
|
|
2215
2252
|
|
|
2216
2253
|
|
|
@@ -2643,7 +2643,7 @@ def build_default_review_prompt(
|
|
|
2643
2643
|
|
|
2644
2644
|
def load_fact_chain_report(target_root: Path, output_relative: str) -> tuple[dict[str, Any], list[str]]:
|
|
2645
2645
|
report, errors = inspect_fact_chain(target_root, output_relative)
|
|
2646
|
-
if errors and all("Runtime Evidence" in message for message in errors):
|
|
2646
|
+
if errors and all("missing section `Runtime Evidence`" in message for message in errors):
|
|
2647
2647
|
report, errors = inspect_fact_chain_legacy(target_root, output_relative)
|
|
2648
2648
|
if errors:
|
|
2649
2649
|
return {}, errors
|
|
@@ -131,6 +131,7 @@ def parse_key_value_section(
|
|
|
131
131
|
return {}, [f"{relative_path}: missing section `{section_name}`"]
|
|
132
132
|
|
|
133
133
|
values: dict[str, str] = {}
|
|
134
|
+
seen: dict[str, str] = {}
|
|
134
135
|
for raw_line in lines:
|
|
135
136
|
stripped = raw_line.strip()
|
|
136
137
|
if not stripped:
|
|
@@ -143,7 +144,15 @@ def parse_key_value_section(
|
|
|
143
144
|
if label not in field_map:
|
|
144
145
|
errors.append(f"{relative_path}: unexpected field `{label}` in `{section_name}`")
|
|
145
146
|
continue
|
|
146
|
-
|
|
147
|
+
canonical = field_map[label]
|
|
148
|
+
if canonical in seen:
|
|
149
|
+
errors.append(
|
|
150
|
+
f"{relative_path}: duplicate field `{label}` in `{section_name}` "
|
|
151
|
+
f"(canonical `{canonical}` already set by `{seen[canonical]}`)"
|
|
152
|
+
)
|
|
153
|
+
continue
|
|
154
|
+
seen[canonical] = label
|
|
155
|
+
values[canonical] = _clean_value(match.group(2))
|
|
147
156
|
|
|
148
157
|
for label, canonical in field_map.items():
|
|
149
158
|
if canonical not in values:
|
|
@@ -2211,6 +2211,43 @@ def check_demo_fact_chain(root: Path) -> list[Failure]:
|
|
|
2211
2211
|
failures.append(Failure("demo-fact-chain", detail))
|
|
2212
2212
|
if report and report.get("fact_chain", {}).get("entry_points", {}).get("status_surface") != ".loom/status/current.md":
|
|
2213
2213
|
failures.append(Failure("demo-fact-chain", "demo fact chain must point status_surface to `.loom/status/current.md`"))
|
|
2214
|
+
with tempfile.TemporaryDirectory(prefix="loom-check-metadata-spoof-") as tmp:
|
|
2215
|
+
spoof_target = Path(tmp) / "new-project"
|
|
2216
|
+
shutil.copytree(target, spoof_target)
|
|
2217
|
+
work_item_path = spoof_target / ".loom/work-items/INIT-0001.md"
|
|
2218
|
+
work_item_text = work_item_path.read_text(encoding="utf-8")
|
|
2219
|
+
work_item_path.write_text(
|
|
2220
|
+
work_item_text.replace(
|
|
2221
|
+
"- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2222
|
+
"- Goal: wrong spoofed goal\n- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2223
|
+
1,
|
|
2224
|
+
),
|
|
2225
|
+
encoding="utf-8",
|
|
2226
|
+
)
|
|
2227
|
+
_, spoof_errors = inspect_fact_chain(spoof_target)
|
|
2228
|
+
if not any("duplicate field `Goal`" in error for error in spoof_errors):
|
|
2229
|
+
failures.append(Failure("demo-fact-chain", "fact-chain parser must reject duplicate canonical metadata fields"))
|
|
2230
|
+
|
|
2231
|
+
runtime_spoof_target = Path(tmp) / "runtime-spoof"
|
|
2232
|
+
shutil.copytree(target, runtime_spoof_target)
|
|
2233
|
+
status_path = runtime_spoof_target / ".loom/status/current.md"
|
|
2234
|
+
status_text = status_path.read_text(encoding="utf-8")
|
|
2235
|
+
status_path.write_text(
|
|
2236
|
+
status_text.replace(
|
|
2237
|
+
"- Run Entry: not_applicable\n",
|
|
2238
|
+
"- Run Entry: wrong-spoofed-run\n- Run Entry: not_applicable\n",
|
|
2239
|
+
1,
|
|
2240
|
+
),
|
|
2241
|
+
encoding="utf-8",
|
|
2242
|
+
)
|
|
2243
|
+
payload, error = load_command_json(
|
|
2244
|
+
root,
|
|
2245
|
+
["python3", "tools/loom_flow.py", "fact-chain", "--target", str(runtime_spoof_target)],
|
|
2246
|
+
)
|
|
2247
|
+
if error:
|
|
2248
|
+
failures.append(Failure("demo-fact-chain", f"`flow fact-chain` metadata spoof sample failed unexpectedly: {error}"))
|
|
2249
|
+
elif payload.get("result") != "block":
|
|
2250
|
+
failures.append(Failure("demo-fact-chain", "`flow fact-chain` must not legacy-fallback past duplicate Runtime Evidence fields"))
|
|
2214
2251
|
return failures
|
|
2215
2252
|
|
|
2216
2253
|
|
|
@@ -2643,7 +2643,7 @@ def build_default_review_prompt(
|
|
|
2643
2643
|
|
|
2644
2644
|
def load_fact_chain_report(target_root: Path, output_relative: str) -> tuple[dict[str, Any], list[str]]:
|
|
2645
2645
|
report, errors = inspect_fact_chain(target_root, output_relative)
|
|
2646
|
-
if errors and all("Runtime Evidence" in message for message in errors):
|
|
2646
|
+
if errors and all("missing section `Runtime Evidence`" in message for message in errors):
|
|
2647
2647
|
report, errors = inspect_fact_chain_legacy(target_root, output_relative)
|
|
2648
2648
|
if errors:
|
|
2649
2649
|
return {}, errors
|
|
@@ -131,6 +131,7 @@ def parse_key_value_section(
|
|
|
131
131
|
return {}, [f"{relative_path}: missing section `{section_name}`"]
|
|
132
132
|
|
|
133
133
|
values: dict[str, str] = {}
|
|
134
|
+
seen: dict[str, str] = {}
|
|
134
135
|
for raw_line in lines:
|
|
135
136
|
stripped = raw_line.strip()
|
|
136
137
|
if not stripped:
|
|
@@ -143,7 +144,15 @@ def parse_key_value_section(
|
|
|
143
144
|
if label not in field_map:
|
|
144
145
|
errors.append(f"{relative_path}: unexpected field `{label}` in `{section_name}`")
|
|
145
146
|
continue
|
|
146
|
-
|
|
147
|
+
canonical = field_map[label]
|
|
148
|
+
if canonical in seen:
|
|
149
|
+
errors.append(
|
|
150
|
+
f"{relative_path}: duplicate field `{label}` in `{section_name}` "
|
|
151
|
+
f"(canonical `{canonical}` already set by `{seen[canonical]}`)"
|
|
152
|
+
)
|
|
153
|
+
continue
|
|
154
|
+
seen[canonical] = label
|
|
155
|
+
values[canonical] = _clean_value(match.group(2))
|
|
147
156
|
|
|
148
157
|
for label, canonical in field_map.items():
|
|
149
158
|
if canonical not in values:
|
|
@@ -2211,6 +2211,43 @@ def check_demo_fact_chain(root: Path) -> list[Failure]:
|
|
|
2211
2211
|
failures.append(Failure("demo-fact-chain", detail))
|
|
2212
2212
|
if report and report.get("fact_chain", {}).get("entry_points", {}).get("status_surface") != ".loom/status/current.md":
|
|
2213
2213
|
failures.append(Failure("demo-fact-chain", "demo fact chain must point status_surface to `.loom/status/current.md`"))
|
|
2214
|
+
with tempfile.TemporaryDirectory(prefix="loom-check-metadata-spoof-") as tmp:
|
|
2215
|
+
spoof_target = Path(tmp) / "new-project"
|
|
2216
|
+
shutil.copytree(target, spoof_target)
|
|
2217
|
+
work_item_path = spoof_target / ".loom/work-items/INIT-0001.md"
|
|
2218
|
+
work_item_text = work_item_path.read_text(encoding="utf-8")
|
|
2219
|
+
work_item_path.write_text(
|
|
2220
|
+
work_item_text.replace(
|
|
2221
|
+
"- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2222
|
+
"- Goal: wrong spoofed goal\n- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2223
|
+
1,
|
|
2224
|
+
),
|
|
2225
|
+
encoding="utf-8",
|
|
2226
|
+
)
|
|
2227
|
+
_, spoof_errors = inspect_fact_chain(spoof_target)
|
|
2228
|
+
if not any("duplicate field `Goal`" in error for error in spoof_errors):
|
|
2229
|
+
failures.append(Failure("demo-fact-chain", "fact-chain parser must reject duplicate canonical metadata fields"))
|
|
2230
|
+
|
|
2231
|
+
runtime_spoof_target = Path(tmp) / "runtime-spoof"
|
|
2232
|
+
shutil.copytree(target, runtime_spoof_target)
|
|
2233
|
+
status_path = runtime_spoof_target / ".loom/status/current.md"
|
|
2234
|
+
status_text = status_path.read_text(encoding="utf-8")
|
|
2235
|
+
status_path.write_text(
|
|
2236
|
+
status_text.replace(
|
|
2237
|
+
"- Run Entry: not_applicable\n",
|
|
2238
|
+
"- Run Entry: wrong-spoofed-run\n- Run Entry: not_applicable\n",
|
|
2239
|
+
1,
|
|
2240
|
+
),
|
|
2241
|
+
encoding="utf-8",
|
|
2242
|
+
)
|
|
2243
|
+
payload, error = load_command_json(
|
|
2244
|
+
root,
|
|
2245
|
+
["python3", "tools/loom_flow.py", "fact-chain", "--target", str(runtime_spoof_target)],
|
|
2246
|
+
)
|
|
2247
|
+
if error:
|
|
2248
|
+
failures.append(Failure("demo-fact-chain", f"`flow fact-chain` metadata spoof sample failed unexpectedly: {error}"))
|
|
2249
|
+
elif payload.get("result") != "block":
|
|
2250
|
+
failures.append(Failure("demo-fact-chain", "`flow fact-chain` must not legacy-fallback past duplicate Runtime Evidence fields"))
|
|
2214
2251
|
return failures
|
|
2215
2252
|
|
|
2216
2253
|
|
|
@@ -2643,7 +2643,7 @@ def build_default_review_prompt(
|
|
|
2643
2643
|
|
|
2644
2644
|
def load_fact_chain_report(target_root: Path, output_relative: str) -> tuple[dict[str, Any], list[str]]:
|
|
2645
2645
|
report, errors = inspect_fact_chain(target_root, output_relative)
|
|
2646
|
-
if errors and all("Runtime Evidence" in message for message in errors):
|
|
2646
|
+
if errors and all("missing section `Runtime Evidence`" in message for message in errors):
|
|
2647
2647
|
report, errors = inspect_fact_chain_legacy(target_root, output_relative)
|
|
2648
2648
|
if errors:
|
|
2649
2649
|
return {}, errors
|
|
@@ -131,6 +131,7 @@ def parse_key_value_section(
|
|
|
131
131
|
return {}, [f"{relative_path}: missing section `{section_name}`"]
|
|
132
132
|
|
|
133
133
|
values: dict[str, str] = {}
|
|
134
|
+
seen: dict[str, str] = {}
|
|
134
135
|
for raw_line in lines:
|
|
135
136
|
stripped = raw_line.strip()
|
|
136
137
|
if not stripped:
|
|
@@ -143,7 +144,15 @@ def parse_key_value_section(
|
|
|
143
144
|
if label not in field_map:
|
|
144
145
|
errors.append(f"{relative_path}: unexpected field `{label}` in `{section_name}`")
|
|
145
146
|
continue
|
|
146
|
-
|
|
147
|
+
canonical = field_map[label]
|
|
148
|
+
if canonical in seen:
|
|
149
|
+
errors.append(
|
|
150
|
+
f"{relative_path}: duplicate field `{label}` in `{section_name}` "
|
|
151
|
+
f"(canonical `{canonical}` already set by `{seen[canonical]}`)"
|
|
152
|
+
)
|
|
153
|
+
continue
|
|
154
|
+
seen[canonical] = label
|
|
155
|
+
values[canonical] = _clean_value(match.group(2))
|
|
147
156
|
|
|
148
157
|
for label, canonical in field_map.items():
|
|
149
158
|
if canonical not in values:
|
|
@@ -2211,6 +2211,43 @@ def check_demo_fact_chain(root: Path) -> list[Failure]:
|
|
|
2211
2211
|
failures.append(Failure("demo-fact-chain", detail))
|
|
2212
2212
|
if report and report.get("fact_chain", {}).get("entry_points", {}).get("status_surface") != ".loom/status/current.md":
|
|
2213
2213
|
failures.append(Failure("demo-fact-chain", "demo fact chain must point status_surface to `.loom/status/current.md`"))
|
|
2214
|
+
with tempfile.TemporaryDirectory(prefix="loom-check-metadata-spoof-") as tmp:
|
|
2215
|
+
spoof_target = Path(tmp) / "new-project"
|
|
2216
|
+
shutil.copytree(target, spoof_target)
|
|
2217
|
+
work_item_path = spoof_target / ".loom/work-items/INIT-0001.md"
|
|
2218
|
+
work_item_text = work_item_path.read_text(encoding="utf-8")
|
|
2219
|
+
work_item_path.write_text(
|
|
2220
|
+
work_item_text.replace(
|
|
2221
|
+
"- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2222
|
+
"- Goal: wrong spoofed goal\n- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2223
|
+
1,
|
|
2224
|
+
),
|
|
2225
|
+
encoding="utf-8",
|
|
2226
|
+
)
|
|
2227
|
+
_, spoof_errors = inspect_fact_chain(spoof_target)
|
|
2228
|
+
if not any("duplicate field `Goal`" in error for error in spoof_errors):
|
|
2229
|
+
failures.append(Failure("demo-fact-chain", "fact-chain parser must reject duplicate canonical metadata fields"))
|
|
2230
|
+
|
|
2231
|
+
runtime_spoof_target = Path(tmp) / "runtime-spoof"
|
|
2232
|
+
shutil.copytree(target, runtime_spoof_target)
|
|
2233
|
+
status_path = runtime_spoof_target / ".loom/status/current.md"
|
|
2234
|
+
status_text = status_path.read_text(encoding="utf-8")
|
|
2235
|
+
status_path.write_text(
|
|
2236
|
+
status_text.replace(
|
|
2237
|
+
"- Run Entry: not_applicable\n",
|
|
2238
|
+
"- Run Entry: wrong-spoofed-run\n- Run Entry: not_applicable\n",
|
|
2239
|
+
1,
|
|
2240
|
+
),
|
|
2241
|
+
encoding="utf-8",
|
|
2242
|
+
)
|
|
2243
|
+
payload, error = load_command_json(
|
|
2244
|
+
root,
|
|
2245
|
+
["python3", "tools/loom_flow.py", "fact-chain", "--target", str(runtime_spoof_target)],
|
|
2246
|
+
)
|
|
2247
|
+
if error:
|
|
2248
|
+
failures.append(Failure("demo-fact-chain", f"`flow fact-chain` metadata spoof sample failed unexpectedly: {error}"))
|
|
2249
|
+
elif payload.get("result") != "block":
|
|
2250
|
+
failures.append(Failure("demo-fact-chain", "`flow fact-chain` must not legacy-fallback past duplicate Runtime Evidence fields"))
|
|
2214
2251
|
return failures
|
|
2215
2252
|
|
|
2216
2253
|
|
|
@@ -2643,7 +2643,7 @@ def build_default_review_prompt(
|
|
|
2643
2643
|
|
|
2644
2644
|
def load_fact_chain_report(target_root: Path, output_relative: str) -> tuple[dict[str, Any], list[str]]:
|
|
2645
2645
|
report, errors = inspect_fact_chain(target_root, output_relative)
|
|
2646
|
-
if errors and all("Runtime Evidence" in message for message in errors):
|
|
2646
|
+
if errors and all("missing section `Runtime Evidence`" in message for message in errors):
|
|
2647
2647
|
report, errors = inspect_fact_chain_legacy(target_root, output_relative)
|
|
2648
2648
|
if errors:
|
|
2649
2649
|
return {}, errors
|
|
@@ -131,6 +131,7 @@ def parse_key_value_section(
|
|
|
131
131
|
return {}, [f"{relative_path}: missing section `{section_name}`"]
|
|
132
132
|
|
|
133
133
|
values: dict[str, str] = {}
|
|
134
|
+
seen: dict[str, str] = {}
|
|
134
135
|
for raw_line in lines:
|
|
135
136
|
stripped = raw_line.strip()
|
|
136
137
|
if not stripped:
|
|
@@ -143,7 +144,15 @@ def parse_key_value_section(
|
|
|
143
144
|
if label not in field_map:
|
|
144
145
|
errors.append(f"{relative_path}: unexpected field `{label}` in `{section_name}`")
|
|
145
146
|
continue
|
|
146
|
-
|
|
147
|
+
canonical = field_map[label]
|
|
148
|
+
if canonical in seen:
|
|
149
|
+
errors.append(
|
|
150
|
+
f"{relative_path}: duplicate field `{label}` in `{section_name}` "
|
|
151
|
+
f"(canonical `{canonical}` already set by `{seen[canonical]}`)"
|
|
152
|
+
)
|
|
153
|
+
continue
|
|
154
|
+
seen[canonical] = label
|
|
155
|
+
values[canonical] = _clean_value(match.group(2))
|
|
147
156
|
|
|
148
157
|
for label, canonical in field_map.items():
|
|
149
158
|
if canonical not in values:
|
|
@@ -2211,6 +2211,43 @@ def check_demo_fact_chain(root: Path) -> list[Failure]:
|
|
|
2211
2211
|
failures.append(Failure("demo-fact-chain", detail))
|
|
2212
2212
|
if report and report.get("fact_chain", {}).get("entry_points", {}).get("status_surface") != ".loom/status/current.md":
|
|
2213
2213
|
failures.append(Failure("demo-fact-chain", "demo fact chain must point status_surface to `.loom/status/current.md`"))
|
|
2214
|
+
with tempfile.TemporaryDirectory(prefix="loom-check-metadata-spoof-") as tmp:
|
|
2215
|
+
spoof_target = Path(tmp) / "new-project"
|
|
2216
|
+
shutil.copytree(target, spoof_target)
|
|
2217
|
+
work_item_path = spoof_target / ".loom/work-items/INIT-0001.md"
|
|
2218
|
+
work_item_text = work_item_path.read_text(encoding="utf-8")
|
|
2219
|
+
work_item_path.write_text(
|
|
2220
|
+
work_item_text.replace(
|
|
2221
|
+
"- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2222
|
+
"- Goal: wrong spoofed goal\n- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2223
|
+
1,
|
|
2224
|
+
),
|
|
2225
|
+
encoding="utf-8",
|
|
2226
|
+
)
|
|
2227
|
+
_, spoof_errors = inspect_fact_chain(spoof_target)
|
|
2228
|
+
if not any("duplicate field `Goal`" in error for error in spoof_errors):
|
|
2229
|
+
failures.append(Failure("demo-fact-chain", "fact-chain parser must reject duplicate canonical metadata fields"))
|
|
2230
|
+
|
|
2231
|
+
runtime_spoof_target = Path(tmp) / "runtime-spoof"
|
|
2232
|
+
shutil.copytree(target, runtime_spoof_target)
|
|
2233
|
+
status_path = runtime_spoof_target / ".loom/status/current.md"
|
|
2234
|
+
status_text = status_path.read_text(encoding="utf-8")
|
|
2235
|
+
status_path.write_text(
|
|
2236
|
+
status_text.replace(
|
|
2237
|
+
"- Run Entry: not_applicable\n",
|
|
2238
|
+
"- Run Entry: wrong-spoofed-run\n- Run Entry: not_applicable\n",
|
|
2239
|
+
1,
|
|
2240
|
+
),
|
|
2241
|
+
encoding="utf-8",
|
|
2242
|
+
)
|
|
2243
|
+
payload, error = load_command_json(
|
|
2244
|
+
root,
|
|
2245
|
+
["python3", "tools/loom_flow.py", "fact-chain", "--target", str(runtime_spoof_target)],
|
|
2246
|
+
)
|
|
2247
|
+
if error:
|
|
2248
|
+
failures.append(Failure("demo-fact-chain", f"`flow fact-chain` metadata spoof sample failed unexpectedly: {error}"))
|
|
2249
|
+
elif payload.get("result") != "block":
|
|
2250
|
+
failures.append(Failure("demo-fact-chain", "`flow fact-chain` must not legacy-fallback past duplicate Runtime Evidence fields"))
|
|
2214
2251
|
return failures
|
|
2215
2252
|
|
|
2216
2253
|
|
|
@@ -2643,7 +2643,7 @@ def build_default_review_prompt(
|
|
|
2643
2643
|
|
|
2644
2644
|
def load_fact_chain_report(target_root: Path, output_relative: str) -> tuple[dict[str, Any], list[str]]:
|
|
2645
2645
|
report, errors = inspect_fact_chain(target_root, output_relative)
|
|
2646
|
-
if errors and all("Runtime Evidence" in message for message in errors):
|
|
2646
|
+
if errors and all("missing section `Runtime Evidence`" in message for message in errors):
|
|
2647
2647
|
report, errors = inspect_fact_chain_legacy(target_root, output_relative)
|
|
2648
2648
|
if errors:
|
|
2649
2649
|
return {}, errors
|
|
@@ -131,6 +131,7 @@ def parse_key_value_section(
|
|
|
131
131
|
return {}, [f"{relative_path}: missing section `{section_name}`"]
|
|
132
132
|
|
|
133
133
|
values: dict[str, str] = {}
|
|
134
|
+
seen: dict[str, str] = {}
|
|
134
135
|
for raw_line in lines:
|
|
135
136
|
stripped = raw_line.strip()
|
|
136
137
|
if not stripped:
|
|
@@ -143,7 +144,15 @@ def parse_key_value_section(
|
|
|
143
144
|
if label not in field_map:
|
|
144
145
|
errors.append(f"{relative_path}: unexpected field `{label}` in `{section_name}`")
|
|
145
146
|
continue
|
|
146
|
-
|
|
147
|
+
canonical = field_map[label]
|
|
148
|
+
if canonical in seen:
|
|
149
|
+
errors.append(
|
|
150
|
+
f"{relative_path}: duplicate field `{label}` in `{section_name}` "
|
|
151
|
+
f"(canonical `{canonical}` already set by `{seen[canonical]}`)"
|
|
152
|
+
)
|
|
153
|
+
continue
|
|
154
|
+
seen[canonical] = label
|
|
155
|
+
values[canonical] = _clean_value(match.group(2))
|
|
147
156
|
|
|
148
157
|
for label, canonical in field_map.items():
|
|
149
158
|
if canonical not in values:
|
|
@@ -2211,6 +2211,43 @@ def check_demo_fact_chain(root: Path) -> list[Failure]:
|
|
|
2211
2211
|
failures.append(Failure("demo-fact-chain", detail))
|
|
2212
2212
|
if report and report.get("fact_chain", {}).get("entry_points", {}).get("status_surface") != ".loom/status/current.md":
|
|
2213
2213
|
failures.append(Failure("demo-fact-chain", "demo fact chain must point status_surface to `.loom/status/current.md`"))
|
|
2214
|
+
with tempfile.TemporaryDirectory(prefix="loom-check-metadata-spoof-") as tmp:
|
|
2215
|
+
spoof_target = Path(tmp) / "new-project"
|
|
2216
|
+
shutil.copytree(target, spoof_target)
|
|
2217
|
+
work_item_path = spoof_target / ".loom/work-items/INIT-0001.md"
|
|
2218
|
+
work_item_text = work_item_path.read_text(encoding="utf-8")
|
|
2219
|
+
work_item_path.write_text(
|
|
2220
|
+
work_item_text.replace(
|
|
2221
|
+
"- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2222
|
+
"- Goal: wrong spoofed goal\n- Goal: Bootstrap the first executable Loom path for this repository\n",
|
|
2223
|
+
1,
|
|
2224
|
+
),
|
|
2225
|
+
encoding="utf-8",
|
|
2226
|
+
)
|
|
2227
|
+
_, spoof_errors = inspect_fact_chain(spoof_target)
|
|
2228
|
+
if not any("duplicate field `Goal`" in error for error in spoof_errors):
|
|
2229
|
+
failures.append(Failure("demo-fact-chain", "fact-chain parser must reject duplicate canonical metadata fields"))
|
|
2230
|
+
|
|
2231
|
+
runtime_spoof_target = Path(tmp) / "runtime-spoof"
|
|
2232
|
+
shutil.copytree(target, runtime_spoof_target)
|
|
2233
|
+
status_path = runtime_spoof_target / ".loom/status/current.md"
|
|
2234
|
+
status_text = status_path.read_text(encoding="utf-8")
|
|
2235
|
+
status_path.write_text(
|
|
2236
|
+
status_text.replace(
|
|
2237
|
+
"- Run Entry: not_applicable\n",
|
|
2238
|
+
"- Run Entry: wrong-spoofed-run\n- Run Entry: not_applicable\n",
|
|
2239
|
+
1,
|
|
2240
|
+
),
|
|
2241
|
+
encoding="utf-8",
|
|
2242
|
+
)
|
|
2243
|
+
payload, error = load_command_json(
|
|
2244
|
+
root,
|
|
2245
|
+
["python3", "tools/loom_flow.py", "fact-chain", "--target", str(runtime_spoof_target)],
|
|
2246
|
+
)
|
|
2247
|
+
if error:
|
|
2248
|
+
failures.append(Failure("demo-fact-chain", f"`flow fact-chain` metadata spoof sample failed unexpectedly: {error}"))
|
|
2249
|
+
elif payload.get("result") != "block":
|
|
2250
|
+
failures.append(Failure("demo-fact-chain", "`flow fact-chain` must not legacy-fallback past duplicate Runtime Evidence fields"))
|
|
2214
2251
|
return failures
|
|
2215
2252
|
|
|
2216
2253
|
|
|
@@ -2643,7 +2643,7 @@ def build_default_review_prompt(
|
|
|
2643
2643
|
|
|
2644
2644
|
def load_fact_chain_report(target_root: Path, output_relative: str) -> tuple[dict[str, Any], list[str]]:
|
|
2645
2645
|
report, errors = inspect_fact_chain(target_root, output_relative)
|
|
2646
|
-
if errors and all("Runtime Evidence" in message for message in errors):
|
|
2646
|
+
if errors and all("missing section `Runtime Evidence`" in message for message in errors):
|
|
2647
2647
|
report, errors = inspect_fact_chain_legacy(target_root, output_relative)
|
|
2648
2648
|
if errors:
|
|
2649
2649
|
return {}, errors
|