@nimiplatform/nimi-coding 0.3.0 → 0.4.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 (118) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/CONTRIBUTING.md +3 -3
  3. package/README.md +285 -41
  4. package/README.zh-CN.md +294 -31
  5. package/bin/nimicoding.mjs +1 -1
  6. package/cli/commands/authority.mjs +849 -0
  7. package/cli/commands/clear.mjs +27 -7
  8. package/cli/commands/doctor.mjs +10 -1
  9. package/cli/commands/start.mjs +23 -27
  10. package/cli/commands/sync.mjs +21 -101
  11. package/cli/constants.mjs +7 -31
  12. package/cli/dev/check-authority-surface.mjs +173 -0
  13. package/cli/dev/check-product-conformance.mjs +917 -0
  14. package/cli/help.mjs +10 -17
  15. package/cli/index.mjs +6 -47
  16. package/cli/lib/authority/anchors.mjs +491 -0
  17. package/cli/lib/authority/audit.mjs +490 -0
  18. package/cli/lib/authority/closed-sets.mjs +646 -0
  19. package/cli/lib/authority/compile.mjs +110 -0
  20. package/cli/lib/authority/consumers.mjs +470 -0
  21. package/cli/lib/authority/diagnostics.mjs +87 -0
  22. package/cli/lib/authority/diff.mjs +287 -0
  23. package/cli/lib/authority/discover.mjs +447 -0
  24. package/cli/lib/authority/evidence-snapshot.mjs +1 -0
  25. package/cli/lib/authority/evidence.mjs +1432 -0
  26. package/cli/lib/authority/format.mjs +197 -0
  27. package/cli/lib/authority/git-snapshot.mjs +1065 -0
  28. package/cli/lib/authority/graph.mjs +351 -0
  29. package/cli/lib/authority/impact.mjs +252 -0
  30. package/cli/lib/authority/lexicon.mjs +55 -0
  31. package/cli/lib/authority/query.mjs +162 -0
  32. package/cli/lib/authority/repository-corpus.mjs +37 -0
  33. package/cli/lib/authority/repository-inventory.mjs +34 -0
  34. package/cli/lib/authority/review.mjs +222 -0
  35. package/cli/lib/authority/sarif.mjs +181 -0
  36. package/cli/lib/authority/scope-bindings.mjs +225 -0
  37. package/cli/lib/authority/source-map.mjs +105 -0
  38. package/cli/lib/authority/source-markdown.mjs +167 -0
  39. package/cli/lib/authority/source-yaml.mjs +312 -0
  40. package/cli/lib/authority/surface.mjs +79 -0
  41. package/cli/lib/authority/terms.mjs +421 -0
  42. package/cli/lib/authority/tracked-text.mjs +70 -0
  43. package/cli/lib/authority/validate.mjs +308 -0
  44. package/cli/lib/bootstrap.mjs +55 -71
  45. package/cli/lib/doctor.mjs +40 -123
  46. package/cli/lib/entrypoints.mjs +103 -105
  47. package/cli/lib/fs-helpers.mjs +139 -15
  48. package/cli/lib/internal/governance/config.mjs +0 -36
  49. package/cli/lib/shared.mjs +8 -21
  50. package/cli/lib/sync.mjs +81 -92
  51. package/cli/lib/value-helpers.mjs +0 -26
  52. package/cli/lib/yaml-helpers.mjs +1 -124
  53. package/cli/seeds/bootstrap.mjs +31 -87
  54. package/cli/seeds/seed-policy.yaml +9 -63
  55. package/contracts/authority-evidence-bindings.schema.yaml +53 -0
  56. package/contracts/authority-evidence-probe-results.schema.yaml +38 -0
  57. package/contracts/authority-impact-dispositions.schema.yaml +13 -0
  58. package/contracts/authority-scope-bindings.schema.yaml +15 -0
  59. package/contracts/authority-source.schema.yaml +64 -0
  60. package/contracts/authority-verifier-bindings.schema.yaml +20 -0
  61. package/methodology/authority-authoring.yaml +144 -0
  62. package/methodology/core.yaml +31 -26
  63. package/package.json +10 -6
  64. package/spec/authority-anchors.yaml +99 -0
  65. package/spec/authority-audit.yaml +85 -0
  66. package/spec/authority-authoring.yaml +150 -0
  67. package/spec/authority-closed-sets.yaml +65 -0
  68. package/spec/authority-consumers.yaml +70 -0
  69. package/spec/authority-context.yaml +50 -0
  70. package/spec/authority-discovery.yaml +119 -0
  71. package/spec/authority-evidence.yaml +200 -0
  72. package/spec/authority-graph.yaml +59 -0
  73. package/spec/authority-impact.yaml +84 -0
  74. package/spec/authority-review.yaml +89 -0
  75. package/spec/authority-terms.yaml +59 -0
  76. package/spec/product-scope.yaml +36 -23
  77. package/cli/commands/blueprint-audit.mjs +0 -91
  78. package/cli/commands/classify-spec-tree.mjs +0 -5
  79. package/cli/commands/generate-spec-derived-docs.mjs +0 -151
  80. package/cli/commands/generate-spec-migration-plan.mjs +0 -30
  81. package/cli/commands/surface-validator-command.mjs +0 -49
  82. package/cli/commands/validate-domain-admission.mjs +0 -5
  83. package/cli/commands/validate-guidance-bodies.mjs +0 -5
  84. package/cli/commands/validate-placement.mjs +0 -5
  85. package/cli/commands/validate-projection-edges.mjs +0 -5
  86. package/cli/commands/validate-spec-audit.mjs +0 -27
  87. package/cli/commands/validate-spec-governance.mjs +0 -124
  88. package/cli/commands/validate-spec-tree.mjs +0 -27
  89. package/cli/commands/validate-table-family.mjs +0 -5
  90. package/cli/commands/validate-tracked-output-admission.mjs +0 -5
  91. package/cli/lib/blueprint-audit.mjs +0 -363
  92. package/cli/lib/contracts.mjs +0 -21
  93. package/cli/lib/internal/contracts-loaders.mjs +0 -47
  94. package/cli/lib/internal/contracts-parse.mjs +0 -192
  95. package/cli/lib/internal/governance/runner.mjs +0 -35
  96. package/cli/lib/internal/surface-taxonomy-validators.mjs +0 -1147
  97. package/cli/lib/internal/validators-shared.mjs +0 -12
  98. package/cli/lib/internal/validators-spec-helpers.mjs +0 -52
  99. package/cli/lib/internal/validators-spec.mjs +0 -380
  100. package/cli/lib/validators.mjs +0 -30
  101. package/config/bootstrap.yaml +0 -6
  102. package/config/spec-generation-inputs.yaml +0 -41
  103. package/contracts/domain-admission.schema.yaml +0 -56
  104. package/contracts/migration-inventory.schema.yaml +0 -15
  105. package/contracts/negative-fixtures.yaml +0 -26
  106. package/contracts/placement-contract.schema.yaml +0 -127
  107. package/contracts/projection-edge.schema.yaml +0 -46
  108. package/contracts/shared-enums.yaml +0 -63
  109. package/contracts/spec-generation-audit.schema.yaml +0 -46
  110. package/contracts/spec-generation-inputs.schema.yaml +0 -43
  111. package/contracts/spec-layout.schema.yaml +0 -32
  112. package/contracts/surface-taxonomy.schema.yaml +0 -139
  113. package/contracts/table-family.schema.yaml +0 -133
  114. package/contracts/tracked-output-admission.schema.yaml +0 -17
  115. package/methodology/four-closure-policy.yaml +0 -20
  116. package/methodology/role-separation-policy.yaml +0 -22
  117. package/methodology/spec-reconstruction.yaml +0 -69
  118. package/spec/_meta/spec-tree-model.yaml +0 -44
