@ecoma-io/archkeep 0.24.1 → 0.26.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.
Files changed (125) hide show
  1. package/cli.mjs +166 -506
  2. package/commands.mjs +7 -1
  3. package/package.json +1 -1
  4. package/src/architecture-intent/judge.mjs +1 -1
  5. package/src/architecture-intent/model.mjs +34 -0
  6. package/src/canonical.mjs +2 -1
  7. package/src/commands/adr-for-workspace.mjs +63 -0
  8. package/src/commands/analyze-capability.mjs +13 -0
  9. package/src/commands/change-intent.mjs +11 -0
  10. package/src/commands/change.mjs +315 -55
  11. package/src/commands/check-capability.mjs +13 -0
  12. package/src/commands/check.mjs +13 -3
  13. package/src/commands/compare-capability.mjs +20 -0
  14. package/src/commands/completeness.mjs +12 -0
  15. package/src/commands/context-command.mjs +31 -0
  16. package/src/commands/coverage-verdict.mjs +15 -2
  17. package/src/commands/debt.mjs +30 -0
  18. package/src/commands/decisions.mjs +59 -10
  19. package/src/commands/delta-snapshot.mjs +25 -4
  20. package/src/commands/delta.mjs +301 -67
  21. package/src/commands/diff.mjs +48 -9
  22. package/src/commands/discover.mjs +37 -0
  23. package/src/commands/drift.mjs +45 -0
  24. package/src/commands/evolution.mjs +22 -2
  25. package/src/commands/explain-capability.mjs +11 -0
  26. package/src/commands/explain.mjs +24 -0
  27. package/src/commands/fitness.mjs +119 -10
  28. package/src/commands/govern-capability.mjs +16 -0
  29. package/src/commands/graph.mjs +39 -0
  30. package/src/commands/health.mjs +34 -0
  31. package/src/commands/history.mjs +119 -2
  32. package/src/commands/impact.mjs +23 -0
  33. package/src/commands/inspect-capability.mjs +16 -0
  34. package/src/commands/plan-context-command.mjs +35 -0
  35. package/src/commands/policy.mjs +42 -4
  36. package/src/commands/provenance-command.mjs +15 -24
  37. package/src/commands/report.mjs +38 -0
  38. package/src/commands/rules-capability.mjs +16 -0
  39. package/src/commands/rules.mjs +3 -1
  40. package/src/commands/scenario.mjs +23 -0
  41. package/src/commands/snapshot-meta.mjs +33 -0
  42. package/src/commands/trajectory.mjs +35 -0
  43. package/src/commands/waivers.mjs +30 -0
  44. package/src/corpus/goldens/.gitkeep +0 -0
  45. package/src/corpus/goldens/adr.json +62 -0
  46. package/src/corpus/goldens/adr.text +6 -0
  47. package/src/corpus/goldens/change.json +98 -0
  48. package/src/corpus/goldens/change.text +6 -0
  49. package/src/corpus/goldens/check.json +90 -0
  50. package/src/corpus/goldens/check.sarif +479 -0
  51. package/src/corpus/goldens/check.text +9 -0
  52. package/src/corpus/goldens/context.json +47 -0
  53. package/src/corpus/goldens/context.text +6 -0
  54. package/src/corpus/goldens/debt.json +59 -0
  55. package/src/corpus/goldens/debt.text +7 -0
  56. package/src/corpus/goldens/decisions.json +74 -0
  57. package/src/corpus/goldens/decisions.text +7 -0
  58. package/src/corpus/goldens/delta.json +94 -0
  59. package/src/corpus/goldens/delta.sarif +472 -0
  60. package/src/corpus/goldens/delta.text +5 -0
  61. package/src/corpus/goldens/diff.json +58 -0
  62. package/src/corpus/goldens/diff.text +4 -0
  63. package/src/corpus/goldens/discover.json +77 -0
  64. package/src/corpus/goldens/discover.text +10 -0
  65. package/src/corpus/goldens/drift.json +43 -0
  66. package/src/corpus/goldens/drift.text +3 -0
  67. package/src/corpus/goldens/evolution.json +328 -0
  68. package/src/corpus/goldens/evolution.text +32 -0
  69. package/src/corpus/goldens/explain.json +60 -0
  70. package/src/corpus/goldens/explain.text +7 -0
  71. package/src/corpus/goldens/fitness.json +45 -0
  72. package/src/corpus/goldens/fitness.text +3 -0
  73. package/src/corpus/goldens/graph.json +78 -0
  74. package/src/corpus/goldens/graph.text +10 -0
  75. package/src/corpus/goldens/health.json +72 -0
  76. package/src/corpus/goldens/health.text +10 -0
  77. package/src/corpus/goldens/help.text +104 -0
  78. package/src/corpus/goldens/history.json +43 -0
  79. package/src/corpus/goldens/history.text +4 -0
  80. package/src/corpus/goldens/impact.json +327 -0
  81. package/src/corpus/goldens/impact.text +10 -0
  82. package/src/corpus/goldens/provenance.json +453 -0
  83. package/src/corpus/goldens/provenance.text +14 -0
  84. package/src/corpus/goldens/reconcile.json +168 -0
  85. package/src/corpus/goldens/reconcile.text +3 -0
  86. package/src/corpus/goldens/report.json +168 -0
  87. package/src/corpus/goldens/report.text +42 -0
  88. package/src/corpus/goldens/rules verify.json +0 -0
  89. package/src/corpus/goldens/rules verify.text +0 -0
  90. package/src/corpus/goldens/scenario.json +441 -0
  91. package/src/corpus/goldens/scenario.text +28 -0
  92. package/src/corpus/goldens/trajectory.json +83 -0
  93. package/src/corpus/goldens/trajectory.text +10 -0
  94. package/src/corpus/goldens/usage-error.text +105 -0
  95. package/src/corpus/goldens/waivers.json +40 -0
  96. package/src/corpus/goldens/waivers.text +1 -0
  97. package/src/corpus/goldens-lsp/initialize.result.json +15 -0
  98. package/src/corpus/goldens-lsp/publish-cleared-empty.json +4 -0
  99. package/src/corpus/goldens-lsp/publish-fixed-empty.json +5 -0
  100. package/src/corpus/goldens-lsp/publish-unparseable.json +54 -0
  101. package/src/corpus/goldens-lsp/publish-violation.json +22 -0
  102. package/src/corpus/goldens-lsp/register-watched-files.json +72 -0
  103. package/src/custom-rules/values.mjs +1 -4
  104. package/src/go-work.mjs +8 -23
  105. package/src/governance/evolution-event.mjs +15 -0
  106. package/src/governance/evolution-store.mjs +59 -31
  107. package/src/governance/fitness-registry.mjs +14 -1
  108. package/src/governance/provenance-record.mjs +0 -152
  109. package/src/governance/verdict.mjs +62 -7
  110. package/src/intent/intent-manifest.json +22 -16
  111. package/src/lsp/server.mjs +5 -8
  112. package/src/lsp/workspace-index.mjs +55 -220
  113. package/src/options.mjs +10 -0
  114. package/src/providers/native/discover.mjs +13 -12
  115. package/src/providers/native/model.mjs +8 -3
  116. package/src/providers/nx-static.mjs +231 -0
  117. package/src/report/json.mjs +7 -4
  118. package/src/report/sarif.mjs +8 -3
  119. package/src/rules/edge-constraints.mjs +9 -0
  120. package/src/rules/index.mjs +26 -10
  121. package/src/rules/messages.mjs +64 -14
  122. package/src/rules/specifiers.mjs +21 -20
  123. package/src/tsconfig-paths.mjs +8 -14
  124. package/src/values.mjs +11 -0
  125. package/src/verdict.mjs +127 -16
