@aiwg/cli 2026.9.21 → 2026.10.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 (210) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +2 -1
  2. package/dist/src/artifacts/cli.js +23 -18
  3. package/dist/src/artifacts/discovery-shadow-evaluator.js +177 -0
  4. package/dist/src/artifacts/discovery-shadow.js +195 -0
  5. package/dist/src/artifacts/fortemi-core-query-adapter.js +6 -2
  6. package/dist/src/artifacts/index-builder.js +47 -9
  7. package/dist/src/artifacts/query-engine.js +108 -5
  8. package/dist/src/artifacts/types.js +1 -0
  9. package/dist/src/cli/handlers/decision.js +88 -0
  10. package/dist/src/cli/handlers/gates.js +154 -0
  11. package/dist/src/cli/handlers/index.js +5 -1
  12. package/dist/src/cli/handlers/sessions.js +10 -7
  13. package/dist/src/cli/handlers/utilities.js +20 -0
  14. package/dist/src/config/aiwg-config.js +15 -2
  15. package/dist/src/decision/acceptance.js +25 -0
  16. package/dist/src/decision/adapters/jev.js +24 -2
  17. package/dist/src/decision/calibration/registry.js +9 -0
  18. package/dist/src/decision/compose.js +74 -15
  19. package/dist/src/decision/context-live-promotion.js +344 -0
  20. package/dist/src/decision/context-live-qualification.js +591 -0
  21. package/dist/src/decision/context-pruning.js +847 -0
  22. package/dist/src/decision/driver.js +430 -0
  23. package/dist/src/decision/egress-live-qualification.js +874 -0
  24. package/dist/src/decision/ensemble/contract.js +64 -1
  25. package/dist/src/decision/ensemble/index.js +1 -0
  26. package/dist/src/decision/ensemble/report.js +8 -42
  27. package/dist/src/decision/ensemble/runtime.js +571 -0
  28. package/dist/src/decision/ensemble-study/artifacts.js +85 -0
  29. package/dist/src/decision/ensemble-study/calibration.js +443 -0
  30. package/dist/src/decision/ensemble-study/corpus.js +229 -0
  31. package/dist/src/decision/ensemble-study/multifact-artifacts.js +23 -0
  32. package/dist/src/decision/ensemble-study/multifact-study.js +596 -0
  33. package/dist/src/decision/ensemble-study/multifact.js +241 -0
  34. package/dist/src/decision/ensemble-study/protocol.js +77 -0
  35. package/dist/src/decision/ensemble-study/score.js +462 -0
  36. package/dist/src/decision/ensemble-study/staged.js +87 -0
  37. package/dist/src/decision/ensemble-study/statistics.js +84 -0
  38. package/dist/src/decision/evaluate.js +227 -14
  39. package/dist/src/decision/export.js +4 -0
  40. package/dist/src/decision/feature-export.js +1074 -0
  41. package/dist/src/decision/graph-index.js +16 -0
  42. package/dist/src/decision/graph-live-qualification.js +1041 -0
  43. package/dist/src/decision/graph-live-workload.js +245 -0
  44. package/dist/src/decision/graph-skill-bridge.js +21 -3
  45. package/dist/src/decision/heldout/calibration.js +140 -0
  46. package/dist/src/decision/heldout/collector.js +430 -0
  47. package/dist/src/decision/heldout/contract.js +309 -0
  48. package/dist/src/decision/heldout/d29-generator-ids.js +12 -0
  49. package/dist/src/decision/heldout/d29-passage-baseline-v2.js +93 -0
  50. package/dist/src/decision/heldout/d29-passage-baseline-v3.js +100 -0
  51. package/dist/src/decision/heldout/d29-passage-baseline.js +66 -0
  52. package/dist/src/decision/heldout/d29-pools-v8.js +575 -0
  53. package/dist/src/decision/heldout/d29-pools.js +288 -0
  54. package/dist/src/decision/heldout/d29-v6.js +169 -0
  55. package/dist/src/decision/heldout/d29-v7.js +202 -0
  56. package/dist/src/decision/heldout/d29-v8.js +586 -0
  57. package/dist/src/decision/heldout/generator-registry.js +67 -0
  58. package/dist/src/decision/heldout/generators.js +696 -0
  59. package/dist/src/decision/heldout/index.js +8 -0
  60. package/dist/src/decision/heldout/journal.js +346 -0
  61. package/dist/src/decision/heldout/study-generators.js +64 -0
  62. package/dist/src/decision/heldout/types.js +2 -0
  63. package/dist/src/decision/host-policy.js +61 -0
  64. package/dist/src/decision/index.js +11 -0
  65. package/dist/src/decision/interop-profile.v1.json +112 -0
  66. package/dist/src/decision/interop.js +653 -0
  67. package/dist/src/decision/issue-triage/index.js +3 -0
  68. package/dist/src/decision/issue-triage/runtime.js +1069 -0
  69. package/dist/src/decision/issue-triage/types.js +2 -0
  70. package/dist/src/decision/patterns/catalog.js +28 -8
  71. package/dist/src/decision/patterns/playground.js +28 -11
  72. package/dist/src/decision/patterns/specs.js +19 -10
  73. package/dist/src/decision/preprocessed-evidence.js +724 -0
  74. package/dist/src/decision/qualification/capture.js +8 -2
  75. package/dist/src/decision/qualification/gate-evidence.js +19 -4
  76. package/dist/src/decision/qualification/load.js +8 -1
  77. package/dist/src/decision/qualification/quality.js +57 -49
  78. package/dist/src/decision/qualification/release.js +76 -3
  79. package/dist/src/decision/qualification/runner.js +5 -2
  80. package/dist/src/decision/routing/contract.js +175 -0
  81. package/dist/src/decision/routing/index.js +5 -0
  82. package/dist/src/decision/routing/report.js +216 -0
  83. package/dist/src/decision/routing/runtime.js +685 -0
  84. package/dist/src/decision/routing/types.js +2 -0
  85. package/dist/src/decision/sdlc-screening.js +1157 -0
  86. package/dist/src/decision/sensitivity/comparative.js +154 -0
  87. package/dist/src/decision/sensitivity/contract.js +218 -0
  88. package/dist/src/decision/sensitivity/index.js +5 -0
  89. package/dist/src/decision/sensitivity/runtime.js +912 -0
  90. package/dist/src/decision/sensitivity/types.js +2 -0
  91. package/dist/src/decision/validate.js +26 -0
  92. package/dist/src/extensions/commands/definitions.js +38 -0
  93. package/dist/src/extensions/manifest.js +44 -0
  94. package/dist/src/gates/discovery.js +315 -0
  95. package/dist/src/gates/driver.js +388 -0
  96. package/dist/src/gates/evaluate.js +719 -0
  97. package/dist/src/gates/floors.js +255 -0
  98. package/dist/src/gates/index.js +17 -0
  99. package/dist/src/gates/providers/evidence.js +38 -0
  100. package/dist/src/gates/providers/index.js +21 -0
  101. package/dist/src/gates/providers/isolated-child-source.js +446 -0
  102. package/dist/src/gates/providers/loader.js +1069 -0
  103. package/dist/src/gates/providers/paired.js +52 -0
  104. package/dist/src/gates/providers/proportion.js +60 -0
  105. package/dist/src/gates/providers/registry.js +40 -0
  106. package/dist/src/gates/providers/scalar.js +41 -0
  107. package/dist/src/gates/providers/screening.js +53 -0
  108. package/dist/src/gates/providers/types.js +25 -0
  109. package/dist/src/gates/registry.js +1003 -0
  110. package/dist/src/gates/report.js +102 -0
  111. package/dist/src/gates/schema.js +56 -0
  112. package/dist/src/gates/stats/binomial.js +26 -0
  113. package/dist/src/gates/stats/bootstrap.js +62 -0
  114. package/dist/src/gates/stats/clopper-pearson.js +95 -0
  115. package/dist/src/gates/stats/digest.js +26 -0
  116. package/dist/src/gates/stats/error.js +5 -0
  117. package/dist/src/gates/stats/index.js +9 -0
  118. package/dist/src/gates/stats/normal.js +35 -0
  119. package/dist/src/gates/stats/paired.js +120 -0
  120. package/dist/src/gates/stats/splits.js +30 -0
  121. package/dist/src/gates/types.js +8 -0
  122. package/dist/src/governance/redaction.js +14 -4
  123. package/dist/src/mcp/muse-native-config.mjs +123 -0
  124. package/dist/src/mcp/tools/decision.mjs +188 -0
  125. package/dist/src/mcp/tools/subsystems.mjs +2 -0
  126. package/dist/src/providers/capability-matrix.yaml +2 -1
  127. package/dist/src/providers/provider-definitions.js +2 -1
  128. package/dist/src/sessions/adapters/muse.js +336 -39
  129. package/dist/src/sessions/batch-import.js +7 -0
  130. package/dist/src/sessions/workspace-discovery.js +13 -2
  131. package/package.json +1 -1
  132. package/schemas/decision/ConformalFrozenOpenData.v2.schema.json +371 -0
  133. package/schemas/decision/ConformalOpenDataReport.v2.schema.json +421 -0
  134. package/schemas/decision/ConformalPreregistration.v2.schema.json +529 -0
  135. package/schemas/decision/ContextPruningCandidate.v1.schema.json +47 -0
  136. package/schemas/decision/ContextPruningEvaluationReport.v1.schema.json +758 -0
  137. package/schemas/decision/ContextPruningPreregistration.v1.schema.json +175 -0
  138. package/schemas/decision/ContextPruningReceipt.v1.schema.json +72 -0
  139. package/schemas/decision/D17Multifact.v1.schema.json +1851 -0
  140. package/schemas/decision/D17StudyAnalysis.v1.schema.json +42 -0
  141. package/schemas/decision/D17StudyAnalysis.v2.schema.json +51 -0
  142. package/schemas/decision/D17StudyCalibration.v1.schema.json +819 -0
  143. package/schemas/decision/D17StudyDryRun.v1.schema.json +169 -0
  144. package/schemas/decision/D17StudyDryRun.v2.schema.json +239 -0
  145. package/schemas/decision/D17StudyGold.v1.schema.json +108 -0
  146. package/schemas/decision/D17StudyGuide.v1.schema.json +63 -0
  147. package/schemas/decision/D17StudyNativeTemplates.v1.schema.json +619 -0
  148. package/schemas/decision/D17StudyProtocol.v1.schema.json +241 -0
  149. package/schemas/decision/D17StudyReport.v1.schema.json +902 -0
  150. package/schemas/decision/D17StudyReport.v2.schema.json +1319 -0
  151. package/schemas/decision/D17StudyReview.v1.schema.json +175 -0
  152. package/schemas/decision/D17StudySplits.v1.schema.json +217 -0
  153. package/schemas/decision/D29Study.v1.schema.json +1903 -0
  154. package/schemas/decision/D29Study.v2.schema.json +1344 -0
  155. package/schemas/decision/D29Study.v3.schema.json +609 -0
  156. package/schemas/decision/D29Study.v4.schema.json +1133 -0
  157. package/schemas/decision/D29Study.v6.schema.json +1205 -0
  158. package/schemas/decision/D29Study.v7.schema.json +1471 -0
  159. package/schemas/decision/D29Study.v8.schema.json +671 -0
  160. package/schemas/decision/D29Study.v9.schema.json +778 -0
  161. package/schemas/decision/DecisionComparativeReplayAuditSample.v1.schema.json +137 -0
  162. package/schemas/decision/DecisionComparativeReplayCorpus.v1.schema.json +258 -0
  163. package/schemas/decision/DecisionComparativeReplayFreeze.v1.schema.json +74 -0
  164. package/schemas/decision/DecisionComparativeReplayGold.v1.schema.json +50 -0
  165. package/schemas/decision/DecisionComparativeReplayGoldAsset.v1.schema.json +37 -0
  166. package/schemas/decision/DecisionComparativeReplayIntegrity.v1.schema.json +81 -0
  167. package/schemas/decision/DecisionComparativeReplayMembers.v1.schema.json +52 -0
  168. package/schemas/decision/DecisionComparativeReplayPreregistration.v1.schema.json +113 -0
  169. package/schemas/decision/DecisionComparativeReplayReport.v1.schema.json +407 -0
  170. package/schemas/decision/DecisionComparativeReplayReviewPackets.v1.schema.json +260 -0
  171. package/schemas/decision/DecisionComparativeReplaySplit.v1.schema.json +84 -0
  172. package/schemas/decision/DecisionFeatureObservation.v1.schema.json +564 -0
  173. package/schemas/decision/DecisionFeatureSet.v1.schema.json +155 -0
  174. package/schemas/decision/DecisionInteropMapping.v1.schema.json +85 -0
  175. package/schemas/decision/DecisionIssueTriageEvaluationManifest.v1.schema.json +174 -0
  176. package/schemas/decision/DecisionIssueTriageEvaluationReport.v1.schema.json +280 -0
  177. package/schemas/decision/DecisionIssueTriagePilotPack.v1.schema.json +114 -0
  178. package/schemas/decision/DecisionRouteReceipt.v1.schema.json +155 -0
  179. package/schemas/decision/DecisionRoutingPolicy.v1.schema.json +132 -0
  180. package/schemas/decision/DecisionRoutingShadowReport.v1.schema.json +170 -0
  181. package/schemas/decision/DecisionRoutingTask.v1.schema.json +49 -0
  182. package/schemas/decision/DecisionRuleset.schema.json +8 -2
  183. package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +8 -2
  184. package/schemas/decision/DecisionSensitivityPlan.v1.schema.json +191 -0
  185. package/schemas/decision/DecisionSensitivityReport.v1.schema.json +163 -0
  186. package/schemas/decision/HeldoutApproval.v1.schema.json +312 -0
  187. package/schemas/decision/HeldoutAttempt.v1.schema.json +211 -0
  188. package/schemas/decision/HeldoutBaseline.v1.schema.json +90 -0
  189. package/schemas/decision/HeldoutCalibrationPhase.v1.schema.json +87 -0
  190. package/schemas/decision/HeldoutCorpus.v1.schema.json +167 -0
  191. package/schemas/decision/HeldoutEvent.v1.schema.json +41 -0
  192. package/schemas/decision/HeldoutFrozen.v1.schema.json +142 -0
  193. package/schemas/decision/HeldoutPreregistration.v1.schema.json +222 -0
  194. package/schemas/decision/HeldoutSpendEvent.v1.schema.json +122 -0
  195. package/schemas/decision/HeldoutSpendHead.v1.schema.json +25 -0
  196. package/schemas/decision/HeldoutSpendHead.v2.schema.json +46 -0
  197. package/schemas/decision/HeldoutSummary.v1.schema.json +101 -0
  198. package/schemas/decision/OpaDecisionLogExport.v1.schema.json +65 -0
  199. package/schemas/decision/PreprocessedEvidence.v1.schema.json +306 -0
  200. package/schemas/decision/RulesetResult.v1alpha2.schema.json +175 -0
  201. package/schemas/decision/SdlcEvidenceScreening.v1.schema.json +499 -0
  202. package/schemas/decision/SdlcScreeningPreregistration.v1.schema.json +132 -0
  203. package/schemas/decision/SdlcScreeningPreregistration.v2.schema.json +138 -0
  204. package/schemas/decision/SdlcScreeningRelease.v1.schema.json +654 -0
  205. package/schemas/decision/SdlcScreeningRelease.v2.schema.json +654 -0
  206. package/tools/agents/deploy-agents.mjs +1 -1
  207. package/tools/agents/providers/base.mjs +26 -7
  208. package/tools/agents/providers/factory.mjs +10 -2
  209. package/tools/agents/providers/muse-hooks.mjs +1 -29
  210. package/tools/agents/providers/muse.mjs +6 -3
