@adrkit/evaluator 0.1.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 (81) hide show
  1. package/README.md +23 -0
  2. package/dist/LICENSE +201 -0
  3. package/dist/NOTICE +11 -0
  4. package/dist/assertions/evaluate.d.ts +36 -0
  5. package/dist/assertions/jsonpath.d.ts +20 -0
  6. package/dist/assertions/limits.d.ts +21 -0
  7. package/dist/assertions/registry.d.ts +20 -0
  8. package/dist/assertions/rego.d.ts +27 -0
  9. package/dist/catalog.d.ts +39 -0
  10. package/dist/compare.d.ts +10 -0
  11. package/dist/crypto/sha256.d.ts +10 -0
  12. package/dist/identity/directory.d.ts +23 -0
  13. package/dist/index.d.ts +24 -0
  14. package/dist/index.js +2064 -0
  15. package/dist/keys.d.ts +27 -0
  16. package/dist/pass0.d.ts +19 -0
  17. package/dist/patch/project.d.ts +20 -0
  18. package/dist/report/aggregate.d.ts +21 -0
  19. package/dist/report/assemble.d.ts +16 -0
  20. package/dist/report/order.d.ts +19 -0
  21. package/dist/report/serialize.d.ts +42 -0
  22. package/dist/routing/accepted-assertion.d.ts +12 -0
  23. package/dist/routing/route.d.ts +18 -0
  24. package/dist/routing/target.d.ts +20 -0
  25. package/dist/rules/affects-overlap.d.ts +13 -0
  26. package/dist/rules/affects-resolvable.d.ts +14 -0
  27. package/dist/rules/assertions-compile.d.ts +12 -0
  28. package/dist/rules/assertions-pass.d.ts +13 -0
  29. package/dist/rules/context.d.ts +21 -0
  30. package/dist/rules/decider-resolvable.d.ts +10 -0
  31. package/dist/rules/expiry-sane.d.ts +11 -0
  32. package/dist/rules/id-unique.d.ts +11 -0
  33. package/dist/rules/kernel.d.ts +16 -0
  34. package/dist/rules/no-orphan-refs.d.ts +12 -0
  35. package/dist/rules/schema-valid.d.ts +11 -0
  36. package/dist/rules/scope-hierarchy.d.ts +14 -0
  37. package/dist/rules/supersession-consistent.d.ts +12 -0
  38. package/dist/targets/canonical.d.ts +37 -0
  39. package/dist/targets/package.d.ts +11 -0
  40. package/dist/targets/path.d.ts +10 -0
  41. package/dist/targets/registry.d.ts +11 -0
  42. package/dist/types.d.ts +360 -0
  43. package/package.json +54 -0
  44. package/src/assertions/evaluate.ts +214 -0
  45. package/src/assertions/jsonpath.ts +95 -0
  46. package/src/assertions/limits.ts +57 -0
  47. package/src/assertions/registry.ts +38 -0
  48. package/src/assertions/rego.ts +272 -0
  49. package/src/catalog.ts +263 -0
  50. package/src/compare.ts +13 -0
  51. package/src/crypto/sha256.ts +101 -0
  52. package/src/identity/directory.ts +69 -0
  53. package/src/index.ts +81 -0
  54. package/src/keys.ts +55 -0
  55. package/src/pass0.ts +163 -0
  56. package/src/patch/project.ts +51 -0
  57. package/src/report/aggregate.ts +59 -0
  58. package/src/report/assemble.ts +43 -0
  59. package/src/report/order.ts +53 -0
  60. package/src/report/serialize.ts +152 -0
  61. package/src/routing/accepted-assertion.ts +39 -0
  62. package/src/routing/route.ts +105 -0
  63. package/src/routing/target.ts +104 -0
  64. package/src/rules/affects-overlap.ts +55 -0
  65. package/src/rules/affects-resolvable.ts +83 -0
  66. package/src/rules/assertions-compile.ts +18 -0
  67. package/src/rules/assertions-pass.ts +21 -0
  68. package/src/rules/context.ts +31 -0
  69. package/src/rules/decider-resolvable.ts +55 -0
  70. package/src/rules/expiry-sane.ts +30 -0
  71. package/src/rules/id-unique.ts +57 -0
  72. package/src/rules/kernel.ts +33 -0
  73. package/src/rules/no-orphan-refs.ts +102 -0
  74. package/src/rules/schema-valid.ts +49 -0
  75. package/src/rules/scope-hierarchy.ts +108 -0
  76. package/src/rules/supersession-consistent.ts +138 -0
  77. package/src/targets/canonical.ts +114 -0
  78. package/src/targets/package.ts +41 -0
  79. package/src/targets/path.ts +32 -0
  80. package/src/targets/registry.ts +23 -0
  81. package/src/types.ts +445 -0