@@ -37,17 +37,8 @@
37
37
  * or a polluted prototype cannot smuggle keys into a validated origin. This
38
38
  * module builds nothing from untrusted keys; it validates and, at write time,
39
39
  * builds a fresh object with only the three permitted keys.
40
- * ## The decision-lifecycle record
41
- *
42
- * The same discipline extends from a row to a DECISION (an ADR id — the
43
- * stable handle later waves reference). `recordDecisionLifecycle` records one
44
- * lifecycle event — a status transition, a supersession, or a bindings
45
- * change — attributed by the same `origin` shape and the same clock door:
46
- * `by`/`tool` are required, and `on` comes from `recordOrigin` and nowhere
47
- * else. A record that records nothing (a no-op transition) is refused loudly.
48
40
  */
49
41
 
50
- import { ADR_STATUSES } from "./adr-registry.mjs";
51
42
  import { clockViolations } from "./clock.mjs";
52
43
  import { describe, isPlainObject } from "../values.mjs";
53
44
 
@@ -175,146 +166,3 @@ export function recordOrigin({ by, tool, clock }) {
175
166
  // record, so two calls with the same clock are byte-identical.
176
167
  return { by, tool, on: clock.now() };
177
168
  }
178
- /**
179
- * The decision-lifecycle events one record can hold, each a single fact about
180
- * ONE decision (an ADR id — `docs/adr/NNN-slug.md` — the stable handle the
181
- * registry's `byId` map keys on). A decision's creation and every status
182
- * change, supersession, and bindings change is recorded as one of these.
183
- */
184
- export const DECISION_LIFECYCLE_KINDS = Object.freeze([
185
- // used by its own test
186
- "status-transition",
187
- "supersession",
188
- "bindings-change",
189
- ]);
190
-
191
- /**
192
- * @typedef {object} DecisionLifecycleRecord
193
- * @property {"status-transition"|"supersession"|"bindings-change"} kind
194
- * One recorded lifecycle event on one decision.
195
- * @property {string} decisionId The ADR id the event happened to — the stable
196
- * handle the ADR registry keys on (`docs/adr/NNN-slug.md`).
197
- * @property {string|null} [from] status-transition: the status the decision
198
- * left, or null when the event is the decision's creation (its proposed
199
- * entry).
200
- * @property {string} [to] status-transition: the status the decision entered.
201
- * @property {string[]} [superseded] supersession: the ADR id(s) this decision
202
- * replaced — `decisionId` is the RECORDING record, the successor.
203
- * @property {string[]} [added] bindings-change: constraint ids made
204
- * enforceable.
205
- * @property {string[]} [removed] bindings-change: constraint ids unbound.
206
- * @property {OriginRecord} origin WHO recorded the event and with what tool —
207
- * `on` produced by `recordOrigin`, the only door.
208
- */
209
-
210
- /**
211
- * Records one decision-lifecycle event, attributed by the same `origin`
212
- * discipline as a row: `by` and `tool` are required, and `on` comes from the
213
- * injected clock through `recordOrigin` — the only producer of an `on`, and
214
- * the refusal to run without one is its own, inherited here.
215
- *
216
- * The record carries ONLY the kind's own keys and the origin, built fresh, so
217
- * nothing from untrusted input rides along. A no-op event — a status
218
- * transition that changes nothing, a supersession naming no target, a
219
- * bindings change that adds and removes nothing — is refused loudly: a record
220
- * that records nothing would read as a transition that happened, the silent
221
- * direction this module exists to exclude.
222
- *
223
- * Statuses are validated against `ADR_STATUSES` (`./adr-registry.mjs`), the
224
- * single status vocabulary — a record can never attest a status the registry
225
- * could not hold.
226
- *
227
- * @param {{kind: "status-transition"|"supersession"|"bindings-change",
228
- * decisionId: string,
229
- * from?: string|null, to?: string,
230
- * superseded?: string[],
231
- * added?: string[], removed?: string[],
232
- * origin: {by: string, tool: string},
233
- * clock: import("./clock.mjs").Clock}} event
234
- * @returns {DecisionLifecycleRecord}
235
- * @throws {Error} on an unknown kind, a missing decisionId, a status outside
236
- * the registry's `ADR_STATUSES`, a no-op event, or an invalid origin/clock.
237
- */
238
- export function recordDecisionLifecycle({
239
- // used by its own test
240
- kind,
241
- decisionId,
242
- from = null,
243
- to,
244
- superseded,
245
- added,
246
- removed,
247
- origin,
248
- clock,
249
- }) {
250
- const violations = [];
251
- if (!DECISION_LIFECYCLE_KINDS.includes(kind)) {
252
- violations.push(
253
- `kind: must be one of ${DECISION_LIFECYCLE_KINDS.join(", ")}, got ${describe(kind)}`,
254
- );
255
- }
256
- if (typeof decisionId !== "string" || decisionId.trim() === "") {
257
- violations.push(
258
- `decisionId: must be a non-empty string naming the ADR, got ${describe(decisionId)}`,
259
- );
260
- }
261
- if (kind === "status-transition") {
262
- if (from !== null && !ADR_STATUSES.includes(from)) {
263
- violations.push(
264
- `from: must be null or one of ${ADR_STATUSES.join(", ")}, got ${describe(from)}`,
265
- );
266
- }
267
- if (!ADR_STATUSES.includes(to)) {
268
- violations.push(`to: must be one of ${ADR_STATUSES.join(", ")}, got ${describe(to)}`);
269
- }
270
- if (from !== null && from === to) {
271
- violations.push(
272
- `to: equals from (${JSON.stringify(from)}) — a status transition that changes nothing is not a recordable event`,
273
- );
274
- }
275
- } else if (kind === "supersession") {
276
- if (
277
- !Array.isArray(superseded) ||
278
- superseded.length === 0 ||
279
- superseded.some((ref) => typeof ref !== "string" || ref.trim() === "")
280
- ) {
281
- violations.push("superseded: must be a non-empty array of ADR ids this decision replaced");
282
- }
283
- } else if (kind === "bindings-change") {
284
- for (const [name, value] of [
285
- ["added", added],
286
- ["removed", removed],
287
- ]) {
288
- if (value !== undefined && !Array.isArray(value)) {
289
- violations.push(`${name}: must be an array of constraint ids, got ${describe(value)}`);
290
- } else if (
291
- Array.isArray(value) &&
292
- value.some((id) => typeof id !== "string" || id.trim() === "")
293
- ) {
294
- violations.push(`${name}: every entry must be a non-empty constraint id`);
295
- }
296
- }
297
- const addedList = Array.isArray(added) ? added : [];
298
- const removedList = Array.isArray(removed) ? removed : [];
299
- if (addedList.length === 0 && removedList.length === 0) {
300
- violations.push(
301
- "added/removed: a bindings change that adds nothing and removes nothing is not a recordable event",
302
- );
303
- }
304
- }
305
- if (violations.length > 0) {
306
- throw new Error(`decisionLifecycle: ${violations.join("; ")}`);
307
- }
308
- const eventFields =
309
- kind === "status-transition"
310
- ? { from, to }
311
- : kind === "supersession"
312
- ? { superseded }
313
- : { added: added ?? [], removed: removed ?? [] };
314
- return {
315
- kind,
316
- decisionId,
317
- ...eventFields,
318
- origin: recordOrigin({ by: origin?.by, tool: origin?.tool, clock }),
319
- };
320
- }
@@ -13,9 +13,17 @@
13
13
  * `"findings"` → `"fail"`