@@ -0,0 +1,653 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { SaxesParser } from 'saxes';
3
+ import { canonicalJson } from '../security/artifact-trust.js';
4
+ import { composeRuleset } from './compose.js';
5
+ import { DECISION_API_VERSION_STRUCTURED } from './types.js';
6
+ import { artifactDigest, resolveJsonPointer } from './validate.js';
7
+ export const DECISION_INTEROP_PROFILE_VERSION = 'decision-interop.aiwg.io/v1';
8
+ export const DECISION_INTEROP_DMN_VERSION = '1.6';
9
+ export const DECISION_INTEROP_DMN_NAMESPACE = 'https://www.omg.org/spec/DMN/20240513/MODEL/';
10
+ export class DecisionInteropError extends Error {
11
+ code;
12
+ constructor(code, message) {
13
+ super(message);
14
+ this.code = code;
15
+ this.name = 'DecisionInteropError';
16
+ }
17
+ }
18
+ const DEFAULT_BOUNDS = {
19
+ maxBytes: 256_000,
20
+ maxElements: 4_000,
21
+ maxDepth: 32,
22
+ maxRules: 512,
23
+ maxInputs: 32,
24
+ maxOutputs: 8,
25
+ maxTextBytes: 64_000,
26
+ maxParseMs: 1_000,
27
+ };
28
+ const FORBIDDEN_XML = [
29
+ { code: 'dmn-doctype', pattern: /<!DOCTYPE/i },
30
+ { code: 'dmn-entity', pattern: /<!ENTITY|&(?!(?:amp|lt|gt|quot|apos);)[a-z][a-z0-9_.:-]*;/i },
31
+ { code: 'dmn-xinclude', pattern: /(?:xi:include|xinclude)/i },
32
+ { code: 'dmn-remote-reference', pattern: /\b(?:schemaLocation|href|src|importLocation)\s*=\s*["'](?:https?:|file:|ftp:)/i },
33
+ { code: 'dmn-path-traversal', pattern: /\b(?:href|src|importLocation)\s*=\s*["'][^"']*(?:\.\.|%2e%2e)/i },
34
+ { code: 'dmn-script-extension', pattern: /<(?:\w+:)?(?:script|literalExpression|context|functionDefinition|invocation)\b/i },
35
+ ];
36
+ const FORBIDDEN_CONTROL_FIELDS = new Set([
37
+ 'provider', 'credentialRef', 'credential', 'egressPolicy', 'model', 'calibrationRecord',
38
+ 'calibration', 'executor', 'backend', 'acceptance', 'artifactPin', 'actionAuthorization',
39
+ ]);
40
+ const SENSITIVE_LOG_KEY = /(?:raw|body|secret|token|credential|private|locator|state|authorization|password|key)/iu;
41
+ const SENSITIVE_LOG_VALUE = /(?:bearer\s+\S+|secret|token|file:\/\/|\/home\/|private|[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,})/iu;
42
+ const SUPPORTED_DMN_ELEMENTS = new Set(['definitions', 'decision', 'informationRequirement', 'requiredDecision', 'decisionTable', 'input', 'inputExpression', 'output', 'rule', 'inputEntry', 'outputEntry', 'text']);
43
+ export function decisionInteropDefaultBounds(overrides = {}) {
44
+ return { ...DEFAULT_BOUNDS, ...overrides };
45
+ }
46
+ export function importDmnDecisionTable(xml, options = {}) {
47
+ const bounds = decisionInteropDefaultBounds(options.bounds);
48
+ const bytes = Buffer.byteLength(xml, 'utf8');
49
+ if (bytes > bounds.maxBytes)
50
+ throw new DecisionInteropError('dmn-input-too-large', `DMN input exceeds ${bounds.maxBytes} bytes`);
51
+ for (const forbidden of FORBIDDEN_XML) {
52
+ if (forbidden.pattern.test(xml))
53
+ throw new DecisionInteropError(forbidden.code, `DMN import rejected by ${forbidden.code}`);
54
+ }
55
+ const parsed = parseBoundedXml(xml, bounds);
56
+ const table = extractDmnTable(parsed.root, bounds, options.externalDecisionPins ?? {});
57
+ const ruleset = tableToRuleset(table);
58
+ const profile = {
59
+ profileVersion: DECISION_INTEROP_PROFILE_VERSION,
60
+ dmnVersion: DECISION_INTEROP_DMN_VERSION,
61
+ namespace: DECISION_INTEROP_DMN_NAMESPACE,
62
+ hitPolicy: table.hitPolicy,
63
+ aggregation: table.aggregation,
64
+ ruleOrder: table.rules.map(rule => rule.id),
65
+ supportedUnaryTests: ['dash', 'literal', 'eq', 'ne', 'lt', 'lte', 'gt', 'gte'],
66
+ unsupportedConstructs: [],
67
+ extensions: table.deferOnConflict ? ['aiwg:order', 'aiwg:defer-on-conflict'] : ['aiwg:order'],
68
+ };
69
+ const source = {
70
+ format: 'dmn-xml',
71
+ version: DECISION_INTEROP_DMN_VERSION,
72
+ origin: options.origin ?? 'inline',
73
+ digest: sha256(xml),
74
+ signature: { state: options.signatureState ?? (options.trustedSource ? 'untrusted' : 'not-provided'), ...(options.signatureKeyId ? { keyId: options.signatureKeyId } : {}) },
75
+ parser: { id: 'aiwg-decision-interop', version: DECISION_INTEROP_PROFILE_VERSION },
76
+ bounds: { ...bounds, ...parsed.report, rules: table.rules.length, inputs: table.inputs.length, outputs: table.outputs.length },
77
+ };
78
+ return {
79
+ apiVersion: DECISION_INTEROP_PROFILE_VERSION,
80
+ kind: 'DecisionInteropMapping',
81
+ metadata: {
82
+ id: `${table.definitionsId}.${table.decisionId}`,
83
+ version: '1.0.0',
84
+ description: `Dry-run DMN import for ${table.decisionName}`,
85
+ },
86
+ spec: {
87
+ source,
88
+ dryRun: true,
89
+ activation: 'requires-review-publish',
90
+ profile,
91
+ normalizedRuleset: ruleset,
92
+ normalizedDigest: artifactDigest(ruleset),
93
+ warnings: options.signatureState === 'verified' ? [] : ['source signature was not verified; activation requires ordinary review/publish authorization'],
94
+ errors: [],
95
+ roundTripStatus: 'not-run',
96
+ },
97
+ };
98
+ }
99
+ export function exportDmnDecisionTable(mapping) {
100
+ const { normalizedRuleset: ruleset, profile } = mapping.spec;
101
+ const inputs = collectRuleInputs(ruleset);
102
+ const outputNames = outputNamesFor(ruleset);
103
+ const hitPolicyAttrs = [
104
+ `hitPolicy="${escapeXml(profile.hitPolicy)}"`,
105
+ profile.aggregation && profile.aggregation !== 'LIST' ? `aggregation="${escapeXml(profile.aggregation)}"` : '',
106
+ profile.extensions.includes('aiwg:defer-on-conflict') ? 'aiwg:conflict="defer-on-conflict"' : '',
107
+ ].filter(Boolean).join(' ');
108
+ const rows = ruleset.spec.rules.map((rule, index) => {
109
+ const inputEntries = inputs.map(input => ` <inputEntry><text>${escapeXml(unaryTestFor(rule.when, input.pointer))}</text></inputEntry>`).join('\n');
110
+ const outcome = outputNames.map(name => ` <outputEntry><text>${escapeXml(outputEntryFor(rule.outcome, name, outputNames.length))}</text></outputEntry>`).join('\n');
111
+ return ` <rule id="${escapeXml(rule.id)}" aiwg:order="${index + 1}">
112
+ ${inputEntries}
113
+ ${outcome}
114
+ </rule>`;
115
+ }).join('\n');
116
+ const baseId = ncName(ruleset.metadata.id);
117
+ const dependencyPins = Object.fromEntries(ruleset.spec.evaluations.map(evaluation => [evaluation.alias, evaluation.decision]));
118
+ const dependencyDecisions = ruleset.spec.evaluations
119
+ .map(evaluation => ` <decision id="${escapeXml(ncName(evaluation.alias))}" name="${escapeXml(evaluation.alias)}" />`)
120
+ .join('\n');
121
+ const dependencyRequirements = ruleset.spec.evaluations
122
+ .map(evaluation => ` <informationRequirement><requiredDecision href="#${escapeXml(ncName(evaluation.alias))}" /></informationRequirement>`)
123
+ .join('\n');
124
+ const rendered = `<?xml version="1.0" encoding="UTF-8"?>
125
+ <definitions xmlns="${DECISION_INTEROP_DMN_NAMESPACE}" xmlns:aiwg="https://aiwg.io/spec/decision-interop/v1" id="${escapeXml(`${baseId}-definitions`)}" name="${escapeXml(ruleset.metadata.description)}" namespace="https://aiwg.io/decision/interop" expressionLanguage="https://aiwg.io/spec/decision-interop/v1/json-pointer">
126
+ ${dependencyDecisions ? `${dependencyDecisions}\n` : ''} <decision id="${escapeXml(baseId)}" name="${escapeXml(ruleset.metadata.description)}">
127
+ ${dependencyRequirements ? `${dependencyRequirements}\n` : ''} <decisionTable id="${escapeXml(`${baseId}-table`)}" ${hitPolicyAttrs}>
128
+ ${inputs.map(input => ` <input id="${escapeXml(input.id)}"><inputExpression typeRef="${escapeXml(input.typeRef)}"><text>${escapeXml(input.pointer)}</text></inputExpression></input>`).join('\n')}
129
+ ${outputNames.map(name => ` <output id="${escapeXml(`out-${name}`)}" name="${escapeXml(name)}" />`).join('\n')}
130
+ ${rows}
131
+ </decisionTable>
132
+ </decision>
133
+ </definitions>
134
+ `;
135
+ importDmnDecisionTable(rendered, { origin: 'round-trip-export', trustedSource: true, externalDecisionPins: dependencyPins });
136
+ return rendered;
137
+ }
138
+ export function evaluateDmnProfile(mapping, input, evaluations = {}) {
139
+ return composeRuleset(mapping.spec.normalizedRuleset, input, evaluations);
140
+ }
141
+ export function exportOpaDecisionLog(result, envelope) {
142
+ validateOpaInteropEnvelope(envelope);
143
+ const redaction = { removed: [] };
144
+ const sanitizedInput = projectOpaInput(envelope.input, envelope.inputProjectionAllowlist ?? [], redaction);
145
+ const actualResult = (Object.hasOwn(result.spec, 'outcome') ? result.spec.outcome : result.spec.status);
146
+ if (envelope.result !== undefined && canonicalJson(envelope.result) !== canonicalJson(actualResult)) {
147
+ throw new DecisionInteropError('opa-result-mismatch', 'OPA decision log result must match the AIWG RulesetResult outcome');
148
+ }
149
+ const sanitizedResult = sanitizeJson(actualResult, '$.result', redaction);
150
+ const lineage = [
151
+ { kind: 'ruleset', ...result.spec.ruleset },
152
+ { kind: 'binding', ...result.spec.binding },
153
+ { kind: 'policy-bundle', id: envelope.bundle.name, version: envelope.bundle.revision, digest: envelope.bundle.digest ?? sha256(envelope.bundle) },
154
+ ];
155
+ return {
156
+ schemaVersion: DECISION_INTEROP_PROFILE_VERSION,
157
+ decision_id: envelope.decisionId,
158
+ ...(envelope.traceId ? { trace_id: envelope.traceId } : {}),
159
+ ...(envelope.spanId ? { span_id: envelope.spanId } : {}),
160
+ path: envelope.policyPath,
161
+ bundles: { [envelope.bundle.name]: { revision: envelope.bundle.revision, ...(envelope.bundle.digest ? { digest: envelope.bundle.digest } : {}) } },
162
+ result: sanitizedResult,
163
+ input: sanitizedInput,
164
+ ...(envelope.metrics ? { metrics: structuredClone(envelope.metrics) } : {}),
165
+ revisionLineage: lineage,
166
+ redaction: { mode: 'default-deny-sensitive', removed: redaction.removed.sort() },
167
+ enforcement: { separated: true, contract: 'decision-only-action-requires-aiwg-authorization' },
168
+ };
169
+ }
170
+ export function validateOpaInteropEnvelope(envelope) {
171
+ assertNoControlOverride(envelope, '$');
172
+ if (envelope.profileVersion !== DECISION_INTEROP_PROFILE_VERSION) {
173
+ throw new DecisionInteropError('opa-profile-version', 'OPA interop envelope has an unsupported profile version');
174
+ }
175
+ if (!envelope.bundle.revision || !envelope.decisionId || !envelope.policyPath) {
176
+ throw new DecisionInteropError('opa-missing-identity', 'OPA interop envelope requires bundle revision, decision ID, and policy path');
177
+ }
178
+ }
179
+ function parseBoundedXml(xml, bounds) {
180
+ const started = performance.now();
181
+ const stack = [];
182
+ let root = null;
183
+ let elements = 0;
184
+ let maxObservedDepth = 0;
185
+ let textBytes = 0;
186
+ const parser = new SaxesParser({ xmlns: true, fragment: false, resolvePrefix: (prefix) => prefix === 'xml' ? 'http://www.w3.org/XML/1998/namespace' : undefined });
187
+ parser.on('opentag', tag => {
188
+ assertParseTime();
189
+ elements += 1;
190
+ if (elements > bounds.maxElements)
191
+ throw new DecisionInteropError('dmn-element-bound', `DMN XML exceeds ${bounds.maxElements} elements`);
192
+ maxObservedDepth = Math.max(maxObservedDepth, stack.length + 1);
193
+ if (maxObservedDepth > bounds.maxDepth)
194
+ throw new DecisionInteropError('dmn-depth-bound', `DMN XML exceeds depth ${bounds.maxDepth}`);
195
+ const element = elementFromTag(tag);
196
+ if (!allowedNamespace(element))
197
+ throw new DecisionInteropError('dmn-namespace', 'Unsupported DMN namespace');
198
+ if (!SUPPORTED_DMN_ELEMENTS.has(element.local))
199
+ throw new DecisionInteropError('dmn-unsupported-element', 'Unsupported DMN element');
200
+ stack.at(-1)?.children.push(element);
201
+ stack.push(element);
202
+ });
203
+ parser.on('text', text => {
204
+ assertParseTime();
205
+ appendText(text);
206
+ });
207
+ parser.on('cdata', text => {
208
+ assertParseTime();
209
+ appendText(text);
210
+ });
211
+ parser.on('closetag', () => {
212
+ assertParseTime();
213
+ const closed = stack.pop();
214
+ if (!stack.length && closed)
215
+ root = closed;
216
+ });
217
+ parser.on('error', error => {
218
+ throw new DecisionInteropError('dmn-xml-parse', sanitizeXmlParseError(error.message));
219
+ });
220
+ const chunkSize = 8192;
221
+ for (let offset = 0; offset < xml.length; offset += chunkSize) {
222
+ assertParseTime();
223
+ parser.write(xml.slice(offset, offset + chunkSize));
224
+ }
225
+ assertParseTime();
226
+ parser.close();
227
+ assertParseTime();
228
+ if (!root)
229
+ throw new DecisionInteropError('dmn-empty', 'DMN XML has no root element');
230
+ const parseTimeMs = performance.now() - started;
231
+ return { root, report: { bytes: Buffer.byteLength(xml, 'utf8'), elements, maxObservedDepth, parseTimeMs } };
232
+ function assertParseTime() {
233
+ if (performance.now() - started > bounds.maxParseMs)
234
+ throw new DecisionInteropError('dmn-parse-time-bound', `DMN XML parse exceeded ${bounds.maxParseMs}ms`);
235
+ }
236
+ function appendText(text) {
237
+ if (!stack.length)
238
+ return;
239
+ textBytes += Buffer.byteLength(text, 'utf8');
240
+ if (textBytes > bounds.maxTextBytes)
241
+ throw new DecisionInteropError('dmn-text-bound', `DMN XML text exceeds ${bounds.maxTextBytes} bytes`);
242
+ stack[stack.length - 1].text += text;
243
+ }
244
+ }
245
+ function sanitizeXmlParseError(message) {
246
+ const location = /^(\d+:\d+):/u.exec(message)?.[1];
247
+ return location ? `${location}: DMN XML parse error` : 'DMN XML parse error';
248
+ }
249
+ function elementFromTag(tag) {
250
+ const attributes = {};
251
+ for (const [name, attr] of Object.entries(tag.attributes)) {
252
+ const value = typeof attr === 'string' ? attr : String(attr.value ?? '');
253
+ attributes[name] = value;
254
+ const uri = typeof attr === 'string' ? '' : attr.uri;
255
+ if (uri && uri !== DECISION_INTEROP_DMN_NAMESPACE && uri !== 'https://aiwg.io/spec/decision-interop/v1'
256
+ && uri !== 'http://www.w3.org/2000/xmlns/') {
257
+ throw new DecisionInteropError('dmn-namespace', 'Unsupported namespace on attribute');
258
+ }
259
+ }
260
+ return { local: tag.local ?? tag.name, uri: tag.uri ?? '', attributes, text: '', children: [] };
261
+ }
262
+ function allowedNamespace(element) {
263
+ return element.uri === DECISION_INTEROP_DMN_NAMESPACE;
264
+ }
265
+ function extractDmnTable(root, bounds, externalDecisionPins) {
266
+ if (root.local !== 'definitions')
267
+ throw new DecisionInteropError('dmn-root', 'DMN root must be definitions');
268
+ const decisions = root.children.filter(child => child.local === 'decision');
269
+ if (decisions.length === 0)
270
+ throw new DecisionInteropError('dmn-decision-count', 'DMN profile requires at least one decision');
271
+ assertUnique(decisions.map(decision => decision.attributes.id ?? ''), 'DMN decision IDs');
272
+ const tableDecisions = decisions.filter(decision => decision.children.some(child => child.local === 'decisionTable'));
273
+ if (tableDecisions.length !== 1)
274
+ throw new DecisionInteropError('dmn-table-count', 'DMN profile supports exactly one decisionTable');
275
+ const decision = tableDecisions[0];
276
+ const tables = decision.children.filter(child => child.local === 'decisionTable');
277
+ if (tables.length !== 1)
278
+ throw new DecisionInteropError('dmn-table-count', 'DMN profile supports exactly one decisionTable');
279
+ const table = tables[0];
280
+ const hitPolicy = parseHitPolicy(table.attributes.hitPolicy ?? 'UNIQUE');
281
+ const aggregation = parseAggregation(table.attributes.aggregation, hitPolicy);
282
+ const inputs = table.children.filter(child => child.local === 'input').map(input => {
283
+ const expr = input.children.find(child => child.local === 'inputExpression');
284
+ const text = expr?.children.find(child => child.local === 'text')?.text.trim();
285
+ if (text === undefined || (text !== '' && !text.startsWith('/')))
286
+ throw new DecisionInteropError('dmn-input-expression', 'DMN inputExpression must be a JSON Pointer');
287
+ return { id: input.attributes.id ?? `input-${text || 'root'}`, pointer: text };
288
+ });
289
+ const outputs = table.children.filter(child => child.local === 'output').map((output, index) => ({
290
+ id: output.attributes.id ?? `output-${index + 1}`,
291
+ name: output.attributes.name ?? `out${index + 1}`,
292
+ }));
293
+ const rules = table.children.filter(child => child.local === 'rule').map((rule, index) => ({
294
+ id: rule.attributes.id ?? `rule-${index + 1}`,
295
+ order: orderOf(rule),
296
+ inputs: rule.children.filter(child => child.local === 'inputEntry').map(entry => ({ text: entry.children.find(child => child.local === 'text')?.text.trim() ?? '' })),
297
+ outputs: rule.children.filter(child => child.local === 'outputEntry').map(entry => ({ text: entry.children.find(child => child.local === 'text')?.text.trim() ?? '' })),
298
+ }));
299
+ if (inputs.length === 0 || inputs.length > bounds.maxInputs)
300
+ throw new DecisionInteropError('dmn-input-bound', 'DMN input count is outside the supported profile');
301
+ if (outputs.length === 0 || outputs.length > bounds.maxOutputs)
302
+ throw new DecisionInteropError('dmn-output-bound', 'DMN output count is outside the supported profile');
303
+ if (rules.length === 0 || rules.length > bounds.maxRules)
304
+ throw new DecisionInteropError('dmn-rule-bound', 'DMN rule count is outside the supported profile');
305
+ const dependencies = dependenciesFor(decision, decisions, externalDecisionPins);
306
+ assertUnique(inputs.map(input => input.id), 'DMN input IDs');
307
+ assertUnique(outputs.map(output => output.id), 'DMN output IDs');
308
+ assertUnique(outputs.map(output => output.name), 'DMN output names');
309
+ assertUnique(rules.map(rule => rule.id), 'DMN rule IDs');
310
+ for (const rule of rules) {
311
+ if (rule.inputs.length !== inputs.length || rule.outputs.length !== outputs.length)
312
+ throw new DecisionInteropError('dmn-rule-shape', 'Each DMN rule must have one entry per declared input and output');
313
+ }
314
+ if ((hitPolicy === 'FIRST' || hitPolicy === 'RULE ORDER' || hitPolicy === 'COLLECT') && rules.some(rule => rule.order === null)) {
315
+ throw new DecisionInteropError('dmn-ambiguous-order', 'DMN hit policy requires explicit aiwg:order on every rule');
316
+ }
317
+ const ordered = rules.slice().sort((left, right) => (left.order ?? 0) - (right.order ?? 0) || left.id.localeCompare(right.id));
318
+ if (new Set(ordered.map(rule => rule.order).filter(order => order !== null)).size !== ordered.filter(rule => rule.order !== null).length) {
319
+ throw new DecisionInteropError('dmn-ambiguous-order', 'aiwg:order values must be unique');
320
+ }
321
+ return {
322
+ definitionsId: root.attributes.id ?? 'definitions',
323
+ decisionId: decision.attributes.id ?? 'decision',
324
+ decisionName: decision.attributes.name ?? decision.attributes.id ?? 'decision',
325
+ tableId: table.attributes.id ?? 'decisionTable',
326
+ hitPolicy,
327
+ aggregation,
328
+ inputs,
329
+ outputs,
330
+ rules: ordered,
331
+ dependencies,
332
+ deferOnConflict: table.attributes['aiwg:conflict'] === 'defer-on-conflict' || table.attributes.conflict === 'defer-on-conflict',
333
+ };
334
+ }
335
+ function tableToRuleset(table) {
336
+ const outputSchema = outputSchemaFor(table);
337
+ return {
338
+ apiVersion: DECISION_API_VERSION_STRUCTURED,
339
+ kind: 'DecisionRuleset',
340
+ metadata: { id: table.decisionId, version: '1.0.0', description: table.decisionName },
341
+ spec: {
342
+ purpose: `Imported DMN ${table.tableId} via ${DECISION_INTEROP_PROFILE_VERSION}; dmn-hit-policy=${table.hitPolicy}`,
343
+ inputSchema: { type: 'object' },
344
+ evaluations: table.dependencies.map(dependency => ({
345
+ alias: dependency.alias,
346
+ decision: dependency.decision,
347
+ inputPointer: '',
348
+ })),
349
+ rules: table.rules.map((rule, index) => ({
350
+ id: rule.id,
351
+ priority: table.rules.length - index,
352
+ when: predicateForRule(table, rule),
353
+ outcome: outcomeForRule(table, rule),
354
+ })),
355
+ composition: compositionFor(table),
356
+ conflict: table.deferOnConflict ? 'review' : 'error',
357
+ defaultOutcome: defaultOutcomeFor(table),
358
+ failureOutcome: defaultOutcomeFor(table),
359
+ outputSchema,
360
+ },
361
+ };
362
+ }
363
+ function predicateForRule(table, rule) {
364
+ const predicates = [
365
+ ...table.dependencies.map(dependency => ({ op: 'exists', left: { source: 'decision', alias: dependency.alias, pointer: '' } })),
366
+ ...rule.inputs.map((entry, index) => predicateForUnaryTest(table.inputs[index].pointer, entry.text)),
367
+ ];
368
+ return predicates.length === 1 ? predicates[0] : { all: predicates };
369
+ }
370
+ function predicateForUnaryTest(pointer, text) {
371
+ if (text === '-' || text === '')
372
+ return { op: 'exists', left: { source: 'input', pointer: '' } };
373
+ const match = text.match(/^(<=|>=|<|>|=|!=)?\s*(.+)$/u);
374
+ if (!match)
375
+ throw new DecisionInteropError('dmn-feel-unsupported', 'Unsupported unary test');
376
+ const operator = (match[1] ?? '=');
377
+ const op = { '<': 'lt', '<=': 'lte', '>': 'gt', '>=': 'gte', '=': 'eq', '!=': 'ne' }[operator];
378
+ return { op, left: { source: 'input', pointer }, right: parseLiteral(match[2].trim()) };
379
+ }
380
+ function outcomeForRule(table, rule) {
381
+ if (rule.outputs.length === 1)
382
+ return parseLiteral(rule.outputs[0].text);
383
+ return Object.fromEntries(rule.outputs.map((entry, index) => [table.outputs[index].name, parseLiteral(entry.text)]));
384
+ }
385
+ function parseLiteral(text) {
386
+ if (/^".*"$|^-?\d+(?:\.\d+)?$|^(?:true|false|null)$/u.test(text))
387
+ return JSON.parse(text);
388
+ if (/^[A-Za-z0-9_.:-]+$/u.test(text))
389
+ return text;
390
+ throw new DecisionInteropError('dmn-feel-unsupported', 'Unsupported FEEL/data literal');
391
+ }
392
+ function outputSchemaFor(table) {
393
+ const scalar = table.outputs.length === 1 ? {} : {
394
+ type: 'object',
395
+ properties: Object.fromEntries(table.outputs.map(output => [output.name, {}])),
396
+ required: table.outputs.map(output => output.name),
397
+ additionalProperties: false,
398
+ };
399
+ if (table.hitPolicy === 'RULE ORDER' || (table.hitPolicy === 'COLLECT' && (!table.aggregation || table.aggregation === 'LIST'))) {
400
+ return { type: 'array', items: scalar };
401
+ }
402
+ if (table.hitPolicy === 'COLLECT' && table.aggregation) {
403
+ if (table.aggregation === 'COUNT')
404
+ return { type: 'integer', minimum: 0 };
405
+ if (table.aggregation === 'SUM')
406
+ return { type: 'number' };
407
+ return { type: ['number', 'null'] };
408
+ }
409
+ return scalar;
410
+ }
411
+ function defaultOutcomeFor(table) {
412
+ if (table.hitPolicy === 'RULE ORDER' || (table.hitPolicy === 'COLLECT' && (!table.aggregation || table.aggregation === 'LIST')))
413
+ return [];
414
+ if (table.hitPolicy === 'COLLECT' && table.aggregation === 'COUNT')
415
+ return 0;
416
+ if (table.hitPolicy === 'COLLECT' && table.aggregation === 'SUM')
417
+ return 0;
418
+ return null;
419
+ }
420
+ function compositionFor(table) {
421
+ if (table.hitPolicy === 'UNIQUE')
422
+ return 'unique';
423
+ if (table.hitPolicy === 'ANY')
424
+ return 'any';
425
+ if (table.hitPolicy === 'RULE ORDER')
426
+ return 'collect';
427
+ if (table.hitPolicy === 'COLLECT') {
428
+ if (!table.aggregation || table.aggregation === 'LIST')
429
+ return 'collect';
430
+ return `collect-${table.aggregation.toLowerCase()}`;
431
+ }
432
+ return 'first-match';
433
+ }
434
+ function dependenciesFor(decision, decisions, externalDecisionPins) {
435
+ const byId = new Map();
436
+ for (const item of decisions) {
437
+ const id = item.attributes.id;
438
+ if (id)
439
+ byId.set(id, item);
440
+ }
441
+ const tableId = decision.attributes.id;
442
+ if (!tableId)
443
+ throw new DecisionInteropError('dmn-dependency-reference', 'DMN table decision must have an id when dependencies are present');
444
+ const direct = requiredDecisionIds(decision, byId, tableId);
445
+ const ordered = [];
446
+ const visited = new Set();
447
+ const visiting = new Set([tableId]);
448
+ const visit = (id) => {
449
+ if (visiting.has(id))
450
+ throw new DecisionInteropError('dmn-cyclic-dependency', 'DMN decision dependencies must be acyclic');
451
+ if (visited.has(id))
452
+ return;
453
+ const node = byId.get(id);
454
+ if (!node)
455
+ throw new DecisionInteropError('dmn-dependency-reference', 'DMN requiredDecision target is not declared');
456
+ if (node.children.some(child => child.local === 'decisionTable')) {
457
+ throw new DecisionInteropError('dmn-dependency-executable', 'DMN profile supports dependency decisions as external evidence only');
458
+ }
459
+ visiting.add(id);
460
+ for (const child of requiredDecisionIds(node, byId, id))
461
+ visit(child);
462
+ visiting.delete(id);
463
+ visited.add(id);
464
+ ordered.push(id);
465
+ };
466
+ for (const id of direct)
467
+ visit(id);
468
+ assertUnique(ordered, 'DMN decision dependencies');
469
+ return ordered.map(alias => {
470
+ const pin = externalDecisionPins[alias];
471
+ if (!pin)
472
+ throw new DecisionInteropError('dmn-dependency-pin-required', 'DMN dependency requires a caller-supplied external decision pin');
473
+ if (pin.id !== alias)
474
+ throw new DecisionInteropError('dmn-dependency-pin-mismatch', 'DMN dependency pin id must match the decision id');
475
+ return { alias, decision: structuredClone(pin) };
476
+ });
477
+ }
478
+ function requiredDecisionIds(decision, decisions, ownId) {
479
+ const refs = decision.children
480
+ .filter(child => child.local === 'informationRequirement')
481
+ .flatMap(requirement => requirement.children.filter(child => child.local === 'requiredDecision'))
482
+ .map(required => required.attributes.href ?? '');
483
+ const dependencies = refs.map(ref => {
484
+ if (!ref.startsWith('#') || ref.length <= 1 || ref.includes('/') || ref.includes(':')) {
485
+ throw new DecisionInteropError('dmn-dependency-reference', 'DMN requiredDecision must use a local decision href');
486
+ }
487
+ const id = ref.slice(1);
488
+ if (id === ownId)
489
+ throw new DecisionInteropError('dmn-cyclic-dependency', 'DMN decision cannot require itself');
490
+ if (!decisions.has(id))
491
+ throw new DecisionInteropError('dmn-dependency-reference', 'DMN requiredDecision target is not declared');
492
+ return id;
493
+ });
494
+ assertUnique(dependencies, 'DMN decision dependencies');
495
+ return dependencies;
496
+ }
497
+ function parseHitPolicy(value) {
498
+ const normalized = value.trim().toUpperCase().replace(/_/gu, ' ');
499
+ if (['UNIQUE', 'ANY', 'FIRST', 'RULE ORDER', 'COLLECT'].includes(normalized))
500
+ return normalized;
501
+ throw new DecisionInteropError('dmn-hit-policy', 'Unsupported DMN hit policy');
502
+ }
503
+ function parseAggregation(value, hitPolicy) {
504
+ if (!value)
505
+ return hitPolicy === 'COLLECT' ? 'LIST' : null;
506
+ if (hitPolicy !== 'COLLECT')
507
+ throw new DecisionInteropError('dmn-aggregation', 'DMN aggregation is supported only with COLLECT');
508
+ const normalized = value.trim().toUpperCase();
509
+ if (['SUM', 'MIN', 'MAX', 'COUNT'].includes(normalized))
510
+ return normalized;
511
+ throw new DecisionInteropError('dmn-aggregation', 'Unsupported DMN aggregation');
512
+ }
513
+ function orderOf(rule) {
514
+ const raw = rule.attributes['aiwg:order'] ?? rule.attributes.order;
515
+ if (raw === undefined)
516
+ return null;
517
+ const parsed = Number(raw);
518
+ if (!Number.isInteger(parsed) || parsed <= 0)
519
+ throw new DecisionInteropError('dmn-ambiguous-order', 'aiwg:order must be a positive integer');
520
+ return parsed;
521
+ }
522
+ function collectRuleInputs(ruleset) {
523
+ const pointers = new Set();
524
+ let hasRootWildcard = false;
525
+ const visit = (predicate) => {
526
+ if ('all' in predicate)
527
+ predicate.all.forEach(visit);
528
+ else if ('any' in predicate)
529
+ predicate.any.forEach(visit);
530
+ else if ('not' in predicate)
531
+ visit(predicate.not);
532
+ else if (predicate.left.source === 'input') {
533
+ if (predicate.left.pointer === '')
534
+ hasRootWildcard = true;
535
+ else
536
+ pointers.add(predicate.left.pointer);
537
+ }
538
+ };
539
+ ruleset.spec.rules.forEach(rule => visit(rule.when));
540
+ if (pointers.size === 0 && hasRootWildcard)
541
+ pointers.add('');
542
+ return [...pointers].sort().map((pointer, index) => ({ id: `input-${index + 1}`, pointer, typeRef: 'Any' }));
543
+ }
544
+ function outputNamesFor(ruleset) {
545
+ const objectOutcome = ruleset.spec.rules.map(rule => rule.outcome).find(value => value && typeof value === 'object' && !Array.isArray(value));
546
+ return objectOutcome && typeof objectOutcome === 'object' && !Array.isArray(objectOutcome) ? Object.keys(objectOutcome).sort() : ['outcome'];
547
+ }
548
+ function unaryTestFor(predicate, pointer) {
549
+ if ('all' in predicate) {
550
+ const found = predicate.all.find(item => !('all' in item) && !('any' in item) && !('not' in item) && item.left.source === 'input' && item.left.pointer === pointer);
551
+ return found ? unaryTestFor(found, pointer) : '-';
552
+ }
553
+ if ('any' in predicate || 'not' in predicate)
554
+ throw new DecisionInteropError('dmn-export-unsupported', 'DMN export supports conjunctive rule predicates only');
555
+ if (predicate.left.pointer !== pointer)
556
+ return '-';
557
+ if (predicate.op === 'exists')
558
+ return '-';
559
+ const op = { eq: '=', ne: '!=', lt: '<', lte: '<=', gt: '>', gte: '>=' }[predicate.op];
560
+ return `${op} ${JSON.stringify(predicate.right)}`;
561
+ }
562
+ function outputEntryFor(outcome, name, count) {
563
+ if (count === 1)
564
+ return JSON.stringify(outcome);
565
+ if (!outcome || typeof outcome !== 'object' || Array.isArray(outcome))
566
+ throw new DecisionInteropError('dmn-export-output', 'Compound output requires object rule outcomes');
567
+ return JSON.stringify(outcome[name]);
568
+ }
569
+ function sanitizeJson(value, path, redaction) {
570
+ if (isSensitiveValue(value)) {
571
+ redaction.removed.push(path);
572
+ return '[redacted]';
573
+ }
574
+ if (Array.isArray(value))
575
+ return value.map((item, index) => sanitizeJson(item, `${path}[${index}]`, redaction));
576
+ if (value && typeof value === 'object') {
577
+ const result = {};
578
+ for (const [key, child] of Object.entries(value)) {
579
+ if (SENSITIVE_LOG_KEY.test(key) || isSensitiveValue(child)) {
580
+ redaction.removed.push(`${path}.${key}`);
581
+ continue;
582
+ }
583
+ result[key] = sanitizeJson(child, `${path}.${key}`, redaction);
584
+ }
585
+ return result;
586
+ }
587
+ return value;
588
+ }
589
+ function projectOpaInput(input, allowlist, redaction) {
590
+ if (allowlist.length === 0) {
591
+ redaction.removed.push('$.input');
592
+ return {};
593
+ }
594
+ const output = {};
595
+ for (const pointer of allowlist) {
596
+ if (!pointer.startsWith('/'))
597
+ throw new DecisionInteropError('opa-input-projection', 'OPA input projection allowlist entries must be JSON Pointers');
598
+ const resolved = resolveJsonPointer(input, pointer);
599
+ if (!resolved.found)
600
+ continue;
601
+ setProjectedValue(output, pointer.slice(1).split('/').map(part => part.replace(/~1/gu, '/').replace(/~0/gu, '~')), sanitizeJson(resolved.value, `$.input${pointer}`, redaction));
602
+ }
603
+ return output;
604
+ }
605
+ function setProjectedValue(target, path, value) {
606
+ let cursor = target;
607
+ path.forEach((segment, index) => {
608
+ if (index === path.length - 1) {
609
+ cursor[segment] = value;
610
+ return;
611
+ }
612
+ const existing = cursor[segment];
613
+ if (!existing || typeof existing !== 'object' || Array.isArray(existing))
614
+ cursor[segment] = {};
615
+ cursor = cursor[segment];
616
+ });
617
+ }
618
+ function isSensitiveValue(value) {
619
+ return typeof value === 'string' && SENSITIVE_LOG_VALUE.test(value);
620
+ }
621
+ function assertUnique(values, label) {
622
+ const seen = new Set();
623
+ for (const value of values) {
624
+ if (seen.has(value))
625
+ throw new DecisionInteropError('dmn-duplicate-id', `${label} must be unique`);
626
+ seen.add(value);
627
+ }
628
+ }
629
+ function assertNoControlOverride(value, path) {
630
+ if (Array.isArray(value)) {
631
+ value.forEach((item, index) => assertNoControlOverride(item, `${path}[${index}]`));
632
+ return;
633
+ }
634
+ if (!value || typeof value !== 'object')
635
+ return;
636
+ for (const [key, child] of Object.entries(value)) {
637
+ if (FORBIDDEN_CONTROL_FIELDS.has(key))
638
+ throw new DecisionInteropError('interop-control-override', 'Imported policy cannot set a control field');
639
+ assertNoControlOverride(child, `${path}.${key}`);
640
+ }
641
+ }
642
+ function sha256(value) {
643
+ const bytes = typeof value === 'string' ? value : canonicalJson(value);
644
+ return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
645
+ }
646
+ function escapeXml(value) {
647
+ return value.replace(/&/gu, '&amp;').replace(/</gu, '&lt;').replace(/>/gu, '&gt;').replace(/"/gu, '&quot;');
648
+ }
649
+ function ncName(value) {
650
+ const cleaned = value.replace(/[^A-Za-z0-9_.-]/gu, '-').replace(/^[^A-Za-z_]+/u, '');
651
+ return cleaned || 'decision';
652
+ }
653
+ //# sourceMappingURL=interop.js.map
@@ -0,0 +1,3 @@
1
+ export * from './types.js';
2
+ export * from './runtime.js';
3
+ //# sourceMappingURL=index.js.map