package/dist/index.js ADDED
@@ -0,0 +1,2064 @@
1
+ // src/catalog.ts
2
+ var RULE_IDS = [
3
+ "schema-valid",
4
+ "id-unique",
5
+ "supersession-consistent",
6
+ "no-orphan-refs",
7
+ "affects-resolvable",
8
+ "affects-overlap",
9
+ "scope-hierarchy",
10
+ "assertions-compile",
11
+ "assertions-pass",
12
+ "decider-resolvable",
13
+ "expiry-sane"
14
+ ];
15
+ var RULE_SEVERITY = {
16
+ "schema-valid": "error",
17
+ "id-unique": "error",
18
+ "supersession-consistent": "error",
19
+ "no-orphan-refs": "error",
20
+ "affects-resolvable": "warn",
21
+ "affects-overlap": "warn",
22
+ "scope-hierarchy": "error",
23
+ "assertions-compile": "error",
24
+ "assertions-pass": "warn",
25
+ "decider-resolvable": "warn",
26
+ "expiry-sane": "info"
27
+ };
28
+ var ROUTING_TRIGGERS = [
29
+ "one-way-door",
30
+ "cost-threshold",
31
+ "security-surface",
32
+ "data-residency",
33
+ "regulatory",
34
+ "contradicts-accepted-adr",
35
+ "agent-authored-production",
36
+ "human-requested"
37
+ ];
38
+ var REASON_CODES = [
39
+ "schema-valid.ok",
40
+ "schema-valid.file-read",
41
+ "schema-valid.parse-error",
42
+ "schema-valid.contract-error",
43
+ "id-unique.ok",
44
+ "id-unique.collision",
45
+ "supersession-consistent.ok",
46
+ "supersession-consistent.dangling-superseded-by",
47
+ "supersession-consistent.non-reciprocal",
48
+ "supersession-consistent.cycle",
49
+ "no-orphan-refs.ok",
50
+ "no-orphan-refs.dangling-supersedes",
51
+ "no-orphan-refs.dangling-relates-to",
52
+ "no-orphan-refs.federated-log-absent",
53
+ "affects-resolvable.ok",
54
+ "affects-resolvable.zero-targets",
55
+ "affects-resolvable.backing-absent",
56
+ "affects-resolvable.resolver-absent",
57
+ "affects-overlap.accepted-intersection",
58
+ "affects-overlap.no-accepted-corpus",
59
+ "affects-overlap.backing-absent",
60
+ "affects-overlap.none",
61
+ "scope-hierarchy.ok",
62
+ "scope-hierarchy.contradicts-org-assertion",
63
+ "scope-hierarchy.evidence-absent",
64
+ "scope-hierarchy.engine-absent",
65
+ "scope-hierarchy.source-absent",
66
+ "scope-hierarchy.base-input-absent",
67
+ "scope-hierarchy.proposed-input-absent",
68
+ "scope-hierarchy.not-applicable-scope",
69
+ "assertions-compile.ok",
70
+ "assertions-compile.none",
71
+ "assertions-compile.no-source",
72
+ "assertions-compile.ambiguous-source",
73
+ "assertions-compile.parse-error",
74
+ "assertions-compile.engine-absent",
75
+ "assertions-compile.source-absent",
76
+ "assertions-pass.ok",
77
+ "assertions-pass.none",
78
+ "assertions-pass.evaluates-false",
79
+ "assertions-pass.evaluation-error",
80
+ "assertions-pass.engine-absent",
81
+ "assertions-pass.input-absent",
82
+ "decider-resolvable.ok",
83
+ "decider-resolvable.none-declared",
84
+ "decider-resolvable.zero-match",
85
+ "decider-resolvable.ambiguous-match",
86
+ "decider-resolvable.directory-absent",
87
+ "expiry-sane.ok",
88
+ "expiry-sane.past-or-equal",
89
+ "not-evaluated.schema-invalid",
90
+ "not-evaluated.prereq-failed",
91
+ "route.escalate.one-way-door",
92
+ "route.escalate.cost-threshold",
93
+ "route.escalate.security-surface",
94
+ "route.escalate.data-residency",
95
+ "route.escalate.regulatory",
96
+ "route.escalate.contradicts-accepted-adr",
97
+ "route.escalate.agent-authored-production",
98
+ "route.escalate.human-requested",
99
+ "route.evidence.one-way-door.not-proven",
100
+ "route.evidence.cost-threshold.not-proven",
101
+ "route.evidence.security-surface.not-proven",
102
+ "route.evidence.data-residency.not-proven",
103
+ "route.evidence.regulatory.not-proven",
104
+ "route.evidence.contradicts-accepted-adr.not-proven",
105
+ "route.evidence.agent-authored-production.not-proven",
106
+ "route.evidence.human-requested.not-proven",
107
+ "route.target.not-required",
108
+ "route.target.deciders",
109
+ "route.target.codeowners",
110
+ "route.target.catalog-owner",
111
+ "route.target.unresolved"
112
+ ];
113
+ var REASON_CODE_SET = new Set(REASON_CODES);
114
+ function isReasonCode(value) {
115
+ return REASON_CODE_SET.has(value);
116
+ }
117
+ var RULE_REASON_PRECEDENCE = {
118
+ "schema-valid": [
119
+ "schema-valid.ok",
120
+ "schema-valid.file-read",
121
+ "schema-valid.parse-error",
122
+ "schema-valid.contract-error"
123
+ ],
124
+ "id-unique": ["id-unique.ok", "id-unique.collision"],
125
+ "supersession-consistent": [
126
+ "supersession-consistent.ok",
127
+ "supersession-consistent.dangling-superseded-by",
128
+ "supersession-consistent.non-reciprocal",
129
+ "supersession-consistent.cycle"
130
+ ],
131
+ "no-orphan-refs": [
132
+ "no-orphan-refs.ok",
133
+ "no-orphan-refs.dangling-supersedes",
134
+ "no-orphan-refs.dangling-relates-to",
135
+ "no-orphan-refs.federated-log-absent"
136
+ ],
137
+ "affects-resolvable": [
138
+ "affects-resolvable.ok",
139
+ "affects-resolvable.zero-targets",
140
+ "affects-resolvable.backing-absent",
141
+ "affects-resolvable.resolver-absent"
142
+ ],
143
+ "affects-overlap": [
144
+ "affects-overlap.accepted-intersection",
145
+ "affects-overlap.no-accepted-corpus",
146
+ "affects-overlap.backing-absent",
147
+ "affects-overlap.none"
148
+ ],
149
+ "scope-hierarchy": [
150
+ "scope-hierarchy.ok",
151
+ "scope-hierarchy.contradicts-org-assertion",
152
+ "scope-hierarchy.evidence-absent",
153
+ "scope-hierarchy.engine-absent",
154
+ "scope-hierarchy.source-absent",
155
+ "scope-hierarchy.base-input-absent",
156
+ "scope-hierarchy.proposed-input-absent",
157
+ "scope-hierarchy.not-applicable-scope"
158
+ ],
159
+ "assertions-compile": [
160
+ "assertions-compile.ok",
161
+ "assertions-compile.none",
162
+ "assertions-compile.no-source",
163
+ "assertions-compile.ambiguous-source",
164
+ "assertions-compile.parse-error",
165
+ "assertions-compile.engine-absent",
166
+ "assertions-compile.source-absent"
167
+ ],
168
+ "assertions-pass": [
169
+ "assertions-pass.ok",
170
+ "assertions-pass.none",
171
+ "assertions-pass.evaluates-false",
172
+ "assertions-pass.evaluation-error",
173
+ "assertions-pass.engine-absent",
174
+ "assertions-pass.input-absent"
175
+ ],
176
+ "decider-resolvable": [
177
+ "decider-resolvable.ok",
178
+ "decider-resolvable.none-declared",
179
+ "decider-resolvable.zero-match",
180
+ "decider-resolvable.ambiguous-match",
181
+ "decider-resolvable.directory-absent"
182
+ ],
183
+ "expiry-sane": ["expiry-sane.ok", "expiry-sane.past-or-equal"]
184
+ };
185
+ var ROUTE_ESCALATE_CODE = {
186
+ "one-way-door": "route.escalate.one-way-door",
187
+ "cost-threshold": "route.escalate.cost-threshold",
188
+ "security-surface": "route.escalate.security-surface",
189
+ "data-residency": "route.escalate.data-residency",
190
+ regulatory: "route.escalate.regulatory",
191
+ "contradicts-accepted-adr": "route.escalate.contradicts-accepted-adr",
192
+ "agent-authored-production": "route.escalate.agent-authored-production",
193
+ "human-requested": "route.escalate.human-requested"
194
+ };
195
+ var ROUTE_EVIDENCE_NOT_PROVEN_CODE = {
196
+ "one-way-door": "route.evidence.one-way-door.not-proven",
197
+ "cost-threshold": "route.evidence.cost-threshold.not-proven",
198
+ "security-surface": "route.evidence.security-surface.not-proven",
199
+ "data-residency": "route.evidence.data-residency.not-proven",
200
+ regulatory: "route.evidence.regulatory.not-proven",
201
+ "contradicts-accepted-adr": "route.evidence.contradicts-accepted-adr.not-proven",
202
+ "agent-authored-production": "route.evidence.agent-authored-production.not-proven",
203
+ "human-requested": "route.evidence.human-requested.not-proven"
204
+ };
205
+ var RUBRIC_VERSION = "0.1.0";
206
+
207
+ // src/keys.ts
208
+ function makeAssertionKey(log, path, id) {
209
+ return JSON.stringify([log ?? "", path, id]);
210
+ }
211
+ function assertionKeyForAssertion(record, assertion) {
212
+ return makeAssertionKey(record.log, record.path, assertion.id);
213
+ }
214
+ function isCanonicalAssertionKey(key) {
215
+ let parsed;
216
+ try {
217
+ parsed = JSON.parse(key);
218
+ } catch {
219
+ return false;
220
+ }
221
+ if (!Array.isArray(parsed) || parsed.length !== 3)
222
+ return false;
223
+ if (!parsed.every((part) => typeof part === "string"))
224
+ return false;
225
+ return JSON.stringify(parsed) === key;
226
+ }
227
+ function parseAssertionKey(key) {
228
+ if (!isCanonicalAssertionKey(key))
229
+ return;
230
+ const [log, path, id] = JSON.parse(key);
231
+ return { log, path, id };
232
+ }
233
+ function canonicalTargetKey(id) {
234
+ return `${id.kind}:${id.id}`;
235
+ }
236
+
237
+ // src/compare.ts
238
+ function byCodeUnit(a, b) {
239
+ return a < b ? -1 : a > b ? 1 : 0;
240
+ }
241
+
242
+ // src/report/order.ts
243
+ function cmp(a, b) {
244
+ return byCodeUnit(a ?? "", b ?? "");
245
+ }
246
+ function targetKey(target) {
247
+ return target ? canonicalTargetKey(target) : "";
248
+ }
249
+ function compareRuleFindings(a, b) {
250
+ return cmp(a.candidateAdr, b.candidateAdr) || cmp(a.relatedAdr, b.relatedAdr) || cmp(a.matcherKey ?? a.assertionKey, b.matcherKey ?? b.assertionKey) || cmp(targetKey(a.target), targetKey(b.target)) || cmp(a.recordPath, b.recordPath) || cmp(a.field, b.field) || cmp(a.adr, b.adr) || cmp(a.message, b.message);
251
+ }
252
+ function sortRuleFindings(findings) {
253
+ return [...findings].sort(compareRuleFindings);
254
+ }
255
+ var RULE_INDEX = new Map(RULE_IDS.map((rule, index) => [rule, index]));
256
+
257
+ // src/report/aggregate.ts
258
+ var STATUS_RANK = { fail: 3, inert: 2, pass: 1 };
259
+ function winningStatus(subs) {
260
+ let winner = "pass";
261
+ for (const sub of subs) {
262
+ if (STATUS_RANK[sub.status] > STATUS_RANK[winner])
263
+ winner = sub.status;
264
+ }
265
+ return winner;
266
+ }
267
+ function primaryReason(rule, winners) {
268
+ const present = new Set(winners.map((sub) => sub.reason));
269
+ for (const code of RULE_REASON_PRECEDENCE[rule]) {
270
+ if (present.has(code))
271
+ return code;
272
+ }
273
+ return RULE_REASON_PRECEDENCE[rule][0];
274
+ }
275
+ function aggregate(rule, subs, evidence) {
276
+ const status = subs.length === 0 ? "pass" : winningStatus(subs);
277
+ const winners = subs.filter((sub) => sub.status === status);
278
+ const reason = winners.length === 0 ? RULE_REASON_PRECEDENCE[rule][0] : primaryReason(rule, winners);
279
+ const findings = sortRuleFindings(subs.flatMap((sub) => sub.finding ? [sub.finding] : []));
280
+ const severity = status === "fail" ? RULE_SEVERITY[rule] : undefined;
281
+ return {
282
+ rule,
283
+ status,
284
+ ...severity ? { severity } : {},
285
+ reason,
286
+ findings,
287
+ ...evidence ? { evidence } : {}
288
+ };
289
+ }
290
+
291
+ // src/rules/kernel.ts
292
+ function passResult(rule, reason = RULE_REASON_PRECEDENCE[rule][0]) {
293
+ return { rule, status: "pass", reason, findings: [] };
294
+ }
295
+ function inertResult(rule, reason, finding) {
296
+ return { rule, status: "inert", reason, findings: finding ? [finding] : [] };
297
+ }
298
+ function notEvaluated(rule, reason) {
299
+ return { rule, status: "not-evaluated", reason, findings: [] };
300
+ }
301
+
302
+ // src/rules/context.ts
303
+ function acceptedRecordsExcludingCandidate(records, proposalPath) {
304
+ return records.filter((record) => record.frontmatter.status === "accepted" && record.path !== proposalPath);
305
+ }
306
+
307
+ // src/rules/schema-valid.ts
308
+ var PARSE_RULES = new Set(["frontmatter-parse", "frontmatter-fence"]);
309
+ function reasonForFinding(finding) {
310
+ if (finding.rule === "file-read")
311
+ return "schema-valid.file-read";
312
+ if (PARSE_RULES.has(finding.rule))
313
+ return "schema-valid.parse-error";
314
+ return "schema-valid.contract-error";
315
+ }
316
+ function toRuleFinding(finding) {
317
+ return {
318
+ reason: reasonForFinding(finding),
319
+ ...finding.message ? { message: finding.message } : {},
320
+ ...finding.path ? { recordPath: finding.path } : {},
321
+ ...finding.field ? { field: finding.field } : {},
322
+ lowerLevel: {
323
+ rule: finding.rule,
324
+ ...finding.path ? { path: finding.path } : {},
325
+ ...finding.id ? { id: finding.id } : {},
326
+ ...finding.field ? { field: finding.field } : {},
327
+ ...finding.pattern ? { pattern: finding.pattern } : {}
328
+ }
329
+ };
330
+ }
331
+ function evaluateSchemaValid(resolution) {
332
+ const findings = resolution.schemaFindings;
333
+ if (findings.length === 0) {
334
+ return passResult("schema-valid");
335
+ }
336
+ const subs = findings.map((finding) => {
337
+ const ruleFinding = toRuleFinding(finding);
338
+ return { status: "fail", reason: ruleFinding.reason, finding: ruleFinding };
339
+ });
340
+ return aggregate("schema-valid", subs);
341
+ }
342
+
343
+ // src/rules/id-unique.ts
344
+ function key(log, id) {
345
+ return JSON.stringify([log ?? "", id]);
346
+ }
347
+ function corpusKey(record) {
348
+ return key(record.log, record.frontmatter.id);
349
+ }
350
+ function federatedKeys(snapshots) {
351
+ const keys = [];
352
+ for (const snapshot of snapshots ?? []) {
353
+ for (const id of snapshot.adrIds)
354
+ keys.push(key(snapshot.log, id));
355
+ }
356
+ return keys;
357
+ }
358
+ function evaluateIdUnique(ctx) {
359
+ const candidate = key(ctx.proposed.log, ctx.proposed.frontmatter.id);
360
+ let occurrences = 0;
361
+ for (const record of ctx.corpusRecords) {
362
+ if (corpusKey(record) === candidate)
363
+ occurrences += 1;
364
+ }
365
+ for (const federated of federatedKeys(ctx.input.federatedLogs)) {
366
+ if (federated === candidate)
367
+ occurrences += 1;
368
+ }
369
+ if (occurrences <= 1) {
370
+ return passResult("id-unique");
371
+ }
372
+ const finding = {
373
+ reason: "id-unique.collision",
374
+ message: `ADR id "${ctx.proposed.frontmatter.id}" is not unique within log "${ctx.proposed.log ?? ""}"`,
375
+ adr: ctx.proposed.frontmatter.id,
376
+ candidateAdr: ctx.proposed.frontmatter.id,
377
+ recordPath: ctx.proposed.path,
378
+ field: "id",
379
+ lowerLevel: { rule: "unique-id", path: ctx.proposed.path, id: ctx.proposed.frontmatter.id, field: "id" }
380
+ };
381
+ const subs = [{ status: "fail", reason: "id-unique.collision", finding }];
382
+ return aggregate("id-unique", subs);
383
+ }
384
+
385
+ // src/rules/supersession-consistent.ts
386
+ import { buildAdrGraph } from "@adrkit/core";
387
+ function hasSupersedesCycle(records) {
388
+ const graph = buildAdrGraph(records);
389
+ const adjacency = new Map;
390
+ for (const edge of graph.edges) {
391
+ if (edge.kind !== "supersedes")
392
+ continue;
393
+ const list = adjacency.get(edge.from) ?? [];
394
+ list.push(edge.to);
395
+ adjacency.set(edge.from, list);
396
+ }
397
+ const WHITE = 0;
398
+ const GRAY = 1;
399
+ const BLACK = 2;
400
+ const color = new Map;
401
+ const nodes = graph.nodes.map((node) => node.id);
402
+ function visit(node) {
403
+ color.set(node, GRAY);
404
+ for (const next of adjacency.get(node) ?? []) {
405
+ const state = color.get(next) ?? WHITE;
406
+ if (state === GRAY)
407
+ return true;
408
+ if (state === WHITE && visit(next))
409
+ return true;
410
+ }
411
+ color.set(node, BLACK);
412
+ return false;
413
+ }
414
+ for (const node of nodes) {
415
+ if ((color.get(node) ?? WHITE) === WHITE && visit(node))
416
+ return true;
417
+ }
418
+ return false;
419
+ }
420
+ function evaluateSupersessionConsistent(ctx) {
421
+ const records = ctx.corpusRecords;
422
+ const ids = new Set(records.map((record) => record.frontmatter.id));
423
+ const supersededByById = new Map;
424
+ for (const record of records) {
425
+ if (record.frontmatter.supersededBy) {
426
+ supersededByById.set(record.frontmatter.id, record.frontmatter.supersededBy);
427
+ }
428
+ }
429
+ const subs = [];
430
+ const seenPairs = new Set;
431
+ const danglingRefs = new Set;
432
+ for (const record of records) {
433
+ const target = record.frontmatter.supersededBy;
434
+ if (target && !ids.has(target)) {
435
+ danglingRefs.add(`${record.frontmatter.id}->${target}`);
436
+ const finding = {
437
+ reason: "supersession-consistent.dangling-superseded-by",
438
+ message: `supersededBy "${target}" does not resolve to a record in the corpus`,
439
+ adr: record.frontmatter.id,
440
+ candidateAdr: record.frontmatter.id,
441
+ relatedAdr: target,
442
+ recordPath: record.path,
443
+ field: "supersededBy",
444
+ lowerLevel: { rule: "dangling-supersededBy", path: record.path, id: record.frontmatter.id, field: "supersededBy" }
445
+ };
446
+ subs.push({ status: "fail", reason: "supersession-consistent.dangling-superseded-by", finding });
447
+ }
448
+ }
449
+ function nonReciprocal(fromId, toId, path, field) {
450
+ const key2 = [fromId, toId].sort().join("|");
451
+ if (seenPairs.has(key2))
452
+ return;
453
+ seenPairs.add(key2);
454
+ const finding = {
455
+ reason: "supersession-consistent.non-reciprocal",
456
+ message: `supersedes/supersededBy between "${fromId}" and "${toId}" is not reciprocal`,
457
+ adr: fromId,
458
+ candidateAdr: fromId,
459
+ relatedAdr: toId,
460
+ recordPath: path,
461
+ field,
462
+ lowerLevel: { rule: "non-reciprocal", path, id: fromId, field }
463
+ };
464
+ subs.push({ status: "fail", reason: "supersession-consistent.non-reciprocal", finding });
465
+ }
466
+ for (const record of records) {
467
+ for (const target of record.frontmatter.supersedes) {
468
+ if (!ids.has(target))
469
+ continue;
470
+ if (supersededByById.get(target) !== record.frontmatter.id) {
471
+ nonReciprocal(record.frontmatter.id, target, record.path, "supersedes");
472
+ }
473
+ }
474
+ }
475
+ const supersedesPairs = new Set;
476
+ for (const record of records) {
477
+ for (const target of record.frontmatter.supersedes) {
478
+ supersedesPairs.add(`${record.frontmatter.id}=>${target}`);
479
+ }
480
+ }
481
+ for (const record of records) {
482
+ const target = record.frontmatter.supersededBy;
483
+ if (!target || !ids.has(target))
484
+ continue;
485
+ if (danglingRefs.has(`${record.frontmatter.id}->${target}`))
486
+ continue;
487
+ if (!supersedesPairs.has(`${target}=>${record.frontmatter.id}`)) {
488
+ nonReciprocal(record.frontmatter.id, target, record.path, "supersededBy");
489
+ }
490
+ }
491
+ if (hasSupersedesCycle(records)) {
492
+ subs.push({
493
+ status: "fail",
494
+ reason: "supersession-consistent.cycle",
495
+ finding: {
496
+ reason: "supersession-consistent.cycle",
497
+ message: "The supersedes relation contains a cycle",
498
+ lowerLevel: { rule: "supersedes-cycle" }
499
+ }
500
+ });
501
+ }
502
+ if (subs.length === 0)
503
+ return passResult("supersession-consistent");
504
+ return aggregate("supersession-consistent", subs);
505
+ }
506
+
507
+ // src/rules/no-orphan-refs.ts
508
+ function parseRef(ref) {
509
+ const idx = ref.indexOf(":");
510
+ if (idx <= 0)
511
+ return { id: ref };
512
+ return { log: ref.slice(0, idx), id: ref.slice(idx + 1) };
513
+ }
514
+ function evaluateNoOrphanRefs(ctx) {
515
+ const resolutionLog = ctx.input.resolutionLog;
516
+ const localIds = new Set;
517
+ const knownLogs = new Map;
518
+ for (const record of ctx.corpusRecords) {
519
+ if (record.log === undefined || record.log === resolutionLog) {
520
+ localIds.add(record.frontmatter.id);
521
+ }
522
+ if (record.log !== undefined) {
523
+ const set = knownLogs.get(record.log) ?? new Set;
524
+ set.add(record.frontmatter.id);
525
+ knownLogs.set(record.log, set);
526
+ }
527
+ }
528
+ for (const snapshot of ctx.input.federatedLogs ?? []) {
529
+ const set = knownLogs.get(snapshot.log) ?? new Set;
530
+ for (const id of snapshot.adrIds)
531
+ set.add(id);
532
+ knownLogs.set(snapshot.log, set);
533
+ }
534
+ const subs = [];
535
+ function classify(ref) {
536
+ const parsed = parseRef(ref);
537
+ if (parsed.log === undefined || parsed.log === resolutionLog) {
538
+ return localIds.has(parsed.id) ? "resolved" : "dangling";
539
+ }
540
+ const known = knownLogs.get(parsed.log);
541
+ if (!known)
542
+ return "federated-absent";
543
+ return known.has(parsed.id) ? "resolved" : "dangling";
544
+ }
545
+ function check(record, field, refs) {
546
+ const danglingReason = field === "supersedes" ? "no-orphan-refs.dangling-supersedes" : "no-orphan-refs.dangling-relates-to";
547
+ const lowerRule = field === "supersedes" ? "dangling-supersedes" : "dangling-relatesTo";
548
+ for (const ref of refs) {
549
+ const outcome = classify(ref);
550
+ if (outcome === "resolved")
551
+ continue;
552
+ if (outcome === "federated-absent") {
553
+ const finding2 = {
554
+ reason: "no-orphan-refs.federated-log-absent",
555
+ message: `Federated ref "${ref}" has no external-log snapshot; reference is inert`,
556
+ candidateAdr: record.frontmatter.id,
557
+ relatedAdr: ref,
558
+ recordPath: record.path,
559
+ field
560
+ };
561
+ subs.push({ status: "inert", reason: "no-orphan-refs.federated-log-absent", finding: finding2 });
562
+ continue;
563
+ }
564
+ const finding = {
565
+ reason: danglingReason,
566
+ message: `Reference "${ref}" in ${field} does not resolve to a record in the corpus`,
567
+ adr: record.frontmatter.id,
568
+ candidateAdr: record.frontmatter.id,
569
+ relatedAdr: ref,
570
+ recordPath: record.path,
571
+ field,
572
+ lowerLevel: { rule: lowerRule, path: record.path, id: record.frontmatter.id, field }
573
+ };
574
+ subs.push({ status: "fail", reason: danglingReason, finding });
575
+ }
576
+ }
577
+ for (const record of ctx.corpusRecords) {
578
+ check(record, "supersedes", record.frontmatter.supersedes);
579
+ check(record, "relatesTo", record.frontmatter.relatesTo);
580
+ }
581
+ if (subs.length === 0)
582
+ return passResult("no-orphan-refs");
583
+ return aggregate("no-orphan-refs", subs);
584
+ }
585
+
586
+ // src/targets/canonical.ts
587
+ function normalizePathId(path) {
588
+ return path.replace(/\\/g, "/").replace(/^\.\//, "");
589
+ }
590
+ function makeTargetId(kind, id) {
591
+ return { kind, id: kind === "path" ? normalizePathId(id) : id };
592
+ }
593
+ function anyMatcherInert(resolution) {
594
+ return resolution.matchers.some((matcher) => matcher.status !== "resolved");
595
+ }
596
+ function sortedUnique(ids) {
597
+ const byKey = new Map;
598
+ for (const id of ids)
599
+ byKey.set(canonicalTargetKey(id), id);
600
+ return [...byKey.values()].sort((a, b) => byCodeUnit(canonicalTargetKey(a), canonicalTargetKey(b)));
601
+ }
602
+ function resolveRecordTargets(record, registry, inventory, resolutionLog) {
603
+ const context = { ...resolutionLog !== undefined ? { log: resolutionLog } : {}, inventory };
604
+ const positive = new Map;
605
+ const negated = new Set;
606
+ const matcherResolutions = [];
607
+ const matchers = record.frontmatter.affects;
608
+ let hasPositiveMatcher = false;
609
+ for (const matcher of matchers) {
610
+ const type = matcher.type;
611
+ if (!matcher.negate)
612
+ hasPositiveMatcher = true;
613
+ if (matcher.repo !== undefined && matcher.repo !== resolutionLog) {
614
+ matcherResolutions.push({ type, pattern: matcher.pattern, negate: matcher.negate, status: "resolved", ids: [] });
615
+ continue;
616
+ }
617
+ const port = registry.get(type);
618
+ if (!port) {
619
+ matcherResolutions.push({ type, pattern: matcher.pattern, negate: matcher.negate, status: "inert-resolver", ids: [] });
620
+ continue;
621
+ }
622
+ const resolution = port.resolve(matcher, context);
623
+ if (resolution.status === "inert") {
624
+ matcherResolutions.push({ type, pattern: matcher.pattern, negate: matcher.negate, status: "inert-backing", ids: [] });
625
+ continue;
626
+ }
627
+ matcherResolutions.push({ type, pattern: matcher.pattern, negate: matcher.negate, status: "resolved", ids: resolution.ids });
628
+ for (const id of resolution.ids) {
629
+ const key2 = canonicalTargetKey(id);
630
+ if (matcher.negate)
631
+ negated.add(key2);
632
+ else
633
+ positive.set(key2, id);
634
+ }
635
+ }
636
+ const finalIds = [...positive.entries()].filter(([key2]) => !negated.has(key2)).map(([, id]) => id);
637
+ const targets = sortedUnique(finalIds);
638
+ return {
639
+ targets,
640
+ targetKeys: new Set(targets.map(canonicalTargetKey)),
641
+ matchers: matcherResolutions,
642
+ hasMatchers: matchers.length > 0,
643
+ hasPositiveMatcher
644
+ };
645
+ }
646
+
647
+ // src/rules/affects-resolvable.ts
648
+ function matcherFinding(ctx, matcher, reason, message) {
649
+ return {
650
+ reason,
651
+ candidateAdr: ctx.proposed.frontmatter.id,
652
+ matcherKey: `${matcher.type}:${matcher.pattern}`,
653
+ recordPath: ctx.proposed.path,
654
+ field: `affects.${matcher.type}`,
655
+ message
656
+ };
657
+ }
658
+ function evaluateAffectsResolvable(ctx) {
659
+ const resolution = resolveRecordTargets(ctx.proposed, ctx.input.targetRegistry, ctx.input.targets, ctx.input.resolutionLog);
660
+ if (!resolution.hasMatchers)
661
+ return passResult("affects-resolvable");
662
+ const subs = [];
663
+ for (const matcher of resolution.matchers) {
664
+ if (matcher.status === "inert-resolver") {
665
+ subs.push({
666
+ status: "inert",
667
+ reason: "affects-resolvable.resolver-absent",
668
+ finding: matcherFinding(ctx, matcher, "affects-resolvable.resolver-absent", `No resolver registered for "${matcher.type}" matcher "${matcher.pattern}"`)
669
+ });
670
+ continue;
671
+ }
672
+ if (matcher.status === "inert-backing") {
673
+ subs.push({
674
+ status: "inert",
675
+ reason: "affects-resolvable.backing-absent",
676
+ finding: matcherFinding(ctx, matcher, "affects-resolvable.backing-absent", `No inventory backing for "${matcher.type}" matcher "${matcher.pattern}"`)
677
+ });
678
+ continue;
679
+ }
680
+ if (matcher.negate)
681
+ continue;
682
+ if (matcher.ids.length >= 1) {
683
+ subs.push({ status: "pass", reason: "affects-resolvable.ok" });
684
+ } else {
685
+ subs.push({
686
+ status: "fail",
687
+ reason: "affects-resolvable.zero-targets",
688
+ finding: matcherFinding(ctx, matcher, "affects-resolvable.zero-targets", `"${matcher.type}" matcher "${matcher.pattern}" resolves to zero targets against the supplied inventory`)
689
+ });
690
+ }
691
+ }
692
+ if (!resolution.hasPositiveMatcher) {
693
+ subs.push({
694
+ status: "fail",
695
+ reason: "affects-resolvable.zero-targets",
696
+ finding: {
697
+ reason: "affects-resolvable.zero-targets",
698
+ candidateAdr: ctx.proposed.frontmatter.id,
699
+ recordPath: ctx.proposed.path,
700
+ field: "affects",
701
+ message: "negation-only affects resolves to zero targets"
702
+ }
703
+ });
704
+ }
705
+ if (subs.length === 0)
706
+ return passResult("affects-resolvable");
707
+ return aggregate("affects-resolvable", subs, { resolvedTargets: resolution.targets });
708
+ }
709
+
710
+ // src/rules/affects-overlap.ts
711
+ function evaluateAffectsOverlap(ctx) {
712
+ const accepted = ctx.acceptedRecords;
713
+ if (accepted.length === 0) {
714
+ return passResult("affects-overlap", "affects-overlap.no-accepted-corpus");
715
+ }
716
+ const proposal = resolveRecordTargets(ctx.proposed, ctx.input.targetRegistry, ctx.input.targets, ctx.input.resolutionLog);
717
+ let anyInert = anyMatcherInert(proposal);
718
+ const subs = [];
719
+ const overlapping = [];
720
+ for (const acc of accepted) {
721
+ const accResolution = resolveRecordTargets(acc, ctx.input.targetRegistry, ctx.input.targets, ctx.input.resolutionLog);
722
+ if (anyMatcherInert(accResolution))
723
+ anyInert = true;
724
+ const intersects = [...proposal.targetKeys].some((key2) => accResolution.targetKeys.has(key2));
725
+ if (intersects) {
726
+ overlapping.push(acc.frontmatter.id);
727
+ subs.push({
728
+ status: "fail",
729
+ reason: "affects-overlap.accepted-intersection",
730
+ finding: {
731
+ reason: "affects-overlap.accepted-intersection",
732
+ candidateAdr: ctx.proposed.frontmatter.id,
733
+ relatedAdr: acc.frontmatter.id,
734
+ message: `affects overlaps accepted ADR "${acc.frontmatter.id}"`
735
+ }
736
+ });
737
+ }
738
+ }
739
+ if (subs.length > 0) {
740
+ return aggregate("affects-overlap", subs, { overlappingWith: [...overlapping].sort(byCodeUnit) });
741
+ }
742
+ if (anyInert) {
743
+ return inertResult("affects-overlap", "affects-overlap.backing-absent");
744
+ }
745
+ return passResult("affects-overlap", "affects-overlap.none");
746
+ }
747
+
748
+ // src/assertions/evaluate.ts
749
+ function runPort(port, key2, record, hasInline, inline, source, input) {
750
+ const base = { assertionKey: key2, candidateAdr: record.frontmatter.id, recordPath: record.path };
751
+ if (!port) {
752
+ return {
753
+ compile: { status: "inert", reason: "assertions-compile.engine-absent", finding: { reason: "assertions-compile.engine-absent", ...base } },
754
+ pass: { status: "inert", reason: "assertions-pass.engine-absent", finding: { reason: "assertions-pass.engine-absent", ...base } }
755
+ };
756
+ }
757
+ if (port.profile === "source") {
758
+ const effectiveSource = hasInline ? inline : source?.fileContent;
759
+ if (effectiveSource === undefined) {
760
+ return {
761
+ compile: { status: "inert", reason: "assertions-compile.source-absent", finding: { reason: "assertions-compile.source-absent", ...base } },
762
+ pass: { status: "inert", reason: "assertions-pass.input-absent", finding: { reason: "assertions-pass.input-absent", ...base } }
763
+ };
764
+ }
765
+ const outcome2 = port.compile(effectiveSource, source?.sourceRef);
766
+ if (!outcome2.ok) {
767
+ return { compile: { status: "fail", reason: outcome2.reason, finding: { reason: outcome2.reason, ...base } } };
768
+ }
769
+ const compileSub2 = { status: "pass", reason: "assertions-compile.ok" };
770
+ if (input === undefined) {
771
+ return { compile: compileSub2, pass: { status: "inert", reason: "assertions-pass.input-absent", finding: { reason: "assertions-pass.input-absent", ...base } } };
772
+ }
773
+ const evaluated2 = port.evaluate(outcome2.compiled, input);
774
+ if (!evaluated2.ok) {
775
+ return { compile: compileSub2, pass: { status: "fail", reason: evaluated2.reason, finding: { reason: evaluated2.reason, ...base } } };
776
+ }
777
+ return {
778
+ compile: compileSub2,
779
+ pass: evaluated2.pass ? { status: "pass", reason: "assertions-pass.ok" } : { status: "fail", reason: "assertions-pass.evaluates-false", finding: { reason: "assertions-pass.evaluates-false", ...base } }
780
+ };
781
+ }
782
+ const artifact = source?.compiledArtifact;
783
+ if (artifact === undefined) {
784
+ return {
785
+ compile: { status: "inert", reason: "assertions-compile.source-absent", finding: { reason: "assertions-compile.source-absent", ...base } },
786
+ pass: { status: "inert", reason: "assertions-pass.input-absent", finding: { reason: "assertions-pass.input-absent", ...base } }
787
+ };
788
+ }
789
+ const outcome = port.validateArtifact(artifact);
790
+ if (!outcome.ok) {
791
+ return { compile: { status: "fail", reason: outcome.reason, finding: { reason: outcome.reason, ...base } } };
792
+ }
793
+ const compileSub = { status: "pass", reason: "assertions-compile.ok" };
794
+ if (input === undefined) {
795
+ return { compile: compileSub, pass: { status: "inert", reason: "assertions-pass.input-absent", finding: { reason: "assertions-pass.input-absent", ...base } } };
796
+ }
797
+ const evaluated = port.evaluate(outcome.compiled, input);
798
+ if (!evaluated.ok) {
799
+ return { compile: compileSub, pass: { status: "fail", reason: evaluated.reason, finding: { reason: evaluated.reason, ...base } } };
800
+ }
801
+ return {
802
+ compile: compileSub,
803
+ pass: evaluated.pass ? { status: "pass", reason: "assertions-pass.ok" } : { status: "fail", reason: "assertions-pass.evaluates-false", finding: { reason: "assertions-pass.evaluates-false", ...base } }
804
+ };
805
+ }
806
+ function dispatch(ctx, assertion, key2, input) {
807
+ const record = ctx.proposed;
808
+ const source = ctx.input.assertionInputs.sources[key2];
809
+ const hasInline = assertion.expression !== undefined;
810
+ const registry = ctx.input.assertionEngines;
811
+ switch (assertion.engine) {
812
+ case "rego":
813
+ return runPort(registry.rego, key2, record, hasInline, assertion.expression, source, input);
814
+ case "jsonpath":
815
+ return runPort(registry.jsonpath, key2, record, hasInline, assertion.expression, source, input);
816
+ case "grep":
817
+ return runPort(registry.grep, key2, record, hasInline, assertion.expression, source, input);
818
+ case "custom":
819
+ return runPort(registry.custom, key2, record, hasInline, assertion.expression, source, input);
820
+ }
821
+ }
822
+ function computeAssertionOutcomes(ctx) {
823
+ const assertions = ctx.proposed.frontmatter.assertions;
824
+ const compileSubs = [];
825
+ const passSubs = [];
826
+ for (const assertion of assertions) {
827
+ const key2 = makeAssertionKey(ctx.proposed.log, ctx.proposed.path, assertion.id);
828
+ const base = { assertionKey: key2, candidateAdr: ctx.proposed.frontmatter.id, recordPath: ctx.proposed.path };
829
+ const hasInline = assertion.expression !== undefined;
830
+ const hasFile = assertion.expressionFile !== undefined;
831
+ if (!hasInline && !hasFile) {
832
+ compileSubs.push({
833
+ status: "fail",
834
+ reason: "assertions-compile.no-source",
835
+ finding: { reason: "assertions-compile.no-source", field: "assertions", message: `Assertion "${assertion.id}" declares no source`, ...base }
836
+ });
837
+ continue;
838
+ }
839
+ if (hasInline && hasFile) {
840
+ compileSubs.push({
841
+ status: "fail",
842
+ reason: "assertions-compile.ambiguous-source",
843
+ finding: { reason: "assertions-compile.ambiguous-source", field: "assertions", message: `Assertion "${assertion.id}" declares both expression and expressionFile`, ...base }
844
+ });
845
+ continue;
846
+ }
847
+ const input = ctx.input.assertionInputs.inputs[key2]?.document;
848
+ const result = dispatch(ctx, assertion, key2, input);
849
+ compileSubs.push(result.compile);
850
+ if (result.pass)
851
+ passSubs.push(result.pass);
852
+ }
853
+ return { compileSubs, passSubs, hasAssertions: assertions.length > 0 };
854
+ }
855
+ function compileWith(port, hasInline, inline, source) {
856
+ if (!port)
857
+ return { ok: false, reason: "engine-absent" };
858
+ if (port.profile === "source") {
859
+ const effectiveSource = hasInline ? inline : source?.fileContent;
860
+ if (effectiveSource === undefined)
861
+ return { ok: false, reason: "source-absent" };
862
+ const outcome2 = port.compile(effectiveSource, source?.sourceRef);
863
+ if (!outcome2.ok)
864
+ return { ok: false, reason: "compile-error" };
865
+ return { ok: true, evaluate: (input) => port.evaluate(outcome2.compiled, input) };
866
+ }
867
+ const artifact = source?.compiledArtifact;
868
+ if (artifact === undefined)
869
+ return { ok: false, reason: "source-absent" };
870
+ const outcome = port.validateArtifact(artifact);
871
+ if (!outcome.ok)
872
+ return { ok: false, reason: "compile-error" };
873
+ return { ok: true, evaluate: (input) => port.evaluate(outcome.compiled, input) };
874
+ }
875
+ function compileAssertionForScope(ctx, orgRecord, assertion) {
876
+ const key2 = makeAssertionKey(orgRecord.log, orgRecord.path, assertion.id);
877
+ const source = ctx.input.assertionInputs.sources[key2];
878
+ const hasInline = assertion.expression !== undefined;
879
+ const hasFile = assertion.expressionFile !== undefined;
880
+ if (hasInline === hasFile)
881
+ return { ok: false, reason: "compile-error" };
882
+ const registry = ctx.input.assertionEngines;
883
+ switch (assertion.engine) {
884
+ case "rego":
885
+ return compileWith(registry.rego, hasInline, assertion.expression, source);
886
+ case "jsonpath":
887
+ return compileWith(registry.jsonpath, hasInline, assertion.expression, source);
888
+ case "grep":
889
+ return compileWith(registry.grep, hasInline, assertion.expression, source);
890
+ case "custom":
891
+ return compileWith(registry.custom, hasInline, assertion.expression, source);
892
+ }
893
+ }
894
+
895
+ // src/rules/scope-hierarchy.ts
896
+ function domainApplies(orgRecord, proposal) {
897
+ const orgDomain = orgRecord.frontmatter.domain;
898
+ if (orgDomain === undefined)
899
+ return true;
900
+ return orgDomain === proposal.frontmatter.domain;
901
+ }
902
+ function evaluateScopeHierarchy(ctx) {
903
+ const proposal = ctx.proposed;
904
+ if (proposal.frontmatter.scope !== "component") {
905
+ return passResult("scope-hierarchy", "scope-hierarchy.not-applicable-scope");
906
+ }
907
+ const proposalTargets = resolveRecordTargets(proposal, ctx.input.targetRegistry, ctx.input.targets, ctx.input.resolutionLog);
908
+ const subs = [];
909
+ if (anyMatcherInert(proposalTargets)) {
910
+ subs.push({ status: "inert", reason: "scope-hierarchy.evidence-absent" });
911
+ }
912
+ const applicableOrgAdrs = [];
913
+ for (const record of ctx.acceptedRecords) {
914
+ if (record.frontmatter.scope !== "org")
915
+ continue;
916
+ if (!domainApplies(record, proposal))
917
+ continue;
918
+ const orgTargets = resolveRecordTargets(record, ctx.input.targetRegistry, ctx.input.targets, ctx.input.resolutionLog);
919
+ if (anyMatcherInert(orgTargets)) {
920
+ subs.push({ status: "inert", reason: "scope-hierarchy.evidence-absent" });
921
+ }
922
+ if ([...proposalTargets.targetKeys].some((key2) => orgTargets.targetKeys.has(key2))) {
923
+ applicableOrgAdrs.push(record);
924
+ }
925
+ }
926
+ if (applicableOrgAdrs.length === 0) {
927
+ return subs.length > 0 ? aggregate("scope-hierarchy", subs) : passResult("scope-hierarchy", "scope-hierarchy.ok");
928
+ }
929
+ const transitions = [];
930
+ for (const orgRecord of applicableOrgAdrs) {
931
+ for (const assertion of orgRecord.frontmatter.assertions) {
932
+ const key2 = makeAssertionKey(orgRecord.log, orgRecord.path, assertion.id);
933
+ const compiled = compileAssertionForScope(ctx, orgRecord, assertion);
934
+ if (!compiled.ok) {
935
+ const reason = compiled.reason === "engine-absent" ? "scope-hierarchy.engine-absent" : "scope-hierarchy.source-absent";
936
+ subs.push({ status: "inert", reason });
937
+ continue;
938
+ }
939
+ const baseInput = ctx.input.scopeEvidence?.baseInputs?.[key2]?.document;
940
+ if (baseInput === undefined) {
941
+ subs.push({ status: "inert", reason: "scope-hierarchy.base-input-absent" });
942
+ continue;
943
+ }
944
+ const proposedInput = ctx.input.assertionInputs.inputs[key2]?.document;
945
+ if (proposedInput === undefined) {
946
+ subs.push({ status: "inert", reason: "scope-hierarchy.proposed-input-absent" });
947
+ continue;
948
+ }
949
+ const baseEval = compiled.evaluate(baseInput);
950
+ const proposedEval = compiled.evaluate(proposedInput);
951
+ if (!baseEval.ok || !proposedEval.ok) {
952
+ subs.push({ status: "inert", reason: "scope-hierarchy.evidence-absent" });
953
+ continue;
954
+ }
955
+ if (baseEval.pass && !proposedEval.pass) {
956
+ transitions.push(`${orgRecord.frontmatter.id}:${assertion.id}`);
957
+ subs.push({
958
+ status: "fail",
959
+ reason: "scope-hierarchy.contradicts-org-assertion",
960
+ finding: {
961
+ reason: "scope-hierarchy.contradicts-org-assertion",
962
+ candidateAdr: proposal.frontmatter.id,
963
+ relatedAdr: orgRecord.frontmatter.id,
964
+ assertionKey: key2,
965
+ message: `Proposal turns accepted org assertion "${orgRecord.frontmatter.id}:${assertion.id}" from green to red`
966
+ }
967
+ });
968
+ }
969
+ }
970
+ }
971
+ if (subs.length === 0)
972
+ return passResult("scope-hierarchy", "scope-hierarchy.ok");
973
+ return aggregate("scope-hierarchy", subs, transitions.length > 0 ? { assertionTransitions: [...transitions].sort(byCodeUnit) } : undefined);
974
+ }
975
+
976
+ // src/rules/assertions-compile.ts
977
+ function evaluateAssertionsCompile(outcomes) {
978
+ if (!outcomes.hasAssertions)
979
+ return passResult("assertions-compile", "assertions-compile.none");
980
+ return aggregate("assertions-compile", outcomes.compileSubs);
981
+ }
982
+
983
+ // src/rules/assertions-pass.ts
984
+ function evaluateAssertionsPass(outcomes) {
985
+ if (!outcomes.hasAssertions || outcomes.passSubs.length === 0) {
986
+ return passResult("assertions-pass", "assertions-pass.none");
987
+ }
988
+ return aggregate("assertions-pass", outcomes.passSubs);
989
+ }
990
+
991
+ // src/identity/directory.ts
992
+ function buildIdentityIndex(directory) {
993
+ const principalsById = new Map;
994
+ for (const principal of directory.principals) {
995
+ principalsById.set(principal.id, principal);
996
+ }
997
+ const teamMembersById = new Map;
998
+ for (const team of directory.teams) {
999
+ teamMembersById.set(team.id, team.members);
1000
+ }
1001
+ function isActiveHuman(ref) {
1002
+ const principal = principalsById.get(ref);
1003
+ return principal !== undefined && principal.kind === "human" && principal.active;
1004
+ }
1005
+ function isTeam(ref) {
1006
+ if (teamMembersById.has(ref))
1007
+ return true;
1008
+ const principal = principalsById.get(ref);
1009
+ return principal !== undefined && principal.kind === "team";
1010
+ }
1011
+ function activeHumansForTeam(ref) {
1012
+ const members = teamMembersById.get(ref) ?? [];
1013
+ const seen = new Set;
1014
+ const humans = [];
1015
+ for (const member of members) {
1016
+ if (seen.has(member))
1017
+ continue;
1018
+ if (isActiveHuman(member)) {
1019
+ seen.add(member);
1020
+ humans.push(member);
1021
+ }
1022
+ }
1023
+ return humans;
1024
+ }
1025
+ function resolveToActiveHuman(ref) {
1026
+ if (isTeam(ref)) {
1027
+ const humans = activeHumansForTeam(ref);
1028
+ if (humans.length === 1)
1029
+ return { status: "resolved", human: humans[0] };
1030
+ return humans.length === 0 ? { status: "zero" } : { status: "ambiguous" };
1031
+ }
1032
+ if (isActiveHuman(ref))
1033
+ return { status: "resolved", human: ref };
1034
+ return { status: "zero" };
1035
+ }
1036
+ return { resolveToActiveHuman, isActiveHuman, isTeam };
1037
+ }
1038
+
1039
+ // src/rules/decider-resolvable.ts
1040
+ function evaluateDeciderResolvable(ctx) {
1041
+ const directory = ctx.input.identity;
1042
+ if (!directory) {
1043
+ return inertResult("decider-resolvable", "decider-resolvable.directory-absent");
1044
+ }
1045
+ const deciders = ctx.proposed.frontmatter.deciders;
1046
+ if (deciders.length === 0) {
1047
+ return aggregate("decider-resolvable", [
1048
+ {
1049
+ status: "fail",
1050
+ reason: "decider-resolvable.none-declared",
1051
+ finding: {
1052
+ reason: "decider-resolvable.none-declared",
1053
+ candidateAdr: ctx.proposed.frontmatter.id,
1054
+ field: "deciders",
1055
+ message: "No deciders are declared on the proposal"
1056
+ }
1057
+ }
1058
+ ]);
1059
+ }
1060
+ const index = buildIdentityIndex(directory);
1061
+ const subs = deciders.map((decider) => {
1062
+ const resolution = index.resolveToActiveHuman(decider);
1063
+ if (resolution.status === "resolved") {
1064
+ return { status: "pass", reason: "decider-resolvable.ok" };
1065
+ }
1066
+ const reason = resolution.status === "ambiguous" ? "decider-resolvable.ambiguous-match" : "decider-resolvable.zero-match";
1067
+ return {
1068
+ status: "fail",
1069
+ reason,
1070
+ finding: {
1071
+ reason,
1072
+ candidateAdr: ctx.proposed.frontmatter.id,
1073
+ field: "deciders",
1074
+ message: `Decider "${decider}" ${resolution.status === "ambiguous" ? "resolves ambiguously" : "does not resolve to one active principal"}`
1075
+ }
1076
+ };
1077
+ });
1078
+ return aggregate("decider-resolvable", subs);
1079
+ }
1080
+
1081
+ // src/rules/expiry-sane.ts
1082
+ function evaluateExpirySane(ctx) {
1083
+ const reviewBy = ctx.proposed.frontmatter.reviewBy;
1084
+ if (reviewBy === undefined)
1085
+ return passResult("expiry-sane");
1086
+ if (reviewBy > ctx.evaluationDate)
1087
+ return passResult("expiry-sane");
1088
+ return aggregate("expiry-sane", [
1089
+ {
1090
+ status: "fail",
1091
+ reason: "expiry-sane.past-or-equal",
1092
+ finding: {
1093
+ reason: "expiry-sane.past-or-equal",
1094
+ candidateAdr: ctx.proposed.frontmatter.id,
1095
+ field: "reviewBy",
1096
+ message: `reviewBy "${reviewBy}" is on or before the evaluation date "${ctx.evaluationDate}"`
1097
+ }
1098
+ }
1099
+ ]);
1100
+ }
1101
+
1102
+ // src/report/assemble.ts
1103
+ function computeOutcome(results) {
1104
+ const returned = results.some((result) => result.status === "fail" && result.severity === "error");
1105
+ return returned ? "returned" : "ok";
1106
+ }
1107
+ function assembleReport(proposalPath, resultsByRule, routing) {
1108
+ const results = RULE_IDS.map((rule) => {
1109
+ const result = resultsByRule.get(rule);
1110
+ if (!result) {
1111
+ throw new Error(`internal: missing rule result for "${rule}"`);
1112
+ }
1113
+ return result;
1114
+ });
1115
+ return {
1116
+ rubricVersion: RUBRIC_VERSION,
1117
+ proposalPath,
1118
+ results,
1119
+ routing,
1120
+ outcome: computeOutcome(results)
1121
+ };
1122
+ }
1123
+
1124
+ // src/routing/target.ts
1125
+ import { matchPathPattern } from "@adrkit/core";
1126
+ var VIA_CODE = {
1127
+ deciders: "route.target.deciders",
1128
+ codeowners: "route.target.codeowners",
1129
+ catalog: "route.target.catalog-owner"
1130
+ };
1131
+ function orderedCandidates(deciders, directory, resolvedPaths, resolvedEntities) {
1132
+ const candidates = [];
1133
+ for (const decider of deciders)
1134
+ candidates.push({ ref: decider, via: "deciders" });
1135
+ const codeowners = directory.codeowners ?? [];
1136
+ const uniquePaths = [...new Set(resolvedPaths)].sort(byCodeUnit);
1137
+ for (const path of uniquePaths) {
1138
+ let lastMatch;
1139
+ for (const rule of codeowners) {
1140
+ if (matchPathPattern(rule.pattern, [path]).matched)
1141
+ lastMatch = rule.owners;
1142
+ }
1143
+ if (lastMatch) {
1144
+ for (const owner of lastMatch)
1145
+ candidates.push({ ref: owner, via: "codeowners" });
1146
+ }
1147
+ }
1148
+ const catalogOwners = directory.catalogOwners;
1149
+ const uniqueEntities = [...new Map(resolvedEntities.map((id) => [canonicalTargetKey(id), id])).values()].sort((a, b) => byCodeUnit(canonicalTargetKey(a), canonicalTargetKey(b)));
1150
+ for (const entity of uniqueEntities) {
1151
+ const owners = catalogOwners !== undefined && Object.hasOwn(catalogOwners, entity.id) ? catalogOwners[entity.id] ?? [] : [];
1152
+ for (const owner of owners)
1153
+ candidates.push({ ref: owner, via: "catalog" });
1154
+ }
1155
+ return candidates;
1156
+ }
1157
+ function resolveRouteTarget(index, deciders, directory, resolvedPaths, resolvedEntities) {
1158
+ const candidates = orderedCandidates(deciders, directory, resolvedPaths, resolvedEntities);
1159
+ const seen = new Set;
1160
+ for (const candidate of candidates) {
1161
+ if (seen.has(candidate.ref))
1162
+ continue;
1163
+ seen.add(candidate.ref);
1164
+ const resolution = index.resolveToActiveHuman(candidate.ref);
1165
+ if (resolution.status === "resolved") {
1166
+ return { kind: "resolved", human: resolution.human, via: candidate.via, code: VIA_CODE[candidate.via] };
1167
+ }
1168
+ if (index.isTeam(candidate.ref)) {
1169
+ return { kind: "unresolved", code: "route.target.unresolved" };
1170
+ }
1171
+ }
1172
+ return { kind: "unresolved", code: "route.target.unresolved" };
1173
+ }
1174
+
1175
+ // src/routing/route.ts
1176
+ function allNotProven() {
1177
+ return ROUTING_TRIGGERS.map((reason) => ({
1178
+ reason,
1179
+ status: "not-proven",
1180
+ code: ROUTE_EVIDENCE_NOT_PROVEN_CODE[reason]
1181
+ }));
1182
+ }
1183
+ function notRequiredRouting() {
1184
+ return {
1185
+ escalate: false,
1186
+ reasons: [],
1187
+ evidenceStatus: allNotProven(),
1188
+ target: { kind: "not-required", code: "route.target.not-required" }
1189
+ };
1190
+ }
1191
+ function intersects(a, b) {
1192
+ if (!b || b.size === 0)
1193
+ return false;
1194
+ for (const key2 of a)
1195
+ if (b.has(key2))
1196
+ return true;
1197
+ return false;
1198
+ }
1199
+ function computeRouting(ctx, proposalTargets, contradictsAccepted) {
1200
+ const evidence = ctx.input.routingEvidence;
1201
+ const proposalKeys = proposalTargets.targetKeys;
1202
+ const frontmatter = ctx.proposed.frontmatter;
1203
+ const proven = {
1204
+ "one-way-door": frontmatter.reversibility === "one-way-door",
1205
+ "cost-threshold": evidence?.costEvidence !== undefined && evidence.costEvidence.normalizedCost >= evidence.costEvidence.threshold,
1206
+ "security-surface": intersects(proposalKeys, evidence?.securitySurfaceTargets),
1207
+ "data-residency": evidence?.dataResidency?.present === true,
1208
+ regulatory: frontmatter.complianceControls.length > 0 || intersects(proposalKeys, evidence?.regulatedTargets),
1209
+ "contradicts-accepted-adr": contradictsAccepted,
1210
+ "agent-authored-production": (frontmatter.provenance?.authoredBy === "agent" || frontmatter.provenance?.authoredBy === "agent-drafted") && intersects(proposalKeys, evidence?.productionTargets),
1211
+ "human-requested": evidence?.humanRequested?.requester !== undefined
1212
+ };
1213
+ const evidenceStatus = ROUTING_TRIGGERS.map((reason) => proven[reason] ? { reason, status: "proven", code: ROUTE_ESCALATE_CODE[reason] } : { reason, status: "not-proven", code: ROUTE_EVIDENCE_NOT_PROVEN_CODE[reason] });
1214
+ const reasons = ROUTING_TRIGGERS.filter((reason) => proven[reason]);
1215
+ const escalate = reasons.length > 0;
1216
+ if (!escalate) {
1217
+ return {
1218
+ escalate: false,
1219
+ reasons: [],
1220
+ evidenceStatus,
1221
+ target: { kind: "not-required", code: "route.target.not-required" }
1222
+ };
1223
+ }
1224
+ const directory = ctx.input.identity;
1225
+ if (!directory) {
1226
+ return { escalate: true, reasons, evidenceStatus, target: { kind: "unresolved", code: "route.target.unresolved" } };
1227
+ }
1228
+ const resolvedPaths = proposalTargets.targets.filter((id) => id.kind === "path").map((id) => id.id);
1229
+ const resolvedEntities = proposalTargets.targets.filter((id) => id.kind === "entity");
1230
+ const target = resolveRouteTarget(buildIdentityIndex(directory), frontmatter.deciders, directory, resolvedPaths, resolvedEntities);
1231
+ return { escalate: true, reasons, evidenceStatus, target };
1232
+ }
1233
+
1234
+ // src/routing/accepted-assertion.ts
1235
+ function contradictsAcceptedAdr(ctx, proposalTargetKeys) {
1236
+ if (proposalTargetKeys.size === 0)
1237
+ return false;
1238
+ for (const accepted of ctx.acceptedRecords) {
1239
+ const acceptedTargets = resolveRecordTargets(accepted, ctx.input.targetRegistry, ctx.input.targets, ctx.input.resolutionLog);
1240
+ const overlaps = [...proposalTargetKeys].some((key2) => acceptedTargets.targetKeys.has(key2));
1241
+ if (!overlaps)
1242
+ continue;
1243
+ for (const assertion of accepted.frontmatter.assertions) {
1244
+ const key2 = makeAssertionKey(accepted.log, accepted.path, assertion.id);
1245
+ const compiled = compileAssertionForScope(ctx, accepted, assertion);
1246
+ if (!compiled.ok)
1247
+ continue;
1248
+ const proposedInput = ctx.input.assertionInputs.inputs[key2]?.document;
1249
+ if (proposedInput === undefined)
1250
+ continue;
1251
+ const evaluated = compiled.evaluate(proposedInput);
1252
+ if (evaluated.ok && !evaluated.pass)
1253
+ return true;
1254
+ }
1255
+ }
1256
+ return false;
1257
+ }
1258
+
1259
+ // src/patch/project.ts
1260
+ import { AdrRef } from "@adrkit/core";
1261
+ function isViolation(result) {
1262
+ return result.status === "fail";
1263
+ }
1264
+ function representativeFinding(result) {
1265
+ return result.findings.find((finding) => finding.reason === result.reason) ?? result.findings[0];
1266
+ }
1267
+ function projectFinding(result) {
1268
+ const finding = representativeFinding(result);
1269
+ const adr = finding?.adr !== undefined && AdrRef.safeParse(finding.adr).success ? finding.adr : undefined;
1270
+ return {
1271
+ rule: result.rule,
1272
+ severity: RULE_SEVERITY[result.rule],
1273
+ ...finding?.message ? { message: finding.message } : {},
1274
+ ...adr !== undefined ? { adr } : {}
1275
+ };
1276
+ }
1277
+ function projectPatch(report) {
1278
+ const deterministicFindings = report.results.filter(isViolation).map(projectFinding);
1279
+ return {
1280
+ deterministicFindings,
1281
+ escalate: report.routing.escalate,
1282
+ escalationReasons: report.routing.reasons
1283
+ };
1284
+ }
1285
+
1286
+ // src/pass0.ts
1287
+ var PROPOSAL_STATUSES = new Set(["draft", "proposed"]);
1288
+ var NON_PROPOSAL_STATUSES = new Set([
1289
+ "accepted",
1290
+ "rejected",
1291
+ "superseded",
1292
+ "deprecated"
1293
+ ]);
1294
+ function resolveProposal(input) {
1295
+ const proposed = input.corpus.records.find((record) => record.path === input.proposalPath);
1296
+ if (proposed) {
1297
+ return { proposalPath: input.proposalPath, schemaFindings: [], proposed };
1298
+ }
1299
+ const onPath = input.corpus.findings.filter((finding) => finding.path === input.proposalPath);
1300
+ const schemaFindings = onPath.length > 0 ? onPath : [
1301
+ {
1302
+ rule: "file-read",
1303
+ severity: "error",
1304
+ message: `Proposal not found in corpus: ${input.proposalPath}`,
1305
+ path: input.proposalPath
1306
+ }
1307
+ ];
1308
+ return { proposalPath: input.proposalPath, schemaFindings };
1309
+ }
1310
+ function evaluateStructuralAndBacked(context) {
1311
+ const results = new Map;
1312
+ results.set("id-unique", evaluateIdUnique(context));
1313
+ results.set("supersession-consistent", evaluateSupersessionConsistent(context));
1314
+ results.set("no-orphan-refs", evaluateNoOrphanRefs(context));
1315
+ results.set("affects-resolvable", evaluateAffectsResolvable(context));
1316
+ results.set("affects-overlap", evaluateAffectsOverlap(context));
1317
+ results.set("scope-hierarchy", evaluateScopeHierarchy(context));
1318
+ const assertionOutcomes = computeAssertionOutcomes(context);
1319
+ const compile = evaluateAssertionsCompile(assertionOutcomes);
1320
+ results.set("assertions-compile", compile);
1321
+ results.set("assertions-pass", compile.status === "fail" ? notEvaluated("assertions-pass", "not-evaluated.prereq-failed") : evaluateAssertionsPass(assertionOutcomes));
1322
+ results.set("decider-resolvable", evaluateDeciderResolvable(context));
1323
+ results.set("expiry-sane", evaluateExpirySane(context));
1324
+ return results;
1325
+ }
1326
+ function evaluatePass0(input) {
1327
+ const resolution = resolveProposal(input);
1328
+ if (resolution.proposed) {
1329
+ const status = resolution.proposed.frontmatter.status;
1330
+ if (!PROPOSAL_STATUSES.has(status) && NON_PROPOSAL_STATUSES.has(status)) {
1331
+ const error = {
1332
+ code: "candidate-status-not-proposal",
1333
+ proposalPath: input.proposalPath,
1334
+ actualStatus: status
1335
+ };
1336
+ return { kind: "input-error", error };
1337
+ }
1338
+ }
1339
+ const schemaValid = evaluateSchemaValid(resolution);
1340
+ const resultsByRule = new Map;
1341
+ resultsByRule.set("schema-valid", schemaValid);
1342
+ if (schemaValid.status === "fail" || !resolution.proposed) {
1343
+ for (const rule of RULE_IDS) {
1344
+ if (rule === "schema-valid")
1345
+ continue;
1346
+ resultsByRule.set(rule, notEvaluated(rule, "not-evaluated.schema-invalid"));
1347
+ }
1348
+ const report2 = assembleReport(input.proposalPath, resultsByRule, notRequiredRouting());
1349
+ return { kind: "evaluated", result: { report: report2, patch: projectPatch(report2) } };
1350
+ }
1351
+ const proposed = resolution.proposed;
1352
+ const context = {
1353
+ input,
1354
+ proposed,
1355
+ resolution,
1356
+ corpusRecords: input.corpus.records,
1357
+ acceptedRecords: acceptedRecordsExcludingCandidate(input.corpus.records, input.proposalPath),
1358
+ evaluationDate: input.evaluationDate
1359
+ };
1360
+ for (const [rule, result] of evaluateStructuralAndBacked(context)) {
1361
+ resultsByRule.set(rule, result);
1362
+ }
1363
+ const proposalTargets = resolveRecordTargets(proposed, input.targetRegistry, input.targets, input.resolutionLog);
1364
+ const contradicts = contradictsAcceptedAdr(context, proposalTargets.targetKeys);
1365
+ const routing = computeRouting(context, proposalTargets, contradicts);
1366
+ const report = assembleReport(input.proposalPath, resultsByRule, routing);
1367
+ return { kind: "evaluated", result: { report, patch: projectPatch(report) } };
1368
+ }
1369
+ // src/targets/registry.ts
1370
+ function createTargetResolutionRegistry(ports) {
1371
+ const byType = new Map;
1372
+ for (const port of ports)
1373
+ byType.set(port.type, port);
1374
+ return { get: (type) => byType.get(type) };
1375
+ }
1376
+ var emptyTargetResolutionRegistry = {
1377
+ get: () => {
1378
+ return;
1379
+ }
1380
+ };
1381
+ // src/targets/path.ts
1382
+ import { matchPathPattern as matchPathPattern2 } from "@adrkit/core";
1383
+ function createPathTargetResolver() {
1384
+ return {
1385
+ type: "path",
1386
+ resolve(matcher, context) {
1387
+ const inventory = context.inventory.trackedPaths;
1388
+ if (inventory === undefined) {
1389
+ return { status: "inert", ids: [], reason: "affects-resolvable.backing-absent" };
1390
+ }
1391
+ const ids = inventory.filter((path) => matchPathPattern2(matcher.pattern, [path]).matched).map((path) => makeTargetId("path", path));
1392
+ return {
1393
+ status: "resolved",
1394
+ ids,
1395
+ reason: ids.length > 0 ? "affects-resolvable.ok" : "affects-resolvable.zero-targets"
1396
+ };
1397
+ }
1398
+ };
1399
+ }
1400
+ // src/targets/package.ts
1401
+ import { matchPackagePattern, parsePackagePattern } from "@adrkit/core";
1402
+ function createPackageTargetResolver() {
1403
+ return {
1404
+ type: "package",
1405
+ resolve(matcher, context) {
1406
+ const inventory = context.inventory.dependencies;
1407
+ if (inventory === undefined) {
1408
+ return { status: "inert", ids: [], reason: "affects-resolvable.backing-absent" };
1409
+ }
1410
+ if (!parsePackagePattern(matcher.pattern)) {
1411
+ return { status: "resolved", ids: [], reason: "affects-resolvable.zero-targets" };
1412
+ }
1413
+ const ids = inventory.filter((dependency) => matchPackagePattern(matcher.pattern, [
1414
+ { name: dependency.name, version: dependency.version ?? "0.0.0-unversioned" }
1415
+ ]).matched).map((dependency) => makeTargetId("package", dependency.name));
1416
+ return {
1417
+ status: "resolved",
1418
+ ids,
1419
+ reason: ids.length > 0 ? "affects-resolvable.ok" : "affects-resolvable.zero-targets"
1420
+ };
1421
+ }
1422
+ };
1423
+ }
1424
+ // src/assertions/registry.ts
1425
+ function createAssertionEngineRegistry(ports = {}) {
1426
+ const registry = {
1427
+ ...ports.rego ? { rego: ports.rego } : {},
1428
+ ...ports.jsonpath ? { jsonpath: ports.jsonpath } : {},
1429
+ ...ports.grep ? { grep: ports.grep } : {},
1430
+ ...ports.custom ? { custom: ports.custom } : {}
1431
+ };
1432
+ return registry;
1433
+ }
1434
+ var emptyAssertionEngineRegistry = {};
1435
+ // src/assertions/jsonpath.ts
1436
+ import { query } from "jsonpath-rfc9535";
1437
+ import parseJsonPath from "jsonpath-rfc9535/parser";
1438
+
1439
+ // src/report/serialize.ts
1440
+ function isRecord(value) {
1441
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1442
+ }
1443
+ function indentOf(depth, pretty) {
1444
+ return pretty ? " ".repeat(depth) : "";
1445
+ }
1446
+ function canonicalStringify(root, pretty = false) {
1447
+ const nl = pretty ? `
1448
+ ` : "";
1449
+ const colon = pretty ? ": " : ":";
1450
+ const out = [];
1451
+ const stack = [{ kind: "val", value: root, depth: 0 }];
1452
+ while (stack.length > 0) {
1453
+ const task = stack.pop();
1454
+ if (!task)
1455
+ continue;
1456
+ if (task.kind === "str") {
1457
+ out.push(task.text);
1458
+ continue;
1459
+ }
1460
+ const { value, depth } = task;
1461
+ if (value === null) {
1462
+ out.push("null");
1463
+ continue;
1464
+ }
1465
+ const type = typeof value;
1466
+ if (type === "string" || type === "boolean") {
1467
+ out.push(JSON.stringify(value));
1468
+ continue;
1469
+ }
1470
+ if (type === "number") {
1471
+ out.push(Number.isFinite(value) ? JSON.stringify(value) : "null");
1472
+ continue;
1473
+ }
1474
+ if (Array.isArray(value)) {
1475
+ if (value.length === 0) {
1476
+ out.push("[]");
1477
+ continue;
1478
+ }
1479
+ const parts = [{ kind: "str", text: `[${nl}` }];
1480
+ value.forEach((item, i) => {
1481
+ parts.push({ kind: "str", text: indentOf(depth + 1, pretty) });
1482
+ parts.push({ kind: "val", value: item, depth: depth + 1 });
1483
+ parts.push({ kind: "str", text: `${i < value.length - 1 ? "," : ""}${nl}` });
1484
+ });
1485
+ parts.push({ kind: "str", text: `${indentOf(depth, pretty)}]` });
1486
+ for (let i = parts.length - 1;i >= 0; i -= 1)
1487
+ stack.push(parts[i]);
1488
+ continue;
1489
+ }
1490
+ if (isRecord(value)) {
1491
+ const source = value;
1492
+ const keys = Object.keys(source).filter((key2) => source[key2] !== undefined).sort(byCodeUnit);
1493
+ if (keys.length === 0) {
1494
+ out.push("{}");
1495
+ continue;
1496
+ }
1497
+ const parts = [{ kind: "str", text: `{${nl}` }];
1498
+ keys.forEach((key2, i) => {
1499
+ parts.push({ kind: "str", text: `${indentOf(depth + 1, pretty)}${JSON.stringify(key2)}${colon}` });
1500
+ parts.push({ kind: "val", value: source[key2], depth: depth + 1 });
1501
+ parts.push({ kind: "str", text: `${i < keys.length - 1 ? "," : ""}${nl}` });
1502
+ });
1503
+ parts.push({ kind: "str", text: `${indentOf(depth, pretty)}}` });
1504
+ for (let i = parts.length - 1;i >= 0; i -= 1)
1505
+ stack.push(parts[i]);
1506
+ continue;
1507
+ }
1508
+ out.push("null");
1509
+ }
1510
+ return out.join("");
1511
+ }
1512
+ function canonicalize(value) {
1513
+ if (value === null)
1514
+ return null;
1515
+ if (typeof value === "string" || typeof value === "boolean")
1516
+ return value;
1517
+ if (typeof value === "number")
1518
+ return Number.isFinite(value) ? value : null;
1519
+ if (Array.isArray(value)) {
1520
+ return value.map((item) => canonicalize(item));
1521
+ }
1522
+ if (!isRecord(value))
1523
+ return null;
1524
+ const source = value;
1525
+ const out = Object.create(null);
1526
+ for (const key2 of Object.keys(source).sort(byCodeUnit)) {
1527
+ const entry = source[key2];
1528
+ if (entry === undefined)
1529
+ continue;
1530
+ out[key2] = canonicalize(entry);
1531
+ }
1532
+ return out;
1533
+ }
1534
+ function canonicalBytes(value) {
1535
+ return `${canonicalStringify(value, true)}
1536
+ `;
1537
+ }
1538
+ function serializeReport(report) {
1539
+ return canonicalBytes(report);
1540
+ }
1541
+ function serializePatch(patch) {
1542
+ return canonicalBytes(patch);
1543
+ }
1544
+ function serializeArtifacts(report, patch) {
1545
+ return { report: serializeReport(report), patch: serializePatch(patch) };
1546
+ }
1547
+
1548
+ // src/assertions/limits.ts
1549
+ var ASSERTION_INPUT_LIMITS = { maxBytes: 1024 * 1024, maxDepth: 64, maxNodes: 1e5 };
1550
+ var REGO_DATA_LIMITS = { maxBytes: 1024 * 1024, maxDepth: 64, maxNodes: 1e5 };
1551
+ function canonicalJsonString(value) {
1552
+ return canonicalStringify(value, false);
1553
+ }
1554
+ function withinStructuralLimits(value, limits) {
1555
+ let nodes = 0;
1556
+ const stack = [{ value, depth: 1 }];
1557
+ while (stack.length > 0) {
1558
+ const { value: current, depth } = stack.pop();
1559
+ nodes += 1;
1560
+ if (nodes > limits.maxNodes)
1561
+ return false;
1562
+ if (depth > limits.maxDepth)
1563
+ return false;
1564
+ if (Array.isArray(current)) {
1565
+ for (const item of current)
1566
+ stack.push({ value: item, depth: depth + 1 });
1567
+ } else if (current !== null && typeof current === "object") {
1568
+ for (const entry of Object.values(current)) {
1569
+ stack.push({ value: entry, depth: depth + 1 });
1570
+ }
1571
+ }
1572
+ }
1573
+ return true;
1574
+ }
1575
+ function withinJsonLimits(value, limits) {
1576
+ if (!withinStructuralLimits(value, limits))
1577
+ return false;
1578
+ const bytes = new TextEncoder().encode(canonicalJsonString(value)).length;
1579
+ return bytes <= limits.maxBytes;
1580
+ }
1581
+
1582
+ // src/assertions/jsonpath.ts
1583
+ var MAX_SOURCE_BYTES = 8 * 1024;
1584
+ var ALLOWED_FUNCTIONS = new Set(["length", "count", "value"]);
1585
+ function functionNames(node, out) {
1586
+ if (Array.isArray(node)) {
1587
+ for (const item of node)
1588
+ functionNames(item, out);
1589
+ return;
1590
+ }
1591
+ if (node === null || typeof node !== "object")
1592
+ return;
1593
+ const record = node;
1594
+ if (record.type === "FunctionExpr" && typeof record.name === "string") {
1595
+ out.add(record.name);
1596
+ }
1597
+ for (const value of Object.values(record))
1598
+ functionNames(value, out);
1599
+ }
1600
+ function usesOnlyAllowedFunctions(ast) {
1601
+ const names = new Set;
1602
+ functionNames(ast, names);
1603
+ for (const name of names) {
1604
+ if (!ALLOWED_FUNCTIONS.has(name))
1605
+ return false;
1606
+ }
1607
+ return true;
1608
+ }
1609
+ function createJsonPathEngine() {
1610
+ return {
1611
+ engine: "jsonpath",
1612
+ profile: "source",
1613
+ compile(effectiveSource, sourceRef) {
1614
+ if (new TextEncoder().encode(effectiveSource).length > MAX_SOURCE_BYTES) {
1615
+ return { ok: false, reason: "assertions-compile.parse-error" };
1616
+ }
1617
+ let ast;
1618
+ try {
1619
+ ast = parseJsonPath(effectiveSource);
1620
+ } catch {
1621
+ return { ok: false, reason: "assertions-compile.parse-error" };
1622
+ }
1623
+ if (!usesOnlyAllowedFunctions(ast)) {
1624
+ return { ok: false, reason: "assertions-compile.parse-error" };
1625
+ }
1626
+ const payload = { source: effectiveSource, ast };
1627
+ return {
1628
+ ok: true,
1629
+ compiled: { engine: "jsonpath", payload, ...sourceRef !== undefined ? { sourceRef } : {} }
1630
+ };
1631
+ },
1632
+ evaluate(compiled, input) {
1633
+ if (!withinJsonLimits(input, ASSERTION_INPUT_LIMITS)) {
1634
+ return { ok: false, reason: "assertions-pass.evaluation-error" };
1635
+ }
1636
+ try {
1637
+ const nodes = query(input, compiled.payload.source);
1638
+ return { ok: true, pass: nodes.length > 0 };
1639
+ } catch {
1640
+ return { ok: false, reason: "assertions-pass.evaluation-error" };
1641
+ }
1642
+ }
1643
+ };
1644
+ }
1645
+ // src/crypto/sha256.ts
1646
+ var K = new Uint32Array([
1647
+ 1116352408,
1648
+ 1899447441,
1649
+ 3049323471,
1650
+ 3921009573,
1651
+ 961987163,
1652
+ 1508970993,
1653
+ 2453635748,
1654
+ 2870763221,
1655
+ 3624381080,
1656
+ 310598401,
1657
+ 607225278,
1658
+ 1426881987,
1659
+ 1925078388,
1660
+ 2162078206,
1661
+ 2614888103,
1662
+ 3248222580,
1663
+ 3835390401,
1664
+ 4022224774,
1665
+ 264347078,
1666
+ 604807628,
1667
+ 770255983,
1668
+ 1249150122,
1669
+ 1555081692,
1670
+ 1996064986,
1671
+ 2554220882,
1672
+ 2821834349,
1673
+ 2952996808,
1674
+ 3210313671,
1675
+ 3336571891,
1676
+ 3584528711,
1677
+ 113926993,
1678
+ 338241895,
1679
+ 666307205,
1680
+ 773529912,
1681
+ 1294757372,
1682
+ 1396182291,
1683
+ 1695183700,
1684
+ 1986661051,
1685
+ 2177026350,
1686
+ 2456956037,
1687
+ 2730485921,
1688
+ 2820302411,
1689
+ 3259730800,
1690
+ 3345764771,
1691
+ 3516065817,
1692
+ 3600352804,
1693
+ 4094571909,
1694
+ 275423344,
1695
+ 430227734,
1696
+ 506948616,
1697
+ 659060556,
1698
+ 883997877,
1699
+ 958139571,
1700
+ 1322822218,
1701
+ 1537002063,
1702
+ 1747873779,
1703
+ 1955562222,
1704
+ 2024104815,
1705
+ 2227730452,
1706
+ 2361852424,
1707
+ 2428436474,
1708
+ 2756734187,
1709
+ 3204031479,
1710
+ 3329325298
1711
+ ]);
1712
+ function rotr(x, n) {
1713
+ return x >>> n | x << 32 - n;
1714
+ }
1715
+ function sha256Bytes(message) {
1716
+ const h = new Uint32Array([1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]);
1717
+ const length = message.length;
1718
+ const bitLength = length * 8;
1719
+ const paddedLength = (length + 8 >> 6) + 1;
1720
+ const padded = new Uint8Array(paddedLength * 64);
1721
+ padded.set(message);
1722
+ padded[length] = 128;
1723
+ const dv = new DataView(padded.buffer);
1724
+ dv.setUint32(padded.length - 4, bitLength >>> 0, false);
1725
+ dv.setUint32(padded.length - 8, Math.floor(bitLength / 4294967296), false);
1726
+ const w = new Uint32Array(64);
1727
+ for (let offset = 0;offset < padded.length; offset += 64) {
1728
+ for (let i = 0;i < 16; i += 1)
1729
+ w[i] = dv.getUint32(offset + i * 4, false);
1730
+ for (let i = 16;i < 64; i += 1) {
1731
+ const a2 = w[i - 15];
1732
+ const b2 = w[i - 2];
1733
+ const s0 = rotr(a2, 7) ^ rotr(a2, 18) ^ a2 >>> 3;
1734
+ const s1 = rotr(b2, 17) ^ rotr(b2, 19) ^ b2 >>> 10;
1735
+ w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
1736
+ }
1737
+ let a = h[0];
1738
+ let b = h[1];
1739
+ let c = h[2];
1740
+ let d = h[3];
1741
+ let e = h[4];
1742
+ let f = h[5];
1743
+ let g = h[6];
1744
+ let hh = h[7];
1745
+ for (let i = 0;i < 64; i += 1) {
1746
+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
1747
+ const ch = e & f ^ ~e & g;
1748
+ const t1 = hh + S1 + ch + K[i] + w[i] >>> 0;
1749
+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
1750
+ const maj = a & b ^ a & c ^ b & c;
1751
+ const t2 = S0 + maj >>> 0;
1752
+ hh = g;
1753
+ g = f;
1754
+ f = e;
1755
+ e = d + t1 >>> 0;
1756
+ d = c;
1757
+ c = b;
1758
+ b = a;
1759
+ a = t1 + t2 >>> 0;
1760
+ }
1761
+ h[0] = h[0] + a >>> 0;
1762
+ h[1] = h[1] + b >>> 0;
1763
+ h[2] = h[2] + c >>> 0;
1764
+ h[3] = h[3] + d >>> 0;
1765
+ h[4] = h[4] + e >>> 0;
1766
+ h[5] = h[5] + f >>> 0;
1767
+ h[6] = h[6] + g >>> 0;
1768
+ h[7] = h[7] + hh >>> 0;
1769
+ }
1770
+ const out = new Uint8Array(32);
1771
+ const outView = new DataView(out.buffer);
1772
+ for (let i = 0;i < 8; i += 1)
1773
+ outView.setUint32(i * 4, h[i], false);
1774
+ return out;
1775
+ }
1776
+ function toHex(bytes) {
1777
+ let hex = "";
1778
+ for (const byte of bytes)
1779
+ hex += byte.toString(16).padStart(2, "0");
1780
+ return hex;
1781
+ }
1782
+ function sha256Hex(message) {
1783
+ return toHex(sha256Bytes(message));
1784
+ }
1785
+ var encoder = new TextEncoder;
1786
+ function sha256HexUtf8(text) {
1787
+ return sha256Hex(encoder.encode(text));
1788
+ }
1789
+
1790
+ // src/assertions/rego.ts
1791
+ var MEDIA_TYPE = "application/vnd.adrkit.rego-wasm-policy.v1+json";
1792
+ var SCHEMA_VERSION = "adrkit.rego-wasm-policy/v1";
1793
+ var CAPABILITIES_PROFILE = "adrkit.rego-wasm.capabilities/v1";
1794
+ var MAX_SOURCE_BYTES2 = 64 * 1024;
1795
+ var MAX_MODULE_BYTES = 4 * 1024 * 1024;
1796
+ var MAX_MODULE_BASE64_LEN = Math.ceil(MAX_MODULE_BYTES / 3) * 4;
1797
+ var MAX_ENVELOPE_BYTES = Math.floor(6.75 * 1024 * 1024);
1798
+ var HEX_64 = /^[0-9a-f]{64}$/;
1799
+ var ENVELOPE_KEYS = [
1800
+ "mediaType",
1801
+ "schemaVersion",
1802
+ "source",
1803
+ "sourceSha256",
1804
+ "moduleBase64",
1805
+ "moduleSha256",
1806
+ "data",
1807
+ "entrypoint",
1808
+ "abi",
1809
+ "compiler",
1810
+ "requiredHostBuiltins",
1811
+ "envelopeSha256"
1812
+ ];
1813
+ function fail(message) {
1814
+ return { ok: false, message: `Rego-Wasm envelope: ${message}` };
1815
+ }
1816
+ function isPlainObject(value) {
1817
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1818
+ }
1819
+ function decodeCanonicalBase64(b64) {
1820
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(b64) || b64.length % 4 !== 0)
1821
+ return;
1822
+ let binary;
1823
+ try {
1824
+ binary = atob(b64);
1825
+ } catch {
1826
+ return;
1827
+ }
1828
+ if (btoa(binary) !== b64)
1829
+ return;
1830
+ const bytes = new Uint8Array(binary.length);
1831
+ for (let i = 0;i < binary.length; i += 1)
1832
+ bytes[i] = binary.charCodeAt(i);
1833
+ return bytes;
1834
+ }
1835
+ function hasWasmMagic(bytes) {
1836
+ return bytes.length >= 8 && bytes[0] === 0 && bytes[1] === 97 && bytes[2] === 115 && bytes[3] === 109;
1837
+ }
1838
+ function isStructurallyValidWasm(bytes) {
1839
+ try {
1840
+ return WebAssembly.validate(bytes);
1841
+ } catch {
1842
+ return false;
1843
+ }
1844
+ }
1845
+ function isJsonValue(root) {
1846
+ const active = new Set;
1847
+ const stack = [{ kind: "enter", value: root }];
1848
+ while (stack.length > 0) {
1849
+ const task = stack.pop();
1850
+ if (!task)
1851
+ continue;
1852
+ if (task.kind === "exit") {
1853
+ active.delete(task.value);
1854
+ continue;
1855
+ }
1856
+ const value = task.value;
1857
+ if (value === null)
1858
+ continue;
1859
+ const type = typeof value;
1860
+ if (type === "boolean" || type === "string")
1861
+ continue;
1862
+ if (type === "number") {
1863
+ if (!Number.isFinite(value))
1864
+ return false;
1865
+ continue;
1866
+ }
1867
+ if (typeof value !== "object" || value === null)
1868
+ return false;
1869
+ const objectValue = value;
1870
+ if (active.has(objectValue))
1871
+ return false;
1872
+ active.add(objectValue);
1873
+ stack.push({ kind: "exit", value: objectValue });
1874
+ if (Array.isArray(objectValue)) {
1875
+ for (let index = objectValue.length - 1;index >= 0; index -= 1) {
1876
+ if (!(index in objectValue))
1877
+ return false;
1878
+ stack.push({ kind: "enter", value: objectValue[index] });
1879
+ }
1880
+ continue;
1881
+ }
1882
+ let prototype;
1883
+ let descriptors;
1884
+ try {
1885
+ prototype = Object.getPrototypeOf(objectValue);
1886
+ descriptors = Object.getOwnPropertyDescriptors(objectValue);
1887
+ } catch {
1888
+ return false;
1889
+ }
1890
+ if (prototype !== Object.prototype && prototype !== null)
1891
+ return false;
1892
+ for (const key2 of Reflect.ownKeys(descriptors)) {
1893
+ if (typeof key2 !== "string")
1894
+ return false;
1895
+ const descriptor = descriptors[key2];
1896
+ if (!descriptor?.enumerable || !("value" in descriptor))
1897
+ return false;
1898
+ stack.push({ kind: "enter", value: descriptor.value });
1899
+ }
1900
+ }
1901
+ return true;
1902
+ }
1903
+ function isCanonicalEntrypoint(entrypoint) {
1904
+ if (!entrypoint.startsWith("/") || entrypoint.length < 2)
1905
+ return false;
1906
+ if (entrypoint.endsWith("/") || entrypoint.includes("//"))
1907
+ return false;
1908
+ return entrypoint.slice(1).split("/").every((segment) => segment.length > 0);
1909
+ }
1910
+ function validateRegoWasmPolicyEnvelopeV1(artifact) {
1911
+ if (!isPlainObject(artifact))
1912
+ return fail("must be an object");
1913
+ for (const key2 of Object.keys(artifact)) {
1914
+ if (!ENVELOPE_KEYS.includes(key2))
1915
+ return fail(`unknown key "${key2}"`);
1916
+ }
1917
+ for (const key2 of ENVELOPE_KEYS) {
1918
+ if (!(key2 in artifact))
1919
+ return fail(`missing key "${key2}"`);
1920
+ }
1921
+ if (artifact.mediaType !== MEDIA_TYPE)
1922
+ return fail("mediaType mismatch");
1923
+ if (artifact.schemaVersion !== SCHEMA_VERSION)
1924
+ return fail("schemaVersion mismatch");
1925
+ const source = artifact.source;
1926
+ if (typeof source !== "string")
1927
+ return fail("source must be a string");
1928
+ if (new TextEncoder().encode(source).length > MAX_SOURCE_BYTES2)
1929
+ return fail("source exceeds 64 KiB");
1930
+ const sourceSha256 = artifact.sourceSha256;
1931
+ if (typeof sourceSha256 !== "string" || !HEX_64.test(sourceSha256))
1932
+ return fail("sourceSha256 must be 64 lowercase hex");
1933
+ if (sha256HexUtf8(source) !== sourceSha256)
1934
+ return fail("sourceSha256 does not match source");
1935
+ const moduleBase64 = artifact.moduleBase64;
1936
+ if (typeof moduleBase64 !== "string")
1937
+ return fail("moduleBase64 must be a string");
1938
+ if (moduleBase64.length > MAX_MODULE_BASE64_LEN)
1939
+ return fail("moduleBase64 exceeds the maximum encoded length for a 4 MiB module");
1940
+ const moduleBytes = decodeCanonicalBase64(moduleBase64);
1941
+ if (!moduleBytes)
1942
+ return fail("moduleBase64 is not canonical base64");
1943
+ if (moduleBytes.length > MAX_MODULE_BYTES)
1944
+ return fail("module exceeds 4 MiB");
1945
+ if (!hasWasmMagic(moduleBytes))
1946
+ return fail("module is not a valid Wasm binary (magic)");
1947
+ if (!isStructurallyValidWasm(moduleBytes))
1948
+ return fail("module is not a structurally valid Wasm binary");
1949
+ const moduleSha256 = artifact.moduleSha256;
1950
+ if (typeof moduleSha256 !== "string" || !HEX_64.test(moduleSha256))
1951
+ return fail("moduleSha256 must be 64 lowercase hex");
1952
+ if (sha256Hex(moduleBytes) !== moduleSha256)
1953
+ return fail("moduleSha256 does not match module");
1954
+ const data = artifact.data;
1955
+ if (!isJsonValue(data))
1956
+ return fail("data must be a JSON value");
1957
+ if (!withinJsonLimits(data, REGO_DATA_LIMITS))
1958
+ return fail("data exceeds size/depth/node limits");
1959
+ const entrypoint = artifact.entrypoint;
1960
+ if (typeof entrypoint !== "string" || !isCanonicalEntrypoint(entrypoint)) {
1961
+ return fail("entrypoint must be a canonical /slash/path");
1962
+ }
1963
+ const abi = artifact.abi;
1964
+ if (!isPlainObject(abi))
1965
+ return fail("abi must be an object");
1966
+ for (const key2 of Object.keys(abi)) {
1967
+ if (key2 !== "major" && key2 !== "minor")
1968
+ return fail(`unknown abi key "${key2}"`);
1969
+ }
1970
+ if (abi.major !== 1 || abi.minor !== 3)
1971
+ return fail("unsupported ABI (expected 1.3)");
1972
+ const compiler = artifact.compiler;
1973
+ if (!isPlainObject(compiler))
1974
+ return fail("compiler must be an object");
1975
+ for (const key2 of Object.keys(compiler)) {
1976
+ if (!["name", "version", "capabilitiesProfile", "capabilitiesSha256"].includes(key2)) {
1977
+ return fail(`unknown compiler key "${key2}"`);
1978
+ }
1979
+ }
1980
+ if (compiler.name !== "opa")
1981
+ return fail('compiler.name must be "opa"');
1982
+ const compilerVersion = compiler.version;
1983
+ if (typeof compilerVersion !== "string" || compilerVersion.length === 0)
1984
+ return fail("compiler.version required");
1985
+ if (compiler.capabilitiesProfile !== CAPABILITIES_PROFILE)
1986
+ return fail("unsupported capabilities profile");
1987
+ const capabilitiesSha256 = compiler.capabilitiesSha256;
1988
+ if (typeof capabilitiesSha256 !== "string" || !HEX_64.test(capabilitiesSha256)) {
1989
+ return fail("compiler.capabilitiesSha256 must be 64 lowercase hex");
1990
+ }
1991
+ if (!Array.isArray(artifact.requiredHostBuiltins) || artifact.requiredHostBuiltins.length !== 0) {
1992
+ return fail("requiredHostBuiltins must be empty in v1");
1993
+ }
1994
+ const envelopeSha256 = artifact.envelopeSha256;
1995
+ if (typeof envelopeSha256 !== "string" || !HEX_64.test(envelopeSha256))
1996
+ return fail("envelopeSha256 must be 64 lowercase hex");
1997
+ const envelope = {
1998
+ mediaType: MEDIA_TYPE,
1999
+ schemaVersion: SCHEMA_VERSION,
2000
+ source,
2001
+ sourceSha256,
2002
+ moduleBase64,
2003
+ moduleSha256,
2004
+ data,
2005
+ entrypoint,
2006
+ abi: { major: 1, minor: 3 },
2007
+ compiler: {
2008
+ name: "opa",
2009
+ version: compilerVersion,
2010
+ capabilitiesProfile: CAPABILITIES_PROFILE,
2011
+ capabilitiesSha256
2012
+ },
2013
+ requiredHostBuiltins: [],
2014
+ envelopeSha256
2015
+ };
2016
+ if (new TextEncoder().encode(canonicalJsonString(envelope)).length > MAX_ENVELOPE_BYTES) {
2017
+ return fail("decoded envelope exceeds 6.75 MiB");
2018
+ }
2019
+ const { envelopeSha256: _boundHash, ...priorFields } = envelope;
2020
+ if (sha256HexUtf8(canonicalJsonString(priorFields)) !== envelopeSha256) {
2021
+ return fail("envelopeSha256 does not bind the prior fields");
2022
+ }
2023
+ return { ok: true, envelope };
2024
+ }
2025
+ export {
2026
+ validateRegoWasmPolicyEnvelopeV1,
2027
+ sortRuleFindings,
2028
+ serializeReport,
2029
+ serializePatch,
2030
+ serializeArtifacts,
2031
+ resolveRecordTargets,
2032
+ parseAssertionKey,
2033
+ normalizePathId,
2034
+ makeTargetId,
2035
+ makeAssertionKey,
2036
+ isReasonCode,
2037
+ isCanonicalAssertionKey,
2038
+ evaluatePass0,
2039
+ emptyTargetResolutionRegistry,
2040
+ emptyAssertionEngineRegistry,
2041
+ createTargetResolutionRegistry,
2042
+ createPathTargetResolver,
2043
+ createPackageTargetResolver,
2044
+ createJsonPathEngine,
2045
+ createAssertionEngineRegistry,
2046
+ computeAssertionOutcomes,
2047
+ compareRuleFindings,
2048
+ canonicalize,
2049
+ canonicalTargetKey,
2050
+ canonicalStringify,
2051
+ canonicalBytes,
2052
+ buildIdentityIndex,
2053
+ assertionKeyForAssertion,
2054
+ anyMatcherInert,
2055
+ aggregate,
2056
+ RULE_SEVERITY,
2057
+ RULE_REASON_PRECEDENCE,
2058
+ RULE_IDS,
2059
+ RUBRIC_VERSION,
2060
+ ROUTING_TRIGGERS,
2061
+ ROUTE_EVIDENCE_NOT_PROVEN_CODE,
2062
+ ROUTE_ESCALATE_CODE,
2063
+ REASON_CODES
2064
+ };