14
14
  * `"no-verdict"` → `"unknown"`
15
15
  *
16
- * and `"not_applicable"` has no source status in this release — it is the
17
- * vocabulary for Fitness functions and Waivers (a follow-up capability),
18
- * never for engine behavior today.
16
+ * and `"not_applicable"` has no source status in this release — no envelope
17
+ * status maps to it, so it never rides an envelope. Engine behavior does emit
18
+ * it today, on the paths that cannot judge: the fitness registry answers it
19
+ * for a declared function whose `match` selects no observed project, and
20
+ * again for `coverage-minimum` on a path-scoped run, which no scoped run can
21
+ * judge; decision fitness derives it for a decision without authority; the
22
+ * boundary metrics answer it when there is no boundary config to judge
23
+ * against (`./metrics.mjs`). A
24
+ * custom rule may return it as its own verdict — the host requires a
25
+ * `notApplicableReason` (`../custom-rules/host.mjs`) — but no engine path
26
+ * answers it on a rule's behalf.
19
27
  *
20
28
  * ## The invariants, I1–I5
21
29
  *
@@ -41,8 +49,25 @@
41
49
  * (`../verdict.mjs`) depend on the presentation layer. `../report/evidence.mjs`
42
50
  * re-exports `buildDecision` so the render-side callers keep their import