@@ -0,0 +1,490 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { createHash } from "node:crypto";
3
+ import { lstat, readFile, stat } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ import YAML, { isMap, isScalar, isSeq } from "yaml";
8
+
9
+ import { compileAuthorityPath } from "./compile.mjs";
10
+ import { compareText, createLocator, makeDiagnostic, portablePath } from "./diagnostics.mjs";
11
+ import { buildAuthorityGraphSnapshot } from "./graph.mjs";
12
+
13
+ const AUDIT_FORMAT = "nimicoding.authority-audit/v1";
14
+ const BINDING_FORMAT = "nimicoding.authority-verifier-bindings/v1";
15
+ const DETECTOR = "minimum-independent-incoming-reference/v1";
16
+ const FINGERPRINT_FORMAT = "nimicoding.authority-audit-fingerprint/v1";
17
+ const contractPath = fileURLToPath(new URL("../../../contracts/authority-verifier-bindings.schema.yaml", import.meta.url));
18
+
19
+ let contractPromise = null;
20
+
21
+ function exactObject(value, fields) {
22
+ return value !== null && typeof value === "object" && !Array.isArray(value)
23
+ && Object.keys(value).length === fields.length
24
+ && Object.keys(value).every((key) => fields.includes(key));
25
+ }
26
+
27
+ function pointerToken(value) {
28
+ return String(value).replaceAll("~", "~0").replaceAll("/", "~1");
29
+ }
30
+
31
+ function nodeRange(node, locator) {
32
+ const range = node?.range;
33
+ if (!Array.isArray(range)) return locator.range(0);
34
+ return locator.range(range[0], range[1] ?? range[0]);
35
+ }
36
+
37
+ function collectLocations(node, locator, pointer, locations) {
38
+ if (!node) return;
39
+ locations.set(pointer, nodeRange(node, locator));
40
+ if (isMap(node)) {
41
+ for (const pair of node.items) {
42
+ const key = isScalar(pair.key) ? String(pair.key.value) : "";
43
+ const childPointer = `${pointer}/${pointerToken(key)}`;
44
+ locations.set(childPointer, nodeRange(pair.value ?? pair.key, locator));
45
+ collectLocations(pair.value, locator, childPointer, locations);
46
+ }
47
+ } else if (isSeq(node)) {
48
+ node.items.forEach((item, index) => collectLocations(item, locator, `${pointer}/${index}`, locations));
49
+ }
50
+ }
51
+
52
+ async function loadContract() {
53
+ if (!contractPromise) contractPromise = readFile(contractPath, "utf8").then((text) => {
54
+ const contract = YAML.parse(text);
55
+ if (contract?.version !== 1 || contract?.contract?.id !== "nimicoding.authority-verifier-bindings.v1") {
56
+ throw new Error("installed authority verifier binding contract is invalid");
57
+ }
58
+ return contract;
59
+ });
60
+ return contractPromise;
61
+ }
62
+
63
+ function bindingDiagnostic(file, reason, location = null) {
64
+ return makeDiagnostic({
65
+ code: "AUTH_AUDIT_BINDING_INVALID",
66
+ file,
67
+ range: location?.range ?? { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } },
68
+ pointer: location?.sourcePointer ?? "",
69
+ reason,
70
+ repair: "declare the exact closed verifier binding without inferring project policy from authority prose",
71
+ });
72
+ }
73
+
74
+ function parsedLocation(parsed, pointer) {
75
+ return {
76
+ file: parsed.file,
77
+ range: parsed.locations.get(pointer) ?? parsed.locations.get("") ?? { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } },
78
+ sourcePointer: pointer,
79
+ };
80
+ }
81
+
82
+ function syntaxLocation(error, file, locator) {
83
+ const positions = Array.isArray(error?.pos) ? error.pos : [0, 0];
84
+ return {
85
+ file,
86
+ range: locator.range(positions[0] ?? 0, positions[1] ?? positions[0] ?? 0),
87
+ sourcePointer: "",
88
+ };
89
+ }
90
+
91
+ async function loadBindings(file, labelOverride = null) {
92
+ const absolute = path.resolve(file);
93
+ const label = labelOverride ?? portablePath(absolute);
94
+ let bytes;
95
+ try {
96
+ const info = await lstat(absolute);
97
+ if (info.isSymbolicLink() || !info.isFile()) {
98
+ return { ok: false, diagnostics: [bindingDiagnostic(label, "verifier bindings must be one regular non-symlink YAML file")] };
99
+ }
100
+ bytes = await readFile(absolute);
101
+ } catch (error) {
102
+ return { ok: false, diagnostics: [bindingDiagnostic(label, `verifier binding file is not readable: ${error.message}`)] };
103
+ }
104
+ let text;
105
+ try {
106
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
107
+ } catch {
108
+ return { ok: false, diagnostics: [bindingDiagnostic(label, "verifier binding bytes are not valid UTF-8")] };
109
+ }
110
+ const locator = createLocator(text);
111
+ const documents = YAML.parseAllDocuments(text, {
112
+ keepSourceTokens: true,
113
+ prettyErrors: false,
114
+ strict: true,
115
+ uniqueKeys: true,
116
+ version: "1.2",
117
+ });
118
+ if (documents.length !== 1 || documents[0].errors.length > 0 || !isMap(documents[0].contents)) {
119
+ const error = documents[0]?.errors?.[0];
120
+ const location = syntaxLocation(error, label, locator);
121
+ const reason = documents.length !== 1
122
+ ? "verifier bindings must contain exactly one YAML document"
123
+ : error ? `invalid verifier binding YAML: ${error.message.split(" at line")[0]}` : "verifier bindings require one top-level mapping";
124
+ return { ok: false, diagnostics: [bindingDiagnostic(label, reason, location)] };
125
+ }
126
+ let data;
127
+ try {
128
+ data = documents[0].toJS({ maxAliasCount: 0 });
129
+ } catch (error) {
130
+ return { ok: false, diagnostics: [bindingDiagnostic(label, `invalid verifier binding YAML: ${error.message}`)] };
131
+ }
132
+ const locations = new Map();
133
+ collectLocations(documents[0].contents, locator, "", locations);
134
+ const parsed = { file: label, locations, data };
135
+ const contract = await loadContract();
136
+ if (!exactObject(data, contract.fields.top) || data.format !== BINDING_FORMAT || !Array.isArray(data.required_bindings) || data.required_bindings.length === 0 || !Array.isArray(data.bindings)) {
137
+ return { ok: false, diagnostics: [bindingDiagnostic(label, "verifier bindings require exact format, required_bindings, and bindings fields", parsedLocation(parsed, ""))] };
138
+ }
139
+ const identifier = new RegExp(contract.identifier_pattern);
140
+ const required = [];
141
+ const requiredIds = new Set();
142
+ for (let index = 0; index < data.required_bindings.length; index += 1) {
143
+ const id = data.required_bindings[index];
144
+ const pointer = `/required_bindings/${index}`;
145
+ if (typeof id !== "string" || !identifier.test(id)) {
146
+ return { ok: false, diagnostics: [bindingDiagnostic(label, "required binding IDs must be exact dotted lowercase identifiers", parsedLocation(parsed, pointer))] };
147
+ }
148
+ if (requiredIds.has(id)) return { ok: false, diagnostics: [bindingDiagnostic(label, `duplicate required binding ID: ${id}`, parsedLocation(parsed, pointer))] };
149
+ requiredIds.add(id);
150
+ required.push({ id, location: parsedLocation(parsed, pointer) });
151
+ }
152
+ const bindings = [];
153
+ const bindingIds = new Set();
154
+ for (let index = 0; index < data.bindings.length; index += 1) {
155
+ const value = data.bindings[index];
156
+ const pointer = `/bindings/${index}`;
157
+ if (!exactObject(value, contract.fields.binding)) {
158
+ return { ok: false, diagnostics: [bindingDiagnostic(label, "each binding requires exact id, detector, premise, targets, minimum, and policy fields", parsedLocation(parsed, pointer))] };
159
+ }
160
+ if (typeof value.id !== "string" || !identifier.test(value.id)) {
161
+ return { ok: false, diagnostics: [bindingDiagnostic(label, "binding id must be an exact dotted lowercase identifier", parsedLocation(parsed, `${pointer}/id`))] };
162
+ }
163
+ if (bindingIds.has(value.id)) return { ok: false, diagnostics: [bindingDiagnostic(label, `duplicate binding ID: ${value.id}`, parsedLocation(parsed, `${pointer}/id`))] };
164
+ bindingIds.add(value.id);
165
+ if (value.detector !== DETECTOR) {
166
+ return { ok: false, diagnostics: [bindingDiagnostic(label, `unknown detector: ${String(value.detector)}`, parsedLocation(parsed, `${pointer}/detector`))] };
167
+ }
168
+ if (typeof value.premise !== "string" || !identifier.test(value.premise)) {
169
+ return { ok: false, diagnostics: [bindingDiagnostic(label, "binding premise must be one exact authority ID", parsedLocation(parsed, `${pointer}/premise`))] };
170
+ }
171
+ if (!Array.isArray(value.targets) || value.targets.length === 0) {
172
+ return { ok: false, diagnostics: [bindingDiagnostic(label, "binding targets must be a non-empty exact ID list", parsedLocation(parsed, `${pointer}/targets`))] };
173
+ }
174
+ const targets = [];
175
+ const targetIds = new Set();
176
+ for (let targetIndex = 0; targetIndex < value.targets.length; targetIndex += 1) {
177
+ const target = value.targets[targetIndex];
178
+ const targetPointer = `${pointer}/targets/${targetIndex}`;
179
+ if (typeof target !== "string" || !identifier.test(target)) {
180
+ return { ok: false, diagnostics: [bindingDiagnostic(label, "binding targets must contain only exact authority IDs", parsedLocation(parsed, targetPointer))] };
181
+ }
182
+ if (targetIds.has(target)) return { ok: false, diagnostics: [bindingDiagnostic(label, `duplicate binding target: ${target}`, parsedLocation(parsed, targetPointer))] };
183
+ targetIds.add(target);
184
+ targets.push({ id: target, location: parsedLocation(parsed, targetPointer) });
185
+ }
186
+ if (!Number.isSafeInteger(value.minimum) || value.minimum <= 0) {
187
+ return { ok: false, diagnostics: [bindingDiagnostic(label, "binding minimum must be a positive safe integer", parsedLocation(parsed, `${pointer}/minimum`))] };
188
+ }
189
+ if (!contract.policies.includes(value.policy)) {
190
+ return { ok: false, diagnostics: [bindingDiagnostic(label, "binding policy must be advisory or blocking", parsedLocation(parsed, `${pointer}/policy`))] };
191
+ }
192
+ bindings.push({
193
+ ...value,
194
+ location: parsedLocation(parsed, `${pointer}/id`),
195
+ premiseLocation: parsedLocation(parsed, `${pointer}/premise`),
196
+ targetEntries: targets,
197
+ });
198
+ }
199
+ required.sort((left, right) => compareText(left.id, right.id));
200
+ bindings.sort((left, right) => compareText(left.id, right.id));
201
+ return { ok: true, diagnostics: [], required, bindings };
202
+ }
203
+
204
+ function refused(compiled, diagnostics) {
205
+ return {
206
+ ok: false,
207
+ diagnostics,
208
+ fileCount: compiled?.fileCount ?? 0,
209
+ unitCount: 0,
210
+ auditBytes: 0,
211
+ audit: null,
212
+ partial: false,
213
+ };
214
+ }
215
+
216
+ function fingerprint(parts) {
217
+ const digest = createHash("sha256").update([FINGERPRINT_FORMAT, ...parts].join("\0")).digest("hex");
218
+ return `sha256:${digest}`;
219
+ }
220
+
221
+ function relatedLocation(role, location) {
222
+ return { role, location };
223
+ }
224
+
225
+ function compareRelatedLocation(left, right) {
226
+ return compareText(left.role, right.role)
227
+ || compareText(left.location.file, right.location.file)
228
+ || left.location.range.start.line - right.location.range.start.line
229
+ || left.location.range.start.column - right.location.range.start.column
230
+ || compareText(left.location.sourcePointer, right.location.sourcePointer);
231
+ }
232
+
233
+ function edgeKey(edge) {
234
+ return `${edge.source}\0${edge.type}\0${edge.target}`;
235
+ }
236
+
237
+ function auditBudgetDiagnostic(kind, required, admitted, location = null) {
238
+ return makeDiagnostic({
239
+ code: "AUTH_AUDIT_BUDGET",
240
+ file: location?.file ?? ".",
241
+ range: location?.range ?? { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } },
242
+ pointer: location?.sourcePointer ?? "",
243
+ reason: `complete authority audit requires ${required} ${kind} but the explicit budget is ${admitted}`,
244
+ repair: "increase the explicit audit budget; partial observations, findings, or gaps are forbidden",
245
+ });
246
+ }
247
+
248
+ function validBudget(value) {
249
+ return Number.isSafeInteger(value) && value > 0;
250
+ }
251
+
252
+ function validateBindingAuthority(binding, snapshot, compiled) {
253
+ const premise = snapshot.byId.get(binding.premise);
254
+ if (!premise || premise.unit.kind !== "rule" || premise.unit.lifecycle !== "active") {
255
+ return refused(compiled, [bindingDiagnostic(
256
+ binding.premiseLocation.file,
257
+ `binding premise must resolve to one exact active rule: ${binding.premise}`,
258
+ binding.premiseLocation,
259
+ )]);
260
+ }
261
+ for (const target of binding.targetEntries) {
262
+ const resolved = snapshot.byId.get(target.id);
263
+ if (!resolved || resolved.unit.kind !== "definition" || resolved.unit.lifecycle !== "active") {
264
+ return refused(compiled, [bindingDiagnostic(
265
+ target.location.file,
266
+ `binding target must resolve to one exact active definition: ${target.id}`,
267
+ target.location,
268
+ )]);
269
+ }
270
+ }
271
+ return { premise };
272
+ }
273
+
274
+ function findingBase(binding, target, code, message, witness, primaryLocation, relatedLocations) {
275
+ const authorityRefs = [...new Set([binding.premise, target.id, ...(witness.incomingRefs ?? [])])].sort(compareText);
276
+ return {
277
+ code,
278
+ detector: DETECTOR,
279
+ binding: binding.id,
280
+ premise: binding.premise,
281
+ target: target.id,
282
+ basis: "governance_bound",
283
+ policy: binding.policy,
284
+ fingerprint: fingerprint([code, DETECTOR, binding.id, binding.premise, target.id, `minimum=${binding.minimum}`]),
285
+ message,
286
+ authorityRefs,
287
+ primaryLocation,
288
+ relatedLocations: [...relatedLocations].sort(compareRelatedLocation),
289
+ witness,
290
+ };
291
+ }
292
+
293
+ export function canonicalAuditBytes(audit) {
294
+ return Buffer.byteLength(JSON.stringify(audit), "utf8");
295
+ }
296
+
297
+ export async function auditAuthorityPath(inputPath, bindingsPath, { maxUnits, maxEdges, maxBytes, bindingLabel = null }) {
298
+ if (![maxUnits, maxEdges, maxBytes].every(validBudget)) {
299
+ return refused(null, [auditBudgetDiagnostic("positive safe-integer budget values", "all", "invalid")]);
300
+ }
301
+ const compiled = await compileAuthorityPath(inputPath);
302
+ if (!compiled.ok) return refused(compiled, compiled.diagnostics);
303
+ const parsedBindings = await loadBindings(bindingsPath, bindingLabel);
304
+ if (!parsedBindings.ok) return refused(compiled, parsedBindings.diagnostics);
305
+ const absoluteInput = path.resolve(inputPath);
306
+ const inputInfo = await stat(absoluteInput);
307
+ const basePath = inputInfo.isDirectory() ? absoluteInput : path.dirname(absoluteInput);
308
+ const snapshot = buildAuthorityGraphSnapshot(compiled, basePath, ["applies_to"]);
309
+ for (const binding of parsedBindings.bindings) {
310
+ const validation = validateBindingAuthority(binding, snapshot, compiled);
311
+ if (validation.ok === false) return validation;
312
+ }
313
+
314
+ const proofUnits = new Set();
315
+ const proofEdges = new Set();
316
+ const observations = [];
317
+ const findings = [];
318
+ const gaps = [];
319
+ const presentBindings = new Set(parsedBindings.bindings.map((binding) => binding.id));
320
+
321
+ for (const required of parsedBindings.required) {
322
+ if (presentBindings.has(required.id)) continue;
323
+ const code = "AUTH_AUDIT_REQUIRED_BINDING_MISSING";
324
+ gaps.push({
325
+ code,
326
+ detector: null,
327
+ binding: required.id,
328
+ premise: null,
329
+ basis: "required_coverage",
330
+ policy: "required",
331
+ fingerprint: fingerprint([code, required.id]),
332
+ message: `required verifier binding is missing: ${required.id}`,
333
+ authorityRefs: [],
334
+ primaryLocation: required.location,
335
+ relatedLocations: [],
336
+ witness: { type: "required-binding", required: true, present: false },
337
+ });
338
+ }
339
+
340
+ for (const binding of parsedBindings.bindings) {
341
+ const premise = snapshot.byId.get(binding.premise);
342
+ proofUnits.add(binding.premise);
343
+ const premiseEdges = snapshot.outgoing.get(binding.premise).filter((edge) => edge.type === "applies_to");
344
+ for (const edge of premiseEdges) {
345
+ proofEdges.add(edgeKey(edge));
346
+ proofUnits.add(edge.source);
347
+ proofUnits.add(edge.target);
348
+ }
349
+ const targetObservations = [];
350
+ const observationRelated = [relatedLocation("premise", premise.node.location)];
351
+ for (const target of [...binding.targetEntries].sort((left, right) => compareText(left.id, right.id))) {
352
+ const targetNode = snapshot.byId.get(target.id);
353
+ proofUnits.add(target.id);
354
+ const attachment = premiseEdges.find((edge) => edge.target === target.id) ?? null;
355
+ if (attachment) proofEdges.add(edgeKey(attachment));
356
+ const incomingCandidates = snapshot.incoming.get(target.id).filter((edge) => edge.type === "applies_to");
357
+ for (const edge of incomingCandidates) {
358
+ proofEdges.add(edgeKey(edge));
359
+ proofUnits.add(edge.source);
360
+ proofUnits.add(edge.target);
361
+ }
362
+ const incoming = incomingCandidates
363
+ .filter((edge) => edge.source !== binding.premise)
364
+ .filter((edge) => {
365
+ const source = snapshot.byId.get(edge.source).unit;
366
+ return source.kind === "rule" && source.lifecycle === "active";
367
+ });
368
+ targetObservations.push({
369
+ id: target.id,
370
+ attached: attachment !== null,
371
+ observed: new Set(incoming.map((edge) => edge.source)).size,
372
+ incomingRefs: [...new Set(incoming.map((edge) => edge.source))].sort(compareText),
373
+ });
374
+ observationRelated.push(relatedLocation("binding-target", target.location));
375
+ observationRelated.push(relatedLocation("target", targetNode.node.location));
376
+ if (attachment) observationRelated.push(relatedLocation("premise-attachment", attachment.relationLocation));
377
+ for (const edge of incoming) observationRelated.push(relatedLocation("incoming-reference", edge.relationLocation));
378
+ const commonRelated = [
379
+ relatedLocation("binding", binding.location),
380
+ relatedLocation("binding-target", target.location),
381
+ relatedLocation("premise", premise.node.location),
382
+ ];
383
+ if (!attachment) {
384
+ findings.push(findingBase(
385
+ binding,
386
+ target,
387
+ "AUTH_AUDIT_PREMISE_TARGET_UNATTACHED",
388
+ `premise ${binding.premise} does not directly attach required target ${target.id}`,
389
+ { type: "premise-direct-attachment", relation: "applies_to", attached: false },
390
+ targetNode.node.location,
391
+ commonRelated,
392
+ ));
393
+ } else {
394
+ commonRelated.push(relatedLocation("premise-attachment", attachment.relationLocation));
395
+ }
396
+ const observed = new Set(incoming.map((edge) => edge.source)).size;
397
+ if (observed < binding.minimum) {
398
+ for (const edge of incoming) commonRelated.push(relatedLocation("incoming-reference", edge.relationLocation));
399
+ findings.push(findingBase(
400
+ binding,
401
+ target,
402
+ "AUTH_AUDIT_MINIMUM_INDEPENDENT_INCOMING_REFERENCE",
403
+ `target ${target.id} has ${observed} independent active-rule applies_to references; binding requires ${binding.minimum}`,
404
+ {
405
+ type: "minimum-independent-incoming-reference",
406
+ relation: "applies_to",
407
+ minimum: binding.minimum,
408
+ observed,
409
+ incomingRefs: [...new Set(incoming.map((edge) => edge.source))].sort(compareText),
410
+ },
411
+ targetNode.node.location,
412
+ commonRelated,
413
+ ));
414
+ }
415
+ }
416
+ observations.push({
417
+ code: "AUTH_AUDIT_INDEPENDENT_INCOMING_REFERENCE_OBSERVATION",
418
+ detector: DETECTOR,
419
+ binding: binding.id,
420
+ premise: binding.premise,
421
+ basis: "governance_bound",
422
+ policy: binding.policy,
423
+ fingerprint: fingerprint([
424
+ "AUTH_AUDIT_INDEPENDENT_INCOMING_REFERENCE_OBSERVATION",
425
+ DETECTOR,
426
+ binding.id,
427
+ binding.premise,
428
+ `targets=${targetObservations.map((target) => target.id).join(",")}`,
429
+ `minimum=${binding.minimum}`,
430
+ ]),
431
+ message: `evaluated ${targetObservations.length} exact targets for binding ${binding.id}`,
432
+ authorityRefs: [...new Set([
433
+ binding.premise,
434
+ ...targetObservations.flatMap((target) => [target.id, ...target.incomingRefs]),
435
+ ])].sort(compareText),
436
+ primaryLocation: binding.location,
437
+ relatedLocations: observationRelated.sort(compareRelatedLocation),
438
+ witness: {
439
+ type: "minimum-independent-incoming-reference",
440
+ relation: "applies_to",
441
+ minimum: binding.minimum,
442
+ targets: targetObservations,
443
+ },
444
+ });
445
+ }
446
+
447
+ findings.sort((left, right) => compareText(left.binding, right.binding) || compareText(left.target, right.target) || compareText(left.code, right.code));
448
+ gaps.sort((left, right) => compareText(left.binding, right.binding));
449
+ const traversal = { units: proofUnits.size, edges: proofEdges.size };
450
+ const firstLocation = parsedBindings.bindings[0]?.location ?? parsedBindings.required[0]?.location ?? null;
451
+ if (traversal.units > maxUnits) return refused(compiled, [auditBudgetDiagnostic("units", traversal.units, maxUnits, firstLocation)]);
452
+ if (traversal.edges > maxEdges) return refused(compiled, [auditBudgetDiagnostic("edges", traversal.edges, maxEdges, firstLocation)]);
453
+ const blocking = findings.some((finding) => finding.policy === "blocking");
454
+ const audit = {
455
+ format: AUDIT_FORMAT,
456
+ operationStatus: "completed",
457
+ policyStatus: blocking ? "blocked" : gaps.length > 0 ? "indeterminate" : "passed",
458
+ complete: gaps.length === 0,
459
+ observations,
460
+ findings,
461
+ gaps,
462
+ counts: {
463
+ bindings: {
464
+ required: parsedBindings.required.length,
465
+ configured: parsedBindings.bindings.length,
466
+ evaluated: observations.length,
467
+ },
468
+ returned: {
469
+ observations: observations.length,
470
+ findings: findings.length,
471
+ gaps: gaps.length,
472
+ },
473
+ traversal,
474
+ },
475
+ budgets: { maxUnits, maxEdges, maxBytes },
476
+ };
477
+ const auditBytes = canonicalAuditBytes(audit);
478
+ if (auditBytes > maxBytes) return refused(compiled, [auditBudgetDiagnostic("UTF-8 bytes", auditBytes, maxBytes, firstLocation)]);
479
+ return {
480
+ ok: true,
481
+ diagnostics: [],
482
+ fileCount: compiled.fileCount,
483
+ unitCount: compiled.unitCount,
484
+ auditBytes,
485
+ audit,
486
+ partial: false,
487
+ };
488
+ }
489
+
490
+ export { AUDIT_FORMAT, BINDING_FORMAT, DETECTOR };