@ecoma-io/archkeep 0.21.0 → 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 +126 -19
- 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 +194 -2
- 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.mjs +17 -18
- package/src/commands/plan-context-command.mjs +10 -5
- package/src/commands/reconcile.mjs +14 -17
- package/src/commands/scenario-evaluation.mjs +93 -16
- package/src/commands/scenario.mjs +28 -18
- package/src/commands/waivers.mjs +36 -28
- package/src/governance/evolution-event.mjs +62 -9
- package/src/intent/intent-manifest.json +83 -39
- package/src/report/json.mjs +32 -5
- package/src/report/text.mjs +82 -12
- package/src/verdict.mjs +78 -36
- package/src/workspace.mjs +126 -2
package/src/commands/waivers.mjs
CHANGED
|
@@ -37,10 +37,11 @@
|
|
|
37
37
|
* `now` and the output is reproducible byte-for-byte. Defaults to the wall
|
|
38
38
|
* clock, the same injection `evaluate` uses for waiver expiry.
|
|
39
39
|
*/
|
|
40
|
-
import {
|
|
40
|
+
import { blindSpotRows } from "../analysis/source-util.mjs";
|
|
41
41
|
import { suppressionCovers } from "../config.mjs";
|
|
42
42
|
import { referenceTime } from "../governance/clock.mjs";
|
|
43
43
|
import { isWaiver, remainingMs, waiverStatus } from "../governance/waiver.mjs";
|
|
44
|
+
import { coverageRefusal, coverageVerdict } from "./coverage-verdict.mjs";
|
|
44
45
|
import { jsonEnvelope, renderJson } from "../report/json.mjs";
|
|
45
46
|
import { formatWaiversReport } from "../report/waivers-text.mjs";
|
|
46
47
|
import { partitionUnownedCoverage } from "./coverage-acceptance.mjs";
|
|
@@ -150,10 +151,13 @@ export function computeWaivers(suppressions, rawViolations, now = referenceTime(
|
|
|
150
151
|
* clock, and — from `cli.mjs`'s `runWaivers` — the workspace-relative path
|
|
151
152
|
* the run's law actually resolved from, so the `coverage.unowned` matching
|
|
152
153
|
* below subtracts the same configuration files `check` subtracts.
|
|
153
|
-
* @returns {Promise<{status: "ok", waivers
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
154
|
+
* @returns {Promise<{status: "ok"|"no-verdict", waivers?: object, coverage: object,
|
|
155
|
+
* report: {text: string, json: string}}>}
|
|
156
|
+
* `status: "no-verdict"` carries no `waivers` payload — the verdict was
|
|
157
|
+
* withheld, and the envelope's `coverage` block is the whole answer (#608).
|
|
158
|
+
* @throws {Error} whenever the run's law is malformed — exit-3 class, the same
|
|
159
|
+
* posture `check` takes on a malformed config. Incomplete coverage returns
|
|
160
|
+
* the structured no-verdict envelope instead of throwing (#608).
|
|
157
161
|
*/
|
|
158
162
|
export async function waiversCommand(commandContext, boundaryConfig, io = {}) {
|
|
159
163
|
const { root, provider, marker, analysis, graph } = commandContext;
|
|
@@ -180,25 +184,23 @@ export async function waiversCommand(commandContext, boundaryConfig, io = {}) {
|
|
|
180
184
|
// A waiver surface over a tree it could not fully read is a lottery ticket,
|
|
181
185
|
// not a surface: a file the analyzer never judged contributes no raw
|
|
182
186
|
// violation, so every waiver that names it reads as stale and the report
|
|
183
|
-
// says "covers nothing" about a finding the run never looked at.
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
`would read as covering nothing it never saw. Fix the unanalyzed files and re-run.`,
|
|
201
|
-
);
|
|
187
|
+
// says "covers nothing" about a finding the run never looked at. Refused
|
|
188
|
+
// through the one structured contract `./coverage-verdict.mjs` builds
|
|
189
|
+
// (#608) — "could not look" must never read as "looked and found nothing".
|
|
190
|
+
// A whole-file failure whose file a `coverage.unowned` row accepts is
|
|
191
|
+
// withdrawn first, exactly as `check` withdraws it (`./check.mjs`'s
|
|
192
|
+
// `acceptedUnclaimed`): its state is a recorded acceptance this very report
|
|
193
|
+
// is about to name, not a hole the run failed to look at.
|
|
194
|
+
const completeness = coverageVerdict(commandContext, {
|
|
195
|
+
acceptedFiles: unownedCoverage.acceptedFiles,
|
|
196
|
+
});
|
|
197
|
+
if (!completeness.complete) {
|
|
198
|
+
return coverageRefusal({
|
|
199
|
+
command: "waivers",
|
|
200
|
+
commandContext,
|
|
201
|
+
what: "measuring the waiver surface",
|
|
202
|
+
acceptedFiles: unownedCoverage.acceptedFiles,
|
|
203
|
+
});
|
|
202
204
|
}
|
|
203
205
|
|
|
204
206
|
// F07: a waiver surface measured against a graph that cannot see the
|
|
@@ -226,10 +228,13 @@ export async function waiversCommand(commandContext, boundaryConfig, io = {}) {
|
|
|
226
228
|
projects: Object.keys(graph.nodes).length,
|
|
227
229
|
analyzedFiles: analysis.analyzed,
|
|
228
230
|
imports: analysis.imports.length,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
// The withdrawn list — whole-file failures a `coverage.unowned` row
|
|
232
|
+
// accepts are already named in `result.unownedAcceptances` below, so
|
|
233
|
+
// repeating them here would double-count the same acceptance. On this
|
|
234
|
+
// path the list is empty by construction: anything unwithdrawn refused
|
|
235
|
+
// above.
|
|
236
|
+
notAnalyzed: completeness.notAnalyzed,
|
|
237
|
+
blindSpots: blindSpotRows(analysis.failures),
|
|
233
238
|
// `remainingMs` reflects the wall clock at the moment of THIS run, not the
|
|
234
239
|
// workspace — it is expected to differ between two runs of an unchanged
|
|
235
240
|
// tree, by design (`../governance/clock.mjs`). Disclosed here, in-band,
|
|
@@ -282,6 +287,9 @@ export async function waiversCommand(commandContext, boundaryConfig, io = {}) {
|
|
|
282
287
|
return {
|
|
283
288
|
status: "ok",
|
|
284
289
|
waivers: result,
|
|
290
|
+
// The same `coverage` block the envelope carries, so both return shapes —
|
|
291
|
+
// this one and the no-verdict refusal's (#608) — expose it under one key.
|
|
292
|
+
coverage,
|
|
285
293
|
report: {
|
|
286
294
|
text: formatWaiversReport(result),
|
|
287
295
|
json: renderJson(envelope),
|
|
@@ -107,15 +107,68 @@ export function declarationDigest(intent) {
|
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* The identity string of one graph edge, in the canonical spelling
|
|
112
|
+
* `source>target:type` — the `(source, target, type)` identity design §1
|
|
113
|
+
* names. The ONE spelling the evolution events' `observed.edges` and
|
|
114
|
+
* `affected.boundaries` use: this module owns it, and `classifyEvolution`
|
|
115
|
+
* maps every edge it is handed through this function, so there is exactly
|
|
116
|
+
* one definition of "same edge" and no second spelling to drift.
|
|
117
|
+
*
|
|
118
|
+
* @param {{source: string, target: string, type: string}} edge
|
|
119
|
+
* @returns {string}
|
|
120
|
+
*/
|
|
121
|
+
export function edgeEvolutionIdentity({ source, target, type }) {
|
|
122
|
+
return `${source}>${target}:${type}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The one accepted input shape for an `observed.edges` entry: the raw
|
|
127
|
+
* `{source, target, type}` triple. A caller handing over a ready-made string
|
|
128
|
+
* would be choosing a second spelling of "same edge", so a string is refused
|
|
129
|
+
* loudly rather than accepted as one shape more — the identity string is this
|
|
130
|
+
* module's output, never its input.
|
|
131
|
+
*
|
|
132
|
+
* @param {unknown} entry
|
|
133
|
+
* @returns {string}
|
|
134
|
+
*/
|
|
135
|
+
function evolutionBoundary(entry) {
|
|
136
|
+
if (
|
|
137
|
+
typeof entry !== "object" ||
|
|
138
|
+
entry === null ||
|
|
139
|
+
!("source" in entry) ||
|
|
140
|
+
typeof entry.source !== "string" ||
|
|
141
|
+
entry.source === "" ||
|
|
142
|
+
!("target" in entry) ||
|
|
143
|
+
typeof entry.target !== "string" ||
|
|
144
|
+
entry.target === "" ||
|
|
145
|
+
!("type" in entry) ||
|
|
146
|
+
typeof entry.type !== "string"
|
|
147
|
+
) {
|
|
148
|
+
throw new TypeError(
|
|
149
|
+
"classifyEvolution: observed.edges entries must be {source, target, type} triples — " +
|
|
150
|
+
"the identity string is classifyEvolution's own output spelling, never an input",
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
// The guard above has verified every property; the annotation only states
|
|
154
|
+
// what it proved.
|
|
155
|
+
return edgeEvolutionIdentity(
|
|
156
|
+
/** @type {{source: string, target: string, type: string}} */ (entry),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
110
160
|
/**
|
|
111
161
|
* @typedef {object} EvolutionEvidence
|
|
112
162
|
* @property {{projects?: {added: string[], removed: string[], changed: string[]},
|
|
113
|
-
* edges?: {added: string
|
|
163
|
+
* edges?: {added: {source: string, target: string, type: string}[],
|
|
164
|
+
* removed: {source: string, target: string, type: string}[]},
|
|
114
165
|
* policyChanged?: boolean|null, policyOneSided?: boolean,
|
|
115
166
|
* provenanceChanged?: boolean|null}} [observed]
|
|
116
|
-
* The structural diff between base and head: project names and edge
|
|
117
|
-
*
|
|
118
|
-
*
|
|
167
|
+
* The structural diff between base and head: project names and raw edge
|
|
168
|
+
* triples that were added, removed, or changed. The triples are mapped
|
|
169
|
+
* through `edgeEvolutionIdentity` here — the identity spelling is this
|
|
170
|
+
* module's own, so `affected.boundaries` comes out as identity strings
|
|
171
|
+
* whichever shape the caller held. Empty by default. `policyChanged` — whether the policy fingerprint changed
|
|
119
172
|
* between base and head; `null` is "could not be compared": exactly one side
|
|
120
173
|
* records the policy (`policyOneSided: true`) or neither does
|
|
121
174
|
* (both-absent). `true` is a disclosure, never a refusal. `policyOneSided`
|
|
@@ -197,9 +250,9 @@ export function declarationDigest(intent) {
|
|
|
197
250
|
* absent (`null`) ⇒ NOT asserted, note added |
|
|
198
251
|
*
|
|
199
252
|
* The `affected` identities are derived from the same signals, never from a
|
|
200
|
-
* second opinion: changed project names, changed
|
|
201
|
-
*
|
|
202
|
-
* ids whose lineage moved.
|
|
253
|
+
* second opinion: changed project names, the changed edges under the one
|
|
254
|
+
* identity spelling (`edgeEvolutionIdentity`), the constraint/intent rows
|
|
255
|
+
* whose verdict was not `pass`/`matched`, and the ADR ids whose lineage moved.
|
|
203
256
|
*
|
|
204
257
|
* @param {EvolutionEvidence} [input]
|
|
205
258
|
* @returns {EvolutionClassification}
|
|
@@ -211,8 +264,8 @@ export function classifyEvolution(input = {}) {
|
|
|
211
264
|
const addedProjects = projects.added ?? [];
|
|
212
265
|
const removedProjects = projects.removed ?? [];
|
|
213
266
|
const changedProjects = projects.changed ?? [];
|
|
214
|
-
const addedEdges = edges.added ?? [];
|
|
215
|
-
const removedEdges = edges.removed ?? [];
|
|
267
|
+
const addedEdges = (edges.added ?? []).map(evolutionBoundary);
|
|
268
|
+
const removedEdges = (edges.removed ?? []).map(evolutionBoundary);
|
|
216
269
|
const structureChanged =
|
|
217
270
|
addedProjects.length +
|
|
218
271
|
removedProjects.length +
|
|
@@ -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
|