43
51
  * path — a path, never a second implementation.
52
+ *
53
+ * Layer: the verdict vocabulary and decision owner — `VERDICTS`, `isVerdict`,
54
+ * `verdictForStatus`, `fitnessVerdict`, and `buildDecision`. Its counterpart,
55
+ * `../verdict.mjs`, is the check lane built on top of `buildDecision` — the
56
+ * process's exit codes, the coverage clauses, and `verdictFor`, the per-lint
57
+ * verdict the `check` command words its envelope and its exit from. The two
58
+ * near-collision pairs cross that boundary, and the name decides which file
59
+ * a reader wants: `verdictForStatus` here looks one status up in a frozen
60
+ * map, while `../verdict.mjs`'s `verdictFor` folds a run's counts into
61
+ * status + exitCode; `VERDICT_FOR_STATUS` here is an independently frozen
62
+ * map, while `../verdict.mjs`'s `EXIT_FOR_STATUS` is derived from its own
63
+ * `EXIT`. Verdict vocabulary and decision construction live here; exit-code,
64
+ * coverage and check-lane concerns live there — and the layering stays
65
+ * one-directional (the check lane imports this module, never the reverse),
66
+ * so this module must not grow exit-code or coverage concerns.
44
67
  */
45
68
 
69
+ import { describe, isNonEmptyString } from "../values.mjs";
70
+
46
71
  /** The four canonical verdict values. */
