@ecoma-io/archkeep 0.20.1 → 0.22.0
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/cli.mjs +156 -66
- package/package.json +1 -1
- package/src/analysis/contract.md +32 -5
- package/src/analysis/source-util.mjs +107 -0
- package/src/analysis/typescript.mjs +86 -5
- package/src/commands/change.mjs +59 -28
- package/src/commands/check.mjs +65 -26
- package/src/commands/completeness.mjs +708 -0
- package/src/commands/context-command.mjs +13 -5
- package/src/commands/context.mjs +31 -4
- package/src/commands/coverage-verdict.mjs +184 -0
- package/src/commands/debt.mjs +18 -15
- package/src/commands/delta-classify.mjs +13 -18
- package/src/commands/delta.mjs +95 -33
- package/src/commands/diff.mjs +31 -24
- package/src/commands/discover.mjs +30 -10
- package/src/commands/drift.mjs +21 -21
- package/src/commands/edge-constraints.mjs +47 -1
- package/src/commands/evaluation-primitives.mjs +691 -0
- package/src/commands/evolution.mjs +27 -10
- package/src/commands/explain.mjs +14 -13
- package/src/commands/fitness.mjs +20 -19
- package/src/commands/graph.mjs +14 -5
- package/src/commands/health.mjs +12 -5
- package/src/commands/history.mjs +29 -15
- package/src/commands/impact-statement.mjs +31 -409
- package/src/commands/impact.mjs +18 -18
- package/src/commands/plan-context-command.mjs +10 -5
- package/src/commands/provenance-command.mjs +33 -2
- package/src/commands/reconcile.mjs +14 -17
- package/src/commands/scenario-evaluation.mjs +363 -198
- package/src/commands/scenario.mjs +32 -21
- package/src/commands/waivers.mjs +36 -28
- package/src/governance/evolution-event.mjs +62 -9
- package/src/governance/provenance-graph.mjs +479 -0
- package/src/intent/intent-manifest.json +83 -39
- package/src/report/json.mjs +32 -5
- package/src/report/provenance-text.mjs +30 -7
- package/src/report/text.mjs +82 -12
- package/src/verdict.mjs +78 -36
- package/src/workspace.mjs +126 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 2,
|
|
3
3
|
"generatedAt": "v1.0.0-hardening",
|
|
4
|
-
"description": "Machine-readable manifest declaring every v1.0 intent and the evidence that supports it. A contract is 'proven' only when at least one evidence entry is an executable proof (behavioral-test, e2e-test, or architecture-test). Source-evidence and documentation support the claim but cannot prove it alone.",
|
|
4
|
+
"description": "Machine-readable manifest declaring every v1.0 intent and the evidence that supports it. A contract is 'proven' only when at least one evidence entry is an executable proof (behavioral-test, e2e-test, or architecture-test). Source-evidence and documentation support the claim but cannot prove it alone. Every evidence entry carries the sha256 of the artifact it names, and the gate recomputes each digest, so evidence is content-addressed: an artifact that changed after its claim was certified is a loud mismatch, not a silent swap.",
|
|
5
5
|
"evidenceTaxonomy": {
|
|
6
6
|
"behavioral-test": "A unit or integration test that imports and calls the code, asserting on runtime behavior (not source text). Proves the intent is regression-protected.",
|
|
7
7
|
"e2e-test": "A CLI subprocess test that exercises the real binary against a consumer workspace. Proves the intent holds end-to-end.",
|
|
@@ -19,12 +19,14 @@
|
|
|
19
19
|
{
|
|
20
20
|
"type": "architecture-test",
|
|
21
21
|
"path": "src/conformance/boundary.test.mjs",
|
|
22
|
-
"assertion": "SHIPPED_PACKAGES allow-list prevents unapproved dependencies; specifiersIn() walk catches all imports including createRequire; no provider import found in core layers"
|
|
22
|
+
"assertion": "SHIPPED_PACKAGES allow-list prevents unapproved dependencies; specifiersIn() walk catches all imports including createRequire; no provider import found in core layers",
|
|
23
|
+
"sha256": "12d4e179f88de2622a41e56f42b2bab1c88a677ec2232430752138c57ba5fb18"
|
|
23
24
|
},
|
|
24
25
|
{
|
|
25
26
|
"type": "source-evidence",
|
|
26
27
|
"path": "src/commands/context.mjs",
|
|
27
|
-
"assertion": "Only commands/context.mjs imports providers — the designated orchestration layer"
|
|
28
|
+
"assertion": "Only commands/context.mjs imports providers — the designated orchestration layer",
|
|
29
|
+
"sha256": "7f566c06afbb175337529bd9bdc42465f8849498b932abf6a77fe0290a0cd13c"
|
|
28
30
|
}
|
|
29
31
|
],
|
|
30
32
|
"status": "proven"
|
|
@@ -37,17 +39,20 @@
|
|
|
37
39
|
{
|
|
38
40
|
"type": "behavioral-test",
|
|
39
41
|
"path": "src/report/json.test.mjs",
|
|
40
|
-
"assertion": "JSON envelope throws on status=ok + incomplete coverage, status/exitCode disagreement, coverage.complete/notAnalyzed disagreement"
|
|
42
|
+
"assertion": "JSON envelope throws on status=ok + incomplete coverage, status/exitCode disagreement, coverage.complete/notAnalyzed disagreement",
|
|
43
|
+
"sha256": "a371d36b89b10c5a4bda4d9e9a2e3bde114256c1377eb95699c9f0f195aabc4a"
|
|
41
44
|
},
|
|
42
45
|
{
|
|
43
46
|
"type": "source-evidence",
|
|
44
47
|
"path": "src/lsp/diagnose.mjs",
|
|
45
|
-
"assertion": "analyzed:false on every non-verdict path; empty diagnostic list only from two named places"
|
|
48
|
+
"assertion": "analyzed:false on every non-verdict path; empty diagnostic list only from two named places",
|
|
49
|
+
"sha256": "4eea19bf245c33963cd292e02f676eb9c5238a5783a3a960976f53afc7cf8b27"
|
|
46
50
|
},
|
|
47
51
|
{
|
|
48
52
|
"type": "source-evidence",
|
|
49
53
|
"path": "cli.mjs",
|
|
50
|
-
"assertion": "Exit codes 0/1/3 distinguish clean/findings/cannot-look"
|
|
54
|
+
"assertion": "Exit codes 0/1/3 distinguish clean/findings/cannot-look",
|
|
55
|
+
"sha256": "81abe8243660b3cca913ee903c1fc0bfcdb6a240640c5302f18848e6a9473fe6"
|
|
51
56
|
}
|
|
52
57
|
],
|
|
53
58
|
"status": "proven"
|
|
@@ -60,17 +65,26 @@
|
|
|
60
65
|
{
|
|
61
66
|
"type": "behavioral-test",
|
|
62
67
|
"path": "src/intent/intent.test.mjs",
|
|
63
|
-
"assertion": "analysis output conforms to the frozen contract schema (no extra verdict/policy fields); analysis output is invariant under project tag changes
|
|
68
|
+
"assertion": "analysis output conforms to the frozen contract schema (no extra verdict/policy fields); analysis output is invariant under project tag changes (three workspaces differing only in project tag fields compared byte-for-byte)",
|
|
69
|
+
"sha256": "6ed86cb5e7af94b56fa2af3d2da5d45d58937fbbf316105da0224d0060e88582"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"type": "architecture-test",
|
|
73
|
+
"path": "src/intent/intent.test.mjs",
|
|
74
|
+
"assertion": "the gate walks every production analysis module and fails when judging vocabulary (judge/forbid/permit/allow/ban) appears in code",
|
|
75
|
+
"sha256": "6ed86cb5e7af94b56fa2af3d2da5d45d58937fbbf316105da0224d0060e88582"
|
|
64
76
|
},
|
|
65
77
|
{
|
|
66
78
|
"type": "source-evidence",
|
|
67
79
|
"path": "src/analysis/contract.md",
|
|
68
|
-
"assertion": "Analysis record is a superset of a graph edge — 5 of 15 violations decided on raw specifier"
|
|
80
|
+
"assertion": "Analysis record is a superset of a graph edge — 5 of 15 violations decided on raw specifier",
|
|
81
|
+
"sha256": "56d2db7816ba5ffc5fbe9e299f7d5e270a857b42eb54bd3bea7c05da17be595b"
|
|
69
82
|
},
|
|
70
83
|
{
|
|
71
84
|
"type": "source-evidence",
|
|
72
85
|
"path": "AGENTS.md",
|
|
73
|
-
"assertion": "src/graph/ is a lossy view of analysis, on purpose; src/analysis/ never judges"
|
|
86
|
+
"assertion": "src/graph/ is a lossy view of analysis, on purpose; src/analysis/ never judges",
|
|
87
|
+
"sha256": "3209154935cc1463127721203da74dc8bf8ba40258b182c5faa7345946611e38"
|
|
74
88
|
}
|
|
75
89
|
],
|
|
76
90
|
"status": "proven"
|
|
@@ -83,17 +97,20 @@
|
|
|
83
97
|
{
|
|
84
98
|
"type": "e2e-test",
|
|
85
99
|
"path": "e2e/determinism.e2e.mjs",
|
|
86
|
-
"assertion": "Graph, check, impact run twice — byte-identical JSON output"
|
|
100
|
+
"assertion": "Graph, check, impact run twice — byte-identical JSON output",
|
|
101
|
+
"sha256": "90c0f2a62a111013990ff96ec07042e961c1aae50710c597192657cc270df5ec"
|
|
87
102
|
},
|
|
88
103
|
{
|
|
89
104
|
"type": "source-evidence",
|
|
90
105
|
"path": "src/commands/graph.mjs",
|
|
91
|
-
"assertion": "Plain string comparison, never localeCompare; INTERNAL_DATA_FIELDS stripped; SCHEMA_VERSION = 2"
|
|
106
|
+
"assertion": "Plain string comparison, never localeCompare; INTERNAL_DATA_FIELDS stripped; SCHEMA_VERSION = 2",
|
|
107
|
+
"sha256": "cce9730e33e83e0a183c0139afcc9a0845f2f95f67b07875f9c583f3ca95d046"
|
|
92
108
|
},
|
|
93
109
|
{
|
|
94
110
|
"type": "source-evidence",
|
|
95
111
|
"path": "src/commands/graph.mjs",
|
|
96
|
-
"assertion": "computePolicyFingerprint produces SHA-256 of canonicalized policy"
|
|
112
|
+
"assertion": "computePolicyFingerprint produces SHA-256 of canonicalized policy",
|
|
113
|
+
"sha256": "cce9730e33e83e0a183c0139afcc9a0845f2f95f67b07875f9c583f3ca95d046"
|
|
97
114
|
}
|
|
98
115
|
],
|
|
99
116
|
"status": "proven"
|
|
@@ -106,12 +123,14 @@
|
|
|
106
123
|
{
|
|
107
124
|
"type": "e2e-test",
|
|
108
125
|
"path": "e2e/diff.e2e.mjs",
|
|
109
|
-
"assertion": "Invalid baseline schemaVersion exits 3; incompatible version exits 3"
|
|
126
|
+
"assertion": "Invalid baseline schemaVersion exits 3; incompatible version exits 3",
|
|
127
|
+
"sha256": "7892a6dc56f5e1ec725fd6d029bba76bc493d8c34790f346e15af0d7d0dbbdc1"
|
|
110
128
|
},
|
|
111
129
|
{
|
|
112
130
|
"type": "source-evidence",
|
|
113
131
|
"path": "src/commands/diff.mjs",
|
|
114
|
-
"assertion": "parseBaseline validates schemaVersion; refuses unknown versions"
|
|
132
|
+
"assertion": "parseBaseline validates schemaVersion; refuses unknown versions",
|
|
133
|
+
"sha256": "6190013ec55eeb7f1a9e4cdd910c7be73bfb1fa87051bdeace7c9581c6a18426"
|
|
115
134
|
}
|
|
116
135
|
],
|
|
117
136
|
"status": "proven"
|
|
@@ -124,12 +143,14 @@
|
|
|
124
143
|
{
|
|
125
144
|
"type": "e2e-test",
|
|
126
145
|
"path": "e2e/diff.e2e.mjs",
|
|
127
|
-
"assertion": "Self-baseline exits 0; added/removed edges reported; policy mismatch warned; rule-impact with config"
|
|
146
|
+
"assertion": "Self-baseline exits 0; added/removed edges reported; policy mismatch warned; rule-impact with config",
|
|
147
|
+
"sha256": "7892a6dc56f5e1ec725fd6d029bba76bc493d8c34790f346e15af0d7d0dbbdc1"
|
|
128
148
|
},
|
|
129
149
|
{
|
|
130
150
|
"type": "source-evidence",
|
|
131
151
|
"path": "src/commands/diff.mjs",
|
|
132
|
-
"assertion": "computeDiff returns structural diff; policyMismatch detected via fingerprint; computeRuleImpact for depConstraints-only context"
|
|
152
|
+
"assertion": "computeDiff returns structural diff; policyMismatch detected via fingerprint; computeRuleImpact for depConstraints-only context",
|
|
153
|
+
"sha256": "6190013ec55eeb7f1a9e4cdd910c7be73bfb1fa87051bdeace7c9581c6a18426"
|
|
133
154
|
}
|
|
134
155
|
],
|
|
135
156
|
"status": "proven"
|
|
@@ -142,12 +163,14 @@
|
|
|
142
163
|
{
|
|
143
164
|
"type": "e2e-test",
|
|
144
165
|
"path": "e2e/determinism.e2e.mjs",
|
|
145
|
-
"assertion": "Impact --format json produces identical output on two runs"
|
|
166
|
+
"assertion": "Impact --format json produces identical output on two runs",
|
|
167
|
+
"sha256": "90c0f2a62a111013990ff96ec07042e961c1aae50710c597192657cc270df5ec"
|
|
146
168
|
},
|
|
147
169
|
{
|
|
148
170
|
"type": "behavioral-test",
|
|
149
171
|
"path": "src/commands/impact.test.mjs",
|
|
150
|
-
"assertion": "Sorts results using plain string comparison (never localeCompare)"
|
|
172
|
+
"assertion": "Sorts results using plain string comparison (never localeCompare)",
|
|
173
|
+
"sha256": "b188d7001d146c203be490727414f0c585d8e589f10e3a9add40a34b5aedd532"
|
|
151
174
|
}
|
|
152
175
|
],
|
|
153
176
|
"status": "proven"
|
|
@@ -160,17 +183,20 @@
|
|
|
160
183
|
{
|
|
161
184
|
"type": "behavioral-test",
|
|
162
185
|
"path": "src/commands/context-command.test.mjs",
|
|
163
|
-
"assertion": "Returns tags, matched constraints, per-edge violations; coverage.notes warns about depConstraints-only scope"
|
|
186
|
+
"assertion": "Returns tags, matched constraints, per-edge violations; coverage.notes warns about depConstraints-only scope",
|
|
187
|
+
"sha256": "eccd31e3057bbb0cb45785e0047e0d71e83dbbc00703ee231f085cf52fb76db8"
|
|
164
188
|
},
|
|
165
189
|
{
|
|
166
190
|
"type": "e2e-test",
|
|
167
191
|
"path": "e2e/context.e2e.mjs",
|
|
168
|
-
"assertion": "Native consumer: tags, constraints, coverage.notes verified; Moon consumer: context E2E"
|
|
192
|
+
"assertion": "Native consumer: tags, constraints, coverage.notes verified; Moon consumer: context E2E",
|
|
193
|
+
"sha256": "336994ea66e2b7b7ff1d374e3127b478d530508b8c8e5ef927805c5aa11b6cb8"
|
|
169
194
|
},
|
|
170
195
|
{
|
|
171
196
|
"type": "documentation",
|
|
172
197
|
"path": "../../docs/concepts/agentic-development.md",
|
|
173
|
-
"assertion": "Section 'What context and impact do not check' warns agents about the semantic gap"
|
|
198
|
+
"assertion": "Section 'What context and impact do not check' warns agents about the semantic gap",
|
|
199
|
+
"sha256": "a42bca1e927a59d28160eb947a285c20d1a6cc9633a69c91a45e9d46bbe1686b"
|
|
174
200
|
}
|
|
175
201
|
],
|
|
176
202
|
"status": "proven"
|
|
@@ -183,12 +209,14 @@
|
|
|
183
209
|
{
|
|
184
210
|
"type": "behavioral-test",
|
|
185
211
|
"path": "src/commands/explain.test.mjs",
|
|
186
|
-
"assertion": "Returns all violations at a site; unresolvable site returns unresolvable:true with reason"
|
|
212
|
+
"assertion": "Returns all violations at a site; unresolvable site returns unresolvable:true with reason",
|
|
213
|
+
"sha256": "017581d978f277fadecedd6a4a0a4befca3743dbf2b084e1192792a1891466ad"
|
|
187
214
|
},
|
|
188
215
|
{
|
|
189
216
|
"type": "e2e-test",
|
|
190
217
|
"path": "e2e/explain.e2e.mjs",
|
|
191
|
-
"assertion": "Native and Moon consumers: specifier, sourceProject, targetProject, violations verified"
|
|
218
|
+
"assertion": "Native and Moon consumers: specifier, sourceProject, targetProject, violations verified",
|
|
219
|
+
"sha256": "857e198249c0614af643f784d985ae782ab8dfa9906f92f44193e2570ee0c343"
|
|
192
220
|
}
|
|
193
221
|
],
|
|
194
222
|
"status": "proven"
|
|
@@ -201,37 +229,44 @@
|
|
|
201
229
|
{
|
|
202
230
|
"type": "behavioral-test",
|
|
203
231
|
"path": "src/intent/intent.test.mjs",
|
|
204
|
-
"assertion": "depConstraints verdicts from judgeEdge agree with evaluate; explain includes the same violations as evaluate at a given site
|
|
232
|
+
"assertion": "depConstraints verdicts from judgeEdge agree with evaluate in both directions (violating edge found by both, legal edge reported by neither); explain includes the same violations as evaluate at a given site",
|
|
233
|
+
"sha256": "6ed86cb5e7af94b56fa2af3d2da5d45d58937fbbf316105da0224d0060e88582"
|
|
205
234
|
},
|
|
206
235
|
{
|
|
207
236
|
"type": "behavioral-test",
|
|
208
237
|
"path": "src/commands/context-command.test.mjs",
|
|
209
|
-
"assertion": "Test verifies coverage.notes contains depConstraints warning"
|
|
238
|
+
"assertion": "Test verifies coverage.notes contains depConstraints warning",
|
|
239
|
+
"sha256": "eccd31e3057bbb0cb45785e0047e0d71e83dbbc00703ee231f085cf52fb76db8"
|
|
210
240
|
},
|
|
211
241
|
{
|
|
212
242
|
"type": "e2e-test",
|
|
213
243
|
"path": "e2e/context.e2e.mjs",
|
|
214
|
-
"assertion": "E2E verifies coverage.notes in JSON envelope"
|
|
244
|
+
"assertion": "E2E verifies coverage.notes in JSON envelope",
|
|
245
|
+
"sha256": "336994ea66e2b7b7ff1d374e3127b478d530508b8c8e5ef927805c5aa11b6cb8"
|
|
215
246
|
},
|
|
216
247
|
{
|
|
217
248
|
"type": "source-evidence",
|
|
218
249
|
"path": "src/commands/context-command.mjs",
|
|
219
|
-
"assertion": "coverage.notes warns that per-edge violations cover only depConstraints (3 of 15 violation types)"
|
|
250
|
+
"assertion": "coverage.notes warns that per-edge violations cover only depConstraints (3 of 15 violation types)",
|
|
251
|
+
"sha256": "72526cdaf038da4d9a50b33ab3cb30828713e62bbb891f4e2e51e008a403338e"
|
|
220
252
|
},
|
|
221
253
|
{
|
|
222
254
|
"type": "source-evidence",
|
|
223
255
|
"path": "src/commands/impact.mjs",
|
|
224
|
-
"assertion": "coverage.notes warns that per-edge violations cover only depConstraints (3 of 15 violation types)"
|
|
256
|
+
"assertion": "coverage.notes warns that per-edge violations cover only depConstraints (3 of 15 violation types)",
|
|
257
|
+
"sha256": "38cff166a8944860c51a73316447ce789898751489d783e75fd6befbfd30be3c"
|
|
225
258
|
},
|
|
226
259
|
{
|
|
227
260
|
"type": "source-evidence",
|
|
228
261
|
"path": "src/commands/diff.mjs",
|
|
229
|
-
"assertion": "coverage.notes warns when ruleImpact is computed (depConstraints only, 3 of 15)"
|
|
262
|
+
"assertion": "coverage.notes warns when ruleImpact is computed (depConstraints only, 3 of 15)",
|
|
263
|
+
"sha256": "6190013ec55eeb7f1a9e4cdd910c7be73bfb1fa87051bdeace7c9581c6a18426"
|
|
230
264
|
},
|
|
231
265
|
{
|
|
232
266
|
"type": "documentation",
|
|
233
267
|
"path": "../../docs/concepts/agentic-development.md",
|
|
234
|
-
"assertion": "Section warns agents: violations:[] means allowed by constraint table, not free of all boundary violations"
|
|
268
|
+
"assertion": "Section warns agents: violations:[] means allowed by constraint table, not free of all boundary violations",
|
|
269
|
+
"sha256": "a42bca1e927a59d28160eb947a285c20d1a6cc9633a69c91a45e9d46bbe1686b"
|
|
235
270
|
}
|
|
236
271
|
],
|
|
237
272
|
"status": "proven"
|
|
@@ -244,12 +279,14 @@
|
|
|
244
279
|
{
|
|
245
280
|
"type": "e2e-test",
|
|
246
281
|
"path": "e2e/determinism.e2e.mjs",
|
|
247
|
-
"assertion": "Graph, check, impact: two runs produce byte-identical JSON"
|
|
282
|
+
"assertion": "Graph, check, impact: two runs produce byte-identical JSON",
|
|
283
|
+
"sha256": "90c0f2a62a111013990ff96ec07042e961c1aae50710c597192657cc270df5ec"
|
|
248
284
|
},
|
|
249
285
|
{
|
|
250
286
|
"type": "source-evidence",
|
|
251
287
|
"path": "src/commands/graph.mjs",
|
|
252
|
-
"assertion": "Plain string comparison throughout; never localeCompare"
|
|
288
|
+
"assertion": "Plain string comparison throughout; never localeCompare",
|
|
289
|
+
"sha256": "cce9730e33e83e0a183c0139afcc9a0845f2f95f67b07875f9c583f3ca95d046"
|
|
253
290
|
}
|
|
254
291
|
],
|
|
255
292
|
"status": "proven"
|
|
@@ -262,22 +299,26 @@
|
|
|
262
299
|
{
|
|
263
300
|
"type": "e2e-test",
|
|
264
301
|
"path": "e2e/parity.e2e.mjs",
|
|
265
|
-
"assertion": "Nx vs Native: project names, edge source/target/type, violation rule IDs, envelope structure"
|
|
302
|
+
"assertion": "Nx vs Native: project names, edge source/target/type, violation rule IDs, envelope structure",
|
|
303
|
+
"sha256": "106f59315e4661ae67f5c1e44895d56f7f7562eacd4c44101a9ed4b0e56f774e"
|
|
266
304
|
},
|
|
267
305
|
{
|
|
268
306
|
"type": "e2e-test",
|
|
269
307
|
"path": "e2e/moon.e2e.mjs",
|
|
270
|
-
"assertion": "Moon: check, graph, diff, impact, explain, context verified against Moon provider (conditional on moon CLI availability)"
|
|
308
|
+
"assertion": "Moon: check, graph, diff, impact, explain, context verified against Moon provider (conditional on moon CLI availability)",
|
|
309
|
+
"sha256": "8293a65ff407ea43a3985c742ae6333970ec3b0400f3df46189793a3dc87af6b"
|
|
271
310
|
},
|
|
272
311
|
{
|
|
273
312
|
"type": "source-evidence",
|
|
274
313
|
"path": "src/providers/moon.mjs",
|
|
275
|
-
"assertion": "inferWorkspaceLayout returns null for partial layouts — same all-or-nothing contract as Nx and Native"
|
|
314
|
+
"assertion": "inferWorkspaceLayout returns null for partial layouts — same all-or-nothing contract as Nx and Native",
|
|
315
|
+
"sha256": "232dd8e2cbe70c9149ba3a75a03dcebe0db355894c280964ebbf6a5a536b5567"
|
|
276
316
|
},
|
|
277
317
|
{
|
|
278
318
|
"type": "behavioral-test",
|
|
279
319
|
"path": "src/providers/moon.test.mjs",
|
|
280
|
-
"assertion": "Partial layout (apps-only, libs-only) returns undefined workspaceLayout"
|
|
320
|
+
"assertion": "Partial layout (apps-only, libs-only) returns undefined workspaceLayout",
|
|
321
|
+
"sha256": "084a0061b3236e680274d837fb2f12d27dd4b5a379040157f7afe2be5088e8ac"
|
|
281
322
|
}
|
|
282
323
|
],
|
|
283
324
|
"status": "proven"
|
|
@@ -290,17 +331,20 @@
|
|
|
290
331
|
{
|
|
291
332
|
"type": "behavioral-test",
|
|
292
333
|
"path": "src/commands/drift.test.mjs",
|
|
293
|
-
"assertion": "driftCommand returns status ok with observed facts and findings; refuses incomplete coverage, absent intent, and unregistered-plugin incomplete graph; buildObserved counts implicit edges separately"
|
|
334
|
+
"assertion": "driftCommand returns status ok with observed facts and findings; refuses incomplete coverage, absent intent, and unregistered-plugin incomplete graph; buildObserved counts implicit edges separately",
|
|
335
|
+
"sha256": "dbb0dfa1ecce9123ad0a38cdd6bc97e6645a47cba78496357318375db952e43a"
|
|
294
336
|
},
|
|
295
337
|
{
|
|
296
338
|
"type": "behavioral-test",
|
|
297
339
|
"path": "src/report/drift-text.test.mjs",
|
|
298
|
-
"assertion": "formatDriftReport states comparison facts even when clean (no-drift is a claim about coverage); groups findings by rule in taxonomy order; byte-identical determinism"
|
|
340
|
+
"assertion": "formatDriftReport states comparison facts even when clean (no-drift is a claim about coverage); groups findings by rule in taxonomy order; byte-identical determinism",
|
|
341
|
+
"sha256": "767d395c40cb0fa61f19ec06d587708e4ba065323bb1ee170254365847b49cc5"
|
|
299
342
|
},
|
|
300
343
|
{
|
|
301
344
|
"type": "e2e-test",
|
|
302
345
|
"path": "e2e/drift.e2e.mjs",
|
|
303
|
-
"assertion": "Through the installed CLI: drift exits 0 on a matching tree and names fingerprint/rows; reports dependencyForbidden and projectMissing; JSON envelope carries findings; malformed intent exits 3 for both drift and check"
|
|
346
|
+
"assertion": "Through the installed CLI: drift exits 0 on a matching tree and names fingerprint/rows; reports dependencyForbidden and projectMissing; JSON envelope carries findings; malformed intent exits 3 for both drift and check",
|
|
347
|
+
"sha256": "1e060ba0f02497259fdde2481dc59bd74bf6658f2fc354100de4f89ff6ffb926"
|
|
304
348
|
}
|
|
305
349
|
],
|
|
306
350
|
"status": "proven"
|
package/src/report/json.mjs
CHANGED
|
@@ -94,12 +94,39 @@ export function jsonEnvelope({ command, context, status, exitCode, coverage, res
|
|
|
94
94
|
`them disagree would make one of them a lie.`,
|
|
95
95
|
);
|
|
96
96
|
}
|
|
97
|
-
|
|
97
|
+
// Completeness law, one-directional (#595, #599): `complete: true`
|
|
98
|
+
// claims the run judged everything its coverage block describes, and a
|
|
99
|
+
// whole-file failure or an unresolvable import site is exactly the kind
|
|
100
|
+
// of thing it did not judge — so the claim is refused over either. The
|
|
101
|
+
// reverse is deliberately allowed: `complete: false` with both lists
|
|
102
|
+
// empty is the zero-analysis state (a scope that selected no owned file,
|
|
103
|
+
// in-scope files no analyzer claims), which names its reason in the
|
|
104
|
+
// envelope's status and decision rather than in these lists, and refusing
|
|
105
|
+
// it would leave a run that judged nothing unable to say so. blindSpots
|
|
106
|
+
// is optional because not every command's coverage carries it; where it
|
|
107
|
+
// is present, the law is enforced.
|
|
108
|
+
//
|
|
109
|
+
// Within blindSpots only the unresolvable-LITERAL class counts as unjudged
|
|
110
|
+
// work (#595, narrowed): a row carrying `dynamic: true` is the declared
|
|
111
|
+
// non-literal-import limit — the language itself saying the target is
|
|
112
|
+
// computed at runtime, unknowable to static analysis in principle — and a
|
|
113
|
+
// declared limit is disclosed, not withheld over. The classifier lives once
|
|
114
|
+
// in `analysis/source-util.mjs`; this guard reads the row field the same
|
|
115
|
+
// helper that built the rows set, so the two can never disagree.
|
|
116
|
+
const blindSpotList = Array.isArray(coverage.blindSpots) ? coverage.blindSpots : [];
|
|
117
|
+
const unjudgedBlindSpots = blindSpotList.filter(
|
|
118
|
+
(row) => row.dynamic !== true && row.external !== true,
|
|
119
|
+
);
|
|
120
|
+
if (
|
|
121
|
+
coverage.complete === true &&
|
|
122
|
+
(coverage.notAnalyzed.length > 0 || unjudgedBlindSpots.length > 0)
|
|
123
|
+
) {
|
|
98
124
|
throw new Error(
|
|
99
|
-
`archkeep: refusing to build a JSON envelope
|
|
100
|
-
`
|
|
101
|
-
|
|
102
|
-
`
|
|
125
|
+
`archkeep: refusing to build a JSON envelope claiming coverage.complete (${coverage.complete}) ` +
|
|
126
|
+
`over unjudged work (notAnalyzed: ${coverage.notAnalyzed.length} entr${coverage.notAnalyzed.length === 1 ? "y" : "ies"}, ` +
|
|
127
|
+
`blindSpots: ${unjudgedBlindSpots.length}) — a run ` +
|
|
128
|
+
`that could not read everything cannot claim to have. This is a bug in the command that built ` +
|
|
129
|
+
`this envelope, not a fact about the workspace being judged.`,
|
|
103
130
|
);
|
|
104
131
|
}
|
|
105
132
|
// A bare `null` decision is the same programming error as a hand-built
|
|
@@ -31,13 +31,9 @@
|
|
|
31
31
|
* created: string|null, updated: string|null, supersedes: string[],
|
|
32
32
|
* supersededBy: string[], bindings: string[],
|
|
33
33
|
* attribution: {createdBy: object|null, lastChangedBy: object|null}|null,
|
|
34
|
-
* attested: boolean, note: string|null}[]
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* "no fact, no claim" bargain every optional axis in this tool states.
|
|
38
|
-
* `decisionLifecycle` (optional, default `[]`) is the decision-lifecycle
|
|
39
|
-
* section — it renders only when non-empty.
|
|
40
|
-
* @returns {string}
|
|
34
|
+
* attested: boolean, note: string|null}[],
|
|
35
|
+
* provenanceGraph?: {nodes: object[], edges: object[], claims: object[],
|
|
36
|
+
* causalChains: object[]}|null}} input
|
|
41
37
|
*/
|
|
42
38
|
export function formatProvenanceReport({
|
|
43
39
|
establishment,
|
|
@@ -47,6 +43,7 @@ export function formatProvenanceReport({
|
|
|
47
43
|
decisionRefTotal,
|
|
48
44
|
unresolvedDecisionRefs,
|
|
49
45
|
decisionLifecycle = [],
|
|
46
|
+
provenanceGraph = null,
|
|
50
47
|
}) {
|
|
51
48
|
const attestedCount = rowsTotal - unattested.length;
|
|
52
49
|
const text = [];
|
|
@@ -140,5 +137,31 @@ export function formatProvenanceReport({
|
|
|
140
137
|
);
|
|
141
138
|
}
|
|
142
139
|
}
|
|
140
|
+
|
|
141
|
+
// PR4 — provenance graph summary and one chain example
|
|
142
|
+
if (provenanceGraph !== null && provenanceGraph !== undefined) {
|
|
143
|
+
const nodeCount = provenanceGraph.nodes.length;
|
|
144
|
+
const edgeCount = provenanceGraph.edges.length;
|
|
145
|
+
const claimCount = provenanceGraph.claims.length;
|
|
146
|
+
const chainCount = provenanceGraph.causalChains.length;
|
|
147
|
+
text.push(
|
|
148
|
+
`graph ${nodeCount} nodes, ${edgeCount} edges, ${claimCount} claims, ` +
|
|
149
|
+
`${chainCount} causal chain${chainCount === 1 ? "" : "s"}`,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Show the first causal chain as an example (deterministic — sorted order)
|
|
153
|
+
if (chainCount > 0) {
|
|
154
|
+
const chain = provenanceGraph.causalChains[0];
|
|
155
|
+
text.push(
|
|
156
|
+
`chain ${chain.id} — ${chain.hops.length} hop${chain.hops.length === 1 ? "" : "s"}`,
|
|
157
|
+
);
|
|
158
|
+
text.push(` ${chain.startNode}`);
|
|
159
|
+
for (const hop of chain.hops) {
|
|
160
|
+
const evidence = hop.evidence.map((e) => e.detail).join("; ");
|
|
161
|
+
text.push(` → ${hop.toNode} (${hop.edgeKind}: ${evidence})`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
143
166
|
return text.join("\n");
|
|
144
167
|
}
|
package/src/report/text.mjs
CHANGED
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
* wearing a formatter's name, and it would disagree with the engine the first
|
|
22
22
|
* time either changed (`README.md` beside this file).
|
|
23
23
|
*/
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
isDynamicSiteFailure,
|
|
26
|
+
isExternalSiteFailure,
|
|
27
|
+
isWholeFileFailure,
|
|
28
|
+
} from "../analysis/source-util.mjs";
|
|
25
29
|
|
|
26
30
|
/** Two spaces of indent for a violation's detail lines, four for wrapped text. */
|
|
27
31
|
const DETAIL = " ";
|
|
@@ -128,12 +132,19 @@ const formatFailure = (failure) =>
|
|
|
128
132
|
* consequences and one heading for both hid that for as long as it existed.
|
|
129
133
|
*
|
|
130
134
|
* A SITE failure is a blind spot: the file was analyzed, and one specifier in
|
|
131
|
-
* it is not statically knowable
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
135
|
+
* it is not statically knowable. Three classes, and the section says which
|
|
136
|
+
* class did what instead of leaving the exit code to be discovered. An
|
|
137
|
+
* unresolvable literal referencing the workspace's own surface — path-like,
|
|
138
|
+
* `#` subpath, `paths` alias — is a concrete question the resolver was asked
|
|
139
|
+
* about the governed graph and could not answer: it WITHHELD the verdict
|
|
140
|
+
* (exit 3 on a findings-free run, #595). An unresolvable bare-package
|
|
141
|
+
* specifier names no project the workspace declares — `external: true`, the
|
|
142
|
+
* resolvability question an installed dependency tree answers, which a
|
|
143
|
+
* workspace legitimately may not have (the native self-check's `git archive`
|
|
144
|
+
* copy is the measured case: 284 rows) — disclosed without withholding. A
|
|
145
|
+
* non-literal `import()`/`require()` argument is the language declaring the
|
|
146
|
+
* target computed at runtime — a declared limit, `dynamic: true`, disclosed
|
|
147
|
+
* without withholding.
|
|
137
148
|
*
|
|
138
149
|
* A WHOLE-FILE failure is a hole: nothing was read, parsed, or analyzed — or a
|
|
139
150
|
* literal import that names a DECLARED project could not be resolved, so the
|
|
@@ -165,12 +176,42 @@ export function formatFailures(failures) {
|
|
|
165
176
|
}
|
|
166
177
|
|
|
167
178
|
if (blind.length > 0) {
|
|
179
|
+
const dyn = blind.filter(isDynamicSiteFailure).length;
|
|
180
|
+
const ext = blind.filter(isExternalSiteFailure).length;
|
|
181
|
+
const literal = blind.length - dyn - ext;
|
|
168
182
|
sections.push(
|
|
169
183
|
[
|
|
170
|
-
`${blind.length} import${blind.length === 1 ? "" : "s"} could not be resolved
|
|
171
|
-
`
|
|
172
|
-
|
|
173
|
-
|
|
184
|
+
`${blind.length} import${blind.length === 1 ? "" : "s"} could not be resolved — ` +
|
|
185
|
+
`blind spots inside files that were analyzed:`,
|
|
186
|
+
// All permanent classes are disclosed; the verdict treats them
|
|
187
|
+
// differently, and the report says which class did what instead of
|
|
188
|
+
// leaving the exit code to be discovered (the same posture the
|
|
189
|
+
// whole-file section's heading holds).
|
|
190
|
+
...(literal > 0
|
|
191
|
+
? [
|
|
192
|
+
`${literal} unresolvable literal import${literal === 1 ? "" : "s"} withheld the run's verdict (#595):`,
|
|
193
|
+
]
|
|
194
|
+
: []),
|
|
195
|
+
...blind
|
|
196
|
+
.filter((failure) => !isDynamicSiteFailure(failure) && !isExternalSiteFailure(failure))
|
|
197
|
+
.map(formatFailure),
|
|
198
|
+
...(ext > 0
|
|
199
|
+
? [
|
|
200
|
+
`${ext} unresolvable package import${ext === 1 ? "" : "s"} name${ext === 1 ? "s" : ""} ` +
|
|
201
|
+
`no project the workspace declares — external, disclosed without withholding:`,
|
|
202
|
+
]
|
|
203
|
+
: []),
|
|
204
|
+
...blind.filter(isExternalSiteFailure).map(formatFailure),
|
|
205
|
+
...(dyn > 0
|
|
206
|
+
? [
|
|
207
|
+
`${dyn} non-literal import() argument${dyn === 1 ? "" : "s"} — a declared limit static analysis cannot answer; ` +
|
|
208
|
+
`the verdict stands over the statically judgeable surface:`,
|
|
209
|
+
]
|
|
210
|
+
: []),
|
|
211
|
+
...blind.filter(isDynamicSiteFailure).map(formatFailure),
|
|
212
|
+
]
|
|
213
|
+
.filter(Boolean)
|
|
214
|
+
.join("\n"),
|
|
174
215
|
);
|
|
175
216
|
}
|
|
176
217
|
return sections.join("\n\n");
|
|
@@ -751,6 +792,27 @@ export function formatAcceptedViolations(waived, unresolvedDecisionRefs) {
|
|
|
751
792
|
].join("\n\n");
|
|
752
793
|
}
|
|
753
794
|
|
|
795
|
+
/**
|
|
796
|
+
* The could-not-look section: the reasons a run that found no violation is
|
|
797
|
+
* still not a pass, spelled the same way the JSON envelope words them
|
|
798
|
+
* (`../verdict.mjs`'s `coverageIncompleteReasons`, joined into
|
|
799
|
+
* `decision.reason`).
|
|
800
|
+
*
|
|
801
|
+
* "✔ no boundary violations" states only the boundary half of the verdict —
|
|
802
|
+
* on a run whose exit is 3, the reader needs the next lines to say WHY the
|
|
803
|
+
* run failed despite it, or the checkmark reads as a clean tree (P1-04's
|
|
804
|
+
* repro: an exit-3 run printed the checkmark and nothing else). Empty exactly
|
|
805
|
+
* when the run reached a verdict on everything it looked at.
|
|
806
|
+
*
|
|
807
|
+
* @param {string[]} coverageIncomplete The verdict's reason clauses, in
|
|
808
|
+
* `verdictFor`'s pinned order.
|
|
809
|
+
* @returns {string} Empty exactly when there is nothing to disclose.
|
|
810
|
+
*/
|
|
811
|
+
export function formatCoverageIncomplete(coverageIncomplete) {
|
|
812
|
+
if (coverageIncomplete.length === 0) return "";
|
|
813
|
+
return coverageIncomplete.map((reason) => `⚠ ${reason}`).join("\n");
|
|
814
|
+
}
|
|
815
|
+
|
|
754
816
|
/**
|
|
755
817
|
* The whole report, violations first.
|
|
756
818
|
*
|
|
@@ -766,7 +828,7 @@ export function formatAcceptedViolations(waived, unresolvedDecisionRefs) {
|
|
|
766
828
|
* summary line above only says "no boundary violations" when there is nothing
|
|
767
829
|
* a waiver is covering either.
|
|
768
830
|
*
|
|
769
|
-
* @param {{violations: object[], failures: object[], analyzed: number, projects: number, imports: number, goWork?: object|null, tsconfigPaths?: object|null, declaredEdges?: object|null, intent?: object|null, fitness?: object|null, fitnessOverall?: {verdict: string}|null, customRules?: {decisions: object[], overall: {verdict: string}}|null, coverageGaps?: object[], notes?: string[], policy?: {profile: string|null, source: string, fingerprint: string}|null, unresolvedDecisionRefs?: Set<string
|
|
831
|
+
* @param {{violations: object[], failures: object[], analyzed: number, projects: number, imports: number, goWork?: object|null, tsconfigPaths?: object|null, declaredEdges?: object|null, intent?: object|null, fitness?: object|null, fitnessOverall?: {verdict: string}|null, customRules?: {decisions: object[], overall: {verdict: string}}|null, coverageGaps?: object[], notes?: string[], policy?: {profile: string|null, source: string, fingerprint: string}|null, unresolvedDecisionRefs?: Set<string>, coverageIncomplete?: string[]}} run
|
|
770
832
|
* @returns {string}
|
|
771
833
|
*/
|
|
772
834
|
export function formatReport({
|
|
@@ -786,6 +848,7 @@ export function formatReport({
|
|
|
786
848
|
notes = [],
|
|
787
849
|
policy = null,
|
|
788
850
|
unresolvedDecisionRefs,
|
|
851
|
+
coverageIncomplete = [],
|
|
789
852
|
}) {
|
|
790
853
|
const inspected =
|
|
791
854
|
`${imports} import${imports === 1 ? "" : "s"} in ${analyzed} file${analyzed === 1 ? "" : "s"} ` +
|
|
@@ -829,6 +892,13 @@ export function formatReport({
|
|
|
829
892
|
}
|
|
830
893
|
}
|
|
831
894
|
|
|
895
|
+
// The could-not-look reasons render BEFORE the per-feature sections, right
|
|
896
|
+
// after the verdict summary they qualify: a reader who just read "✔ no
|
|
897
|
+
// boundary violations" needs the next lines to be the reason the exit is
|
|
898
|
+
// still 3, not go.work bookkeeping.
|
|
899
|
+
const coverageIncompleteSection = formatCoverageIncomplete(coverageIncomplete);
|
|
900
|
+
if (coverageIncompleteSection !== "") sections.push(coverageIncompleteSection);
|
|
901
|
+
|
|
832
902
|
const goWorkSection = formatGoWork(goWork);
|
|
833
903
|
if (goWorkSection !== "") sections.push(goWorkSection);
|
|
834
904
|
|