47
72
  export const VERDICTS = Object.freeze(["pass", "fail", "unknown", "not_applicable"]);
48
73
 
@@ -113,7 +138,7 @@ export function fitnessVerdict({ verdict, name, evidence, message, rows, notAppl
113
138
  `expected one of ${VERDICTS.join(", ")}.`,
114
139
  );
115
140
  }
116
- if (verdict === "not_applicable" && notApplicableReason === undefined) {
141
+ if (verdict === "not_applicable" && !isNonEmptyString(notApplicableReason)) {
117
142
  throw new Error(
118
143
  `archkeep: fitness function "${name}" returned "not_applicable" without ` +
119
144
  `notApplicableReason — invariant I4: the reader must be told why the ` +
@@ -206,6 +231,21 @@ export function buildDecision(run) {
206
231
  throw new Error("archkeep: buildDecision needs either a status or an explicit verdict");
207
232
  }
208
233
  const verdict = run.verdict ?? verdictForStatus(run.status);
234
+ // The vocabulary latch. `verdictForStatus` cannot produce a stranger, so
235
+ // this refuses exactly the explicitly-passed verdicts — and it must run
236
+ // BEFORE the status-conflict check below, so a malformed verdict reports
237
+ // the vocabulary it violated rather than a conflict it never had. Without
238
+ // this latch, "perhaps", "PASS", `42` and `{}` fell through the
239
+ // pass/fail/unknown arms into the not_applicable one and SHIPPED whenever
240
+ // `notApplicableReason` happened to be truthy — a byte-legal decision
241
+ // about a workspace nobody judged.
242
+ if (!isVerdict(verdict)) {
243
+ throw new Error(
244
+ `archkeep: refusing to build a decision with verdict ${describe(run.verdict)} — ` +
245
+ `expected one of ${VERDICTS.join(", ")}. This is a bug in the command that ` +
246
+ `built the decision.`,
247
+ );
248
+ }
209
249
  if (
210
250
  run.verdict !== undefined &&
211
251
  run.status !== undefined &&
@@ -260,17 +300,32 @@ export function buildDecision(run) {
260
300
  }
261
301
 
262
302
  if (verdict === "unknown") {
303
+ // An absent reason (undefined/null) still defaults to the generic one —
304
+ // absence is not emptiness. A SUPPLIED reason must actually say
305
+ // something: "" and " " are byte-present but semantically absent, and
306
+ // a non-string reason would ship a `typeof` artifact where the reader
307
+ // was promised a sentence (I3).
263
308
  const reason =
264
309
  run.reason ??
265
310
  (run.coverageComplete === true ? "no verdict was reached" : "coverage was incomplete");
311
+ if (!isNonEmptyString(reason)) {
312
+ throw new Error(
313
+ `archkeep: refusing to emit an "unknown" decision with a ${describe(run.reason)} reason — ` +
314
+ `I3 requires a non-empty reason naming why no verdict was reached. ` +
315
+ `This is a bug in the command that built the decision.`,
316
+ );
317
+ }
266
318
  return withSampleTime({ verdict, reason }, run.sampleTime);
267
319
  }
268
320
 
269
- // verdict === "not_applicable" (I4).
270
- if (!run.notApplicableReason) {
321
+ // verdict === "not_applicable" (I4). The reason must be a string a reader
322
+ // could act on — the falsy check this replaces refused "" but SHIPPED
323
+ // " " and non-strings, the same byte-present-semantically-absent hole.
324
+ if (!isNonEmptyString(run.notApplicableReason)) {
271
325
  throw new Error(
272
326
  `archkeep: refusing to emit a "not_applicable" decision without notApplicableReason — ` +
273
- `"did not apply" and "did not run" must never be indistinguishable. ` +
327
+ `"did not apply" and "did not run" must never be indistinguishable, and a reason of ` +
328
+ `${describe(run.notApplicableReason)} tells the reader nothing. ` +
274
329
  `This is a bug in the command that built the decision.`,
275
330
  );
276
331
  }
@@ -25,7 +25,7 @@
25
25
  {
26
26
  "type": "source-evidence",
27
27
  "path": "src/commands/context.mjs",
28
- "assertion": "Only commands/context.mjs imports providers — the designated orchestration layer",
28
+ "assertion": "commands/context.mjs imports providers — the designated orchestration path",
29
29
  "sha256": "b505e3a7476c64af4b2daeb106e2f9cb05be82e314604229d508dda04b629a98"
30
30
  }
31
31
  ],
@@ -40,7 +40,7 @@
40
40
  "type": "behavioral-test",
41
41
  "path": "src/report/json.test.mjs",
42
42
  "assertion": "JSON envelope throws on status=ok + incomplete coverage, status/exitCode disagreement, coverage.complete/notAnalyzed disagreement",
43
- "sha256": "a371d36b89b10c5a4bda4d9e9a2e3bde114256c1377eb95699c9f0f195aabc4a"
43
+ "sha256": "8f587d3bd3f06519b90f760e7eafc091ca4e4cf26b67929ab180abe35c36c8f0"
44
44
  },
45
45
  {
46
46
  "type": "source-evidence",
@@ -52,7 +52,7 @@
52
52
  "type": "source-evidence",
53
53
  "path": "cli.mjs",
54
54
  "assertion": "Exit codes 0/1/3 distinguish clean/findings/cannot-look",
55
- "sha256": "81abe8243660b3cca913ee903c1fc0bfcdb6a240640c5302f18848e6a9473fe6"
55
+ "sha256": "2b6590f4039464d3569727d0a73db61dd86c18648a519ee034ebcc2d845ecfc7"
56
56
  }
57
57
  ],
58
58
  "status": "proven"
@@ -66,13 +66,13 @@
66
66
  "type": "behavioral-test",
67
67
  "path": "src/intent/intent.test.mjs",
68
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": "adb25388f17c97577e97e6b65bd3dacc5811710fe3bb60a409a51ff4abcd945e"
69
+ "sha256": "749414363ef9991ae3697fc00ab5969751666867792d4bb4dbe528ec47ccd740"
70
70
  },
71
71
  {
72
72
  "type": "architecture-test",
73
73
  "path": "src/intent/intent.test.mjs",
74
74
  "assertion": "the gate walks every production analysis module and fails when judging vocabulary (judge/forbid/permit/allow/ban) appears in code",
75
- "sha256": "adb25388f17c97577e97e6b65bd3dacc5811710fe3bb60a409a51ff4abcd945e"
75
+ "sha256": "749414363ef9991ae3697fc00ab5969751666867792d4bb4dbe528ec47ccd740"
76
76
  },
77
77
  {
78
78
  "type": "source-evidence",
@@ -84,7 +84,7 @@
84
84
  "type": "source-evidence",
85
85
  "path": "AGENTS.md",
86
86
  "assertion": "src/graph/ is a lossy view of analysis, on purpose; src/analysis/ never judges",
87
- "sha256": "3209154935cc1463127721203da74dc8bf8ba40258b182c5faa7345946611e38"
87
+ "sha256": "552b54c1956370f024ffeecec95c0e0941a0c66d4a14094045b444a25a27f913"
88
88
  }
89
89
  ],
90
90
  "status": "proven"
@@ -103,14 +103,20 @@
103
103
  {
104
104
  "type": "source-evidence",
105
105
  "path": "src/commands/graph.mjs",
106
- "assertion": "Plain string comparison, never localeCompare; INTERNAL_DATA_FIELDS stripped; SCHEMA_VERSION = 2",
107
- "sha256": "436902ab3ca994c233318437a896630f969a2c8a22b0f93e598d309faed39d49"
106
+ "assertion": "Plain string comparison, never localeCompare; INTERNAL_DATA_FIELDS stripped",
107
+ "sha256": "4f493421e11b012ce7b9c1b111155c8ebdbca6ab4ad3fa37221fbe1a465bef46"
108
+ },
109
+ {
110
+ "type": "source-evidence",
111
+ "path": "src/report/json.mjs",
112
+ "assertion": "SCHEMA_VERSION = 2 — the envelope's schemaVersion constant",
113
+ "sha256": "ac6db6e74b4ef7e54db12cc14ef57a7c5080ad081d7bf1273beb5ba543dcac24"
108
114
  },
109
115
  {
110
116
  "type": "source-evidence",
111
117
  "path": "src/commands/graph.mjs",
112
118
  "assertion": "computePolicyFingerprint produces SHA-256 of canonicalized policy",
113
- "sha256": "436902ab3ca994c233318437a896630f969a2c8a22b0f93e598d309faed39d49"
119
+ "sha256": "4f493421e11b012ce7b9c1b111155c8ebdbca6ab4ad3fa37221fbe1a465bef46"
114
120
  }
115
121
  ],
116
122
  "status": "proven"
@@ -130,7 +136,7 @@
130
136
  "type": "source-evidence",
131
137
  "path": "src/commands/diff.mjs",
132
138
  "assertion": "parseBaseline validates schemaVersion; refuses unknown versions",
133
- "sha256": "85e6400006a7e93c66e64547162c854320fa69d833a11cec42773703f522a163"
139
+ "sha256": "4d44eea475cd4b06122a405718203d2e0018a163c1d42b17324400e7e4de5eea"
134
140
  }
135
141
  ],
136
142
  "status": "proven"
@@ -150,7 +156,7 @@
150
156
  "type": "source-evidence",
151
157
  "path": "src/commands/diff.mjs",
152
158
  "assertion": "computeDiff returns structural diff; policyMismatch detected via fingerprint; computeRuleImpact for depConstraints-only context",
153
- "sha256": "85e6400006a7e93c66e64547162c854320fa69d833a11cec42773703f522a163"
159
+ "sha256": "4d44eea475cd4b06122a405718203d2e0018a163c1d42b17324400e7e4de5eea"
154
160
  }
155
161
  ],
156
162
  "status": "proven"
@@ -230,7 +236,7 @@
230
236
  "type": "behavioral-test",
231
237
  "path": "src/intent/intent.test.mjs",
232
238
  "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": "adb25388f17c97577e97e6b65bd3dacc5811710fe3bb60a409a51ff4abcd945e"
239
+ "sha256": "749414363ef9991ae3697fc00ab5969751666867792d4bb4dbe528ec47ccd740"
234
240
  },
235
241
  {
236
242
  "type": "behavioral-test",
@@ -248,19 +254,19 @@
248
254
  "type": "source-evidence",
249
255
  "path": "src/commands/context-command.mjs",
250
256
  "assertion": "coverage.notes warns that per-edge violations cover only depConstraints (3 of 15 violation types)",
251
- "sha256": "f906671ff4a5720d80ea5a3dab2779f1cfd3648fbc1e6a1f3a6d0e6efef21feb"
257
+ "sha256": "2d8a17dce1a5f4075ee02a7a8c1bdff94e70afe5cd609f63fe3cf6324f78aaf3"
252
258
  },
253
259
  {
254
260
  "type": "source-evidence",
255
261
  "path": "src/commands/impact.mjs",
256
262
  "assertion": "coverage.notes warns that per-edge violations cover only depConstraints (3 of 15 violation types)",
257
- "sha256": "152a53a0010d8cbd51d11e3ce6268fa50d1e2bcacab3d7bcb38dbd53fc120bbf"
263
+ "sha256": "440f20017cceae4b13c6bc5c653c7a8b99511e0c062dcc7abfb18859f3f0520c"
258
264
  },
259
265
  {
260
266
  "type": "source-evidence",
261
267
  "path": "src/commands/diff.mjs",
262
268
  "assertion": "coverage.notes warns when ruleImpact is computed (depConstraints only, 3 of 15)",
263
- "sha256": "85e6400006a7e93c66e64547162c854320fa69d833a11cec42773703f522a163"
269
+ "sha256": "4d44eea475cd4b06122a405718203d2e0018a163c1d42b17324400e7e4de5eea"
264
270
  },
265
271
  {
266
272
  "type": "documentation",
@@ -286,7 +292,7 @@
286
292
  "type": "source-evidence",
287
293
  "path": "src/commands/graph.mjs",
288
294
  "assertion": "Plain string comparison throughout; never localeCompare",
289
- "sha256": "436902ab3ca994c233318437a896630f969a2c8a22b0f93e598d309faed39d49"
295
+ "sha256": "4f493421e11b012ce7b9c1b111155c8ebdbca6ab4ad3fa37221fbe1a465bef46"
290
296
  }
291
297
  ],
292
298
  "status": "proven"
@@ -60,12 +60,8 @@ import {
60
60
  TEXT_DOCUMENT_SYNC_KIND,
61
61
  uriToPath,
62
62
  } from "./protocol.mjs";
63
- import {
64
- buildWorkspaceIndex,
65
- listWorkspaceFiles,
66
- PROJECT_CONFIG_FILE,
67
- readWorkspaceFile,
68
- } from "./workspace-index.mjs";
63
+ import { PROJECT_CONFIG_FILE } from "../providers/native/discover.mjs";
64
+ import { buildWorkspaceIndex, listWorkspaceFiles, readWorkspaceFile } from "./workspace-index.mjs";
69
65
 
70
66
  /**
71
67
  * A project's own `package.json`, and the two spellings of its Module
@@ -232,8 +228,9 @@ const POLYGLOT_GRAPH_MANIFESTS = Object.freeze([
232
228
  * the annotations cached against the old one. The stale waiver then publishes
233
229
  * `[]` for a real violation for the rest of the session, which is the silent
234
230
  * direction exactly (`../../../../AGENTS.md`). `package.json` earns the entry a
235
- * second way as well: it is where `./workspace-index.mjs`'s `discoverProjects`
236
- * takes a project's NAME when its `project.json` states none, so an edit to it
231
+ * second way as well: it is where the static acquisition
232
+ * (`../providers/nx-static.mjs`'s `discoverProjects`) takes a project's NAME
233
+ * when its `project.json` states none, so an edit to it
237
234
  * can move a project in the graph and not only waive something in it.
238
235
  *
239
236
  * The polyglot graph manifests are here for that same argument one level out,