@aiwg/cli 2026.9.20 → 2026.9.24

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 (275) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +58 -6
  2. package/agentic/code/providers/model-capabilities.v1.json +34 -0
  3. package/agentic/code/providers/model-catalog.v1.json +55 -13
  4. package/bin/aiwg.mjs +2 -0
  5. package/dist/src/api/index.d.ts +1 -0
  6. package/dist/src/api/index.js +1 -0
  7. package/dist/src/artifacts/cli.js +23 -18
  8. package/dist/src/artifacts/enrichment/cli.js +1 -1
  9. package/dist/src/artifacts/fortemi-core-query-adapter.js +6 -2
  10. package/dist/src/artifacts/query-engine.js +14 -4
  11. package/dist/src/artifacts/views/cli.js +1 -1
  12. package/dist/src/audit/operator-decision.js +10 -0
  13. package/dist/src/auth/credential-store.js +127 -27
  14. package/dist/src/catalog/builtin-models.json +272 -40
  15. package/dist/src/catalog/cli.js +1 -1
  16. package/dist/src/catalog/cli.mjs +1 -1
  17. package/dist/src/cli/handlers/effect.js +872 -0
  18. package/dist/src/cli/handlers/help.js +2 -0
  19. package/dist/src/cli/handlers/index.js +4 -2
  20. package/dist/src/cli/handlers/models.js +41 -10
  21. package/dist/src/cli/handlers/ralph.js +1 -1
  22. package/dist/src/cli/handlers/sessions.js +113 -37
  23. package/dist/src/cli/handlers/subcommands.js +31 -0
  24. package/dist/src/cli/handlers/use.js +46 -9
  25. package/dist/src/cli/handlers/utilities.js +10 -0
  26. package/dist/src/cli/handlers/workspace.js +3 -1
  27. package/dist/src/cli/scope-resolver.js +112 -9
  28. package/dist/src/cli/services/deployment-verification.js +5 -0
  29. package/dist/src/config/aiwg-config.js +3 -0
  30. package/dist/src/decision/acceptance-rollout.js +93 -0
  31. package/dist/src/decision/acceptance.js +175 -0
  32. package/dist/src/decision/adapters/jev.js +234 -13
  33. package/dist/src/decision/adapters/llm-subagent.js +60 -12
  34. package/dist/src/decision/admission.js +550 -0
  35. package/dist/src/decision/batch-benchmark.js +47 -0
  36. package/dist/src/decision/batch-receipts/accounting.js +75 -0
  37. package/dist/src/decision/batch-receipts/export.js +40 -0
  38. package/dist/src/decision/batch-receipts/index.js +9 -0
  39. package/dist/src/decision/batch-receipts/protection.js +40 -0
  40. package/dist/src/decision/batch-receipts/receipt.js +44 -0
  41. package/dist/src/decision/batch-receipts/result-store.js +341 -0
  42. package/dist/src/decision/batch-receipts/store.js +362 -0
  43. package/dist/src/decision/batch-receipts/types.js +2 -0
  44. package/dist/src/decision/batch-receipts/validate.js +127 -0
  45. package/dist/src/decision/batch.js +68 -0
  46. package/dist/src/decision/calibration/index.js +4 -0
  47. package/dist/src/decision/calibration/receipts.js +130 -0
  48. package/dist/src/decision/calibration/registry.js +258 -0
  49. package/dist/src/decision/calibration/types.js +2 -0
  50. package/dist/src/decision/compile-cache/benchmark.js +62 -0
  51. package/dist/src/decision/compile-cache/compatibility.js +65 -0
  52. package/dist/src/decision/compile-cache/file-store.js +280 -0
  53. package/dist/src/decision/compile-cache/identity.js +26 -0
  54. package/dist/src/decision/compile-cache/index.js +9 -0
  55. package/dist/src/decision/compile-cache/prefix.js +21 -0
  56. package/dist/src/decision/compile-cache/records.js +86 -0
  57. package/dist/src/decision/compile-cache/runtime.js +68 -0
  58. package/dist/src/decision/compile-cache/store.js +108 -0
  59. package/dist/src/decision/compile-cache/types.js +2 -0
  60. package/dist/src/decision/context-plan.js +234 -0
  61. package/dist/src/decision/context-qualification.js +60 -0
  62. package/dist/src/decision/ensemble/aggregate.js +215 -0
  63. package/dist/src/decision/ensemble/contract.js +316 -0
  64. package/dist/src/decision/ensemble/drift.js +51 -0
  65. package/dist/src/decision/ensemble/index.js +6 -0
  66. package/dist/src/decision/ensemble/report.js +71 -0
  67. package/dist/src/decision/ensemble/types.js +2 -0
  68. package/dist/src/decision/evaluate.js +1024 -78
  69. package/dist/src/decision/file-lifecycle-store.js +168 -0
  70. package/dist/src/decision/graph-beam.js +40 -0
  71. package/dist/src/decision/graph-budget.js +110 -0
  72. package/dist/src/decision/graph-decision-bridge.js +93 -0
  73. package/dist/src/decision/graph-evidence.js +123 -0
  74. package/dist/src/decision/graph-flow-adapter.js +56 -0
  75. package/dist/src/decision/graph-flow.js +105 -0
  76. package/dist/src/decision/graph-parallel.js +79 -0
  77. package/dist/src/decision/graph-patterns.js +20 -0
  78. package/dist/src/decision/graph-run-store.js +66 -0
  79. package/dist/src/decision/graph-run.js +166 -0
  80. package/dist/src/decision/graph-skill-bridge.js +91 -0
  81. package/dist/src/decision/graph-templates.js +56 -0
  82. package/dist/src/decision/graph.js +115 -0
  83. package/dist/src/decision/index.js +35 -0
  84. package/dist/src/decision/job-accounting.js +93 -0
  85. package/dist/src/decision/job-contract.js +186 -0
  86. package/dist/src/decision/job-effects.js +95 -0
  87. package/dist/src/decision/job-evaluate.js +80 -0
  88. package/dist/src/decision/job-gateway.js +121 -0
  89. package/dist/src/decision/job-http.js +194 -0
  90. package/dist/src/decision/job-payload-store.js +258 -0
  91. package/dist/src/decision/job-poll-limiter.js +92 -0
  92. package/dist/src/decision/job-quota.js +158 -0
  93. package/dist/src/decision/job-runtime.js +240 -0
  94. package/dist/src/decision/job-scheduler.js +26 -0
  95. package/dist/src/decision/job-service.js +117 -0
  96. package/dist/src/decision/job-store.js +264 -0
  97. package/dist/src/decision/job-worker.js +141 -0
  98. package/dist/src/decision/lifecycle.js +148 -0
  99. package/dist/src/decision/patterns/artifacts.js +173 -0
  100. package/dist/src/decision/patterns/catalog.js +108 -0
  101. package/dist/src/decision/patterns/durable-review.js +237 -0
  102. package/dist/src/decision/patterns/index.js +8 -0
  103. package/dist/src/decision/patterns/playground.js +401 -0
  104. package/dist/src/decision/patterns/recorded.js +43 -0
  105. package/dist/src/decision/patterns/specs.js +174 -0
  106. package/dist/src/decision/patterns/types.js +2 -0
  107. package/dist/src/decision/portable-secrets.js +53 -0
  108. package/dist/src/decision/projection.js +210 -0
  109. package/dist/src/decision/qualification/capture.js +67 -0
  110. package/dist/src/decision/qualification/drift.js +68 -0
  111. package/dist/src/decision/qualification/gate-evidence.js +172 -0
  112. package/dist/src/decision/qualification/gates.js +83 -0
  113. package/dist/src/decision/qualification/index.js +12 -0
  114. package/dist/src/decision/qualification/load.js +282 -0
  115. package/dist/src/decision/qualification/manifest.js +37 -0
  116. package/dist/src/decision/qualification/privacy.js +41 -0
  117. package/dist/src/decision/qualification/quality.js +213 -0
  118. package/dist/src/decision/qualification/release.js +116 -0
  119. package/dist/src/decision/qualification/runner.js +408 -0
  120. package/dist/src/decision/qualification/types.js +2 -0
  121. package/dist/src/decision/receipts.js +30 -7
  122. package/dist/src/decision/result-cache/index.js +6 -0
  123. package/dist/src/decision/result-cache/integrity.js +28 -0
  124. package/dist/src/decision/result-cache/key.js +28 -0
  125. package/dist/src/decision/result-cache/service.js +92 -0
  126. package/dist/src/decision/result-cache/store.js +327 -0
  127. package/dist/src/decision/result-cache/types.js +3 -0
  128. package/dist/src/decision/review/authorization.js +110 -0
  129. package/dist/src/decision/review/effect-ledger-adapter.js +321 -0
  130. package/dist/src/decision/review/index.js +12 -0
  131. package/dist/src/decision/review/migrate.js +34 -0
  132. package/dist/src/decision/review/operator-audit.js +59 -0
  133. package/dist/src/decision/review/recovery-adapters.js +252 -0
  134. package/dist/src/decision/review/recovery.js +33 -0
  135. package/dist/src/decision/review/service.js +498 -0
  136. package/dist/src/decision/review/store.js +219 -0
  137. package/dist/src/decision/review/types.js +2 -0
  138. package/dist/src/decision/review/validate.js +205 -0
  139. package/dist/src/decision/scheduler.js +115 -0
  140. package/dist/src/decision/telemetry/context.js +42 -0
  141. package/dist/src/decision/telemetry/debug-sidecar.js +152 -0
  142. package/dist/src/decision/telemetry/exporter.js +77 -0
  143. package/dist/src/decision/telemetry/file-debug-backend.js +112 -0
  144. package/dist/src/decision/telemetry/index.js +13 -0
  145. package/dist/src/decision/telemetry/mapping.js +146 -0
  146. package/dist/src/decision/telemetry/metrics.js +164 -0
  147. package/dist/src/decision/telemetry/otlp-http.js +183 -0
  148. package/dist/src/decision/telemetry/redaction.js +93 -0
  149. package/dist/src/decision/telemetry/retention.js +109 -0
  150. package/dist/src/decision/telemetry/runtime.js +310 -0
  151. package/dist/src/decision/telemetry/trace.js +158 -0
  152. package/dist/src/decision/telemetry/types.js +3 -0
  153. package/dist/src/decision/validate.js +135 -5
  154. package/dist/src/effects/checkpoint-sinks.js +94 -0
  155. package/dist/src/effects/checkpoint.js +76 -0
  156. package/dist/src/effects/decision-links.js +54 -0
  157. package/dist/src/effects/errors.js +48 -0
  158. package/dist/src/effects/identity.js +178 -0
  159. package/dist/src/effects/index.js +25 -0
  160. package/dist/src/effects/keyring.js +160 -0
  161. package/dist/src/effects/keys.js +130 -0
  162. package/dist/src/effects/ledger.js +435 -0
  163. package/dist/src/effects/lifecycle.js +110 -0
  164. package/dist/src/effects/lock-recovery.js +269 -0
  165. package/dist/src/effects/reader.js +103 -0
  166. package/dist/src/effects/records.js +114 -0
  167. package/dist/src/effects/redaction.js +51 -0
  168. package/dist/src/effects/schema.js +73 -0
  169. package/dist/src/effects/store.js +239 -0
  170. package/dist/src/effects/types.js +49 -0
  171. package/dist/src/effects/verifiers/decision.js +168 -0
  172. package/dist/src/effects/verifiers/file.js +106 -0
  173. package/dist/src/effects/verifiers/git.js +206 -0
  174. package/dist/src/effects/verifiers/index.js +90 -0
  175. package/dist/src/effects/verifiers/registry.js +148 -0
  176. package/dist/src/effects/verifiers/review.js +123 -0
  177. package/dist/src/effects/verifiers/tracker.js +299 -0
  178. package/dist/src/effects/verifiers/types.js +21 -0
  179. package/dist/src/effects/verify.js +295 -0
  180. package/dist/src/evidence/bundle.js +2 -0
  181. package/dist/src/extensions/commands/definitions.js +28 -0
  182. package/dist/src/extensions/manifest.js +1 -0
  183. package/dist/src/marketplace/provenance.js +25 -27
  184. package/dist/src/mcp/cli.mjs +11 -1
  185. package/dist/src/mcp/grok-build-config.mjs +189 -0
  186. package/dist/src/mcp/registry.js +21 -1
  187. package/dist/src/mcp/registry.mjs +20 -1
  188. package/dist/src/models/grok-build-models.js +356 -0
  189. package/dist/src/models/index.js +1 -0
  190. package/dist/src/models/model-capabilities.v1.json +34 -0
  191. package/dist/src/models/model-catalog.v1.json +55 -13
  192. package/dist/src/models/model-discovery.js +41 -5
  193. package/dist/src/models/provider-policy.js +3 -2
  194. package/dist/src/plugin/skill-command-translator.js +1 -0
  195. package/dist/src/providers/capability-matrix.yaml +58 -6
  196. package/dist/src/providers/grok-build-acp.js +192 -0
  197. package/dist/src/providers/grok-build-dispatch.js +251 -0
  198. package/dist/src/providers/grok-build-headless.js +170 -0
  199. package/dist/src/providers/muse-paths.js +105 -0
  200. package/dist/src/providers/provider-definitions.js +93 -7
  201. package/dist/src/providers/provider-definitions.mjs +21 -1
  202. package/dist/src/providers/provider-inventory.js +3 -0
  203. package/dist/src/security/artifact-attestation.js +4 -8
  204. package/dist/src/security/signing.js +119 -0
  205. package/dist/src/sessions/adapters/grok-build.js +187 -0
  206. package/dist/src/sessions/adapters/muse.js +412 -0
  207. package/dist/src/sessions/contracts.js +1 -1
  208. package/dist/src/sessions/grok-build-cli.js +196 -0
  209. package/dist/src/sessions/index.js +3 -0
  210. package/dist/src/sessions/workspace-discovery.js +9 -5
  211. package/dist/src/skills/deployer.js +33 -5
  212. package/dist/src/smiths/context-pipeline/claude-hook.js +2 -0
  213. package/dist/src/smiths/context-pipeline/context-file-safety.js +18 -0
  214. package/dist/src/smiths/context-pipeline/finalization.js +1 -32
  215. package/dist/src/smiths/context-pipeline/generator.js +12 -0
  216. package/dist/src/smiths/context-pipeline/index.js +1 -1
  217. package/dist/src/smiths/context-pipeline/managed-hook.js +2 -0
  218. package/dist/src/smiths/context-pipeline/muse-bridge.js +50 -0
  219. package/dist/src/smiths/context-pipeline/provider-policy.js +5 -0
  220. package/dist/src/smiths/context-pipeline/workspace-context.js +22 -16
  221. package/dist/src/smiths/hook-bridge/grok-build-translator.js +64 -0
  222. package/dist/src/smiths/hook-bridge/index.js +3 -0
  223. package/dist/src/smiths/hook-bridge/shim.js +2 -0
  224. package/dist/src/smiths/hook-bridge/types.js +5 -0
  225. package/dist/src/smiths/skillsmith/collision-detector.js +1 -0
  226. package/dist/src/storage/protected-files.js +102 -0
  227. package/dist/src/tracker/forge-http.js +371 -0
  228. package/dist/src/tracker/project-inputs.js +41 -0
  229. package/package.json +2 -1
  230. package/schemas/decision/BatchReceipt.v1.schema.json +50 -0
  231. package/schemas/decision/CalibrationArtifact.v1.schema.json +76 -0
  232. package/schemas/decision/CalibrationCompatibility.v1.schema.json +18 -0
  233. package/schemas/decision/CalibrationGovernanceReceipt.v1.schema.json +44 -0
  234. package/schemas/decision/DecisionAdmissionEvidence.v1.schema.json +43 -0
  235. package/schemas/decision/DecisionBinding.schema.json +309 -0
  236. package/schemas/decision/DecisionBinding.v1alpha2.schema.json +369 -0
  237. package/schemas/decision/DecisionCacheTelemetry.v1.schema.json +20 -0
  238. package/schemas/decision/DecisionChampionChallenger.v1.schema.json +100 -0
  239. package/schemas/decision/DecisionCompileCacheEntry.v1.schema.json +35 -0
  240. package/schemas/decision/DecisionCompileCacheIdentity.v1.schema.json +26 -0
  241. package/schemas/decision/DecisionCompileCacheTombstone.v1.schema.json +31 -0
  242. package/schemas/decision/DecisionContextEvidence.v1.schema.json +136 -0
  243. package/schemas/decision/DecisionDefinition.schema.json +159 -0
  244. package/schemas/decision/DecisionDefinition.v1alpha2.schema.json +203 -0
  245. package/schemas/decision/DecisionDriftResponse.v1.schema.json +55 -0
  246. package/schemas/decision/DecisionEnsembleAggregate.v1.schema.json +86 -0
  247. package/schemas/decision/DecisionEnsembleIntegrityReport.v1.schema.json +64 -0
  248. package/schemas/decision/DecisionEnsemblePolicy.v1.schema.json +130 -0
  249. package/schemas/decision/DecisionGraph.v1.schema.json +77 -0
  250. package/schemas/decision/DecisionJob.v1.schema.json +71 -0
  251. package/schemas/decision/DecisionLifecyclePolicy.v1.schema.json +52 -0
  252. package/schemas/decision/DecisionProjectionPolicy.v1.schema.json +65 -0
  253. package/schemas/decision/DecisionProviderPrefixCompatibility.v1.schema.json +45 -0
  254. package/schemas/decision/DecisionProviderPrefixEvidence.v1.schema.json +17 -0
  255. package/schemas/decision/DecisionProviderPrefixIdentity.v1.schema.json +36 -0
  256. package/schemas/decision/DecisionResult.schema.json +524 -0
  257. package/schemas/decision/DecisionResult.v1alpha2.schema.json +619 -0
  258. package/schemas/decision/DecisionResultCacheEntry.v1.schema.json +53 -0
  259. package/schemas/decision/DecisionReview.schema.json +29 -0
  260. package/schemas/decision/DecisionRuleset.schema.json +313 -0
  261. package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +313 -0
  262. package/schemas/decision/DecisionTelemetryTrace.v1.schema.json +95 -0
  263. package/schemas/decision/RulesetResult.schema.json +721 -0
  264. package/schemas/decision/RulesetResult.v1alpha2.schema.json +891 -0
  265. package/schemas/security/pr-evidence-receipt.v1.schema.json +77 -0
  266. package/schemas/security/threat-assessment-report.v1.schema.json +71 -1
  267. package/tools/agents/deploy-agents.mjs +21 -6
  268. package/tools/agents/providers/base.mjs +7 -4
  269. package/tools/agents/providers/codex.mjs +1 -1
  270. package/tools/agents/providers/grok-build.d.mts +7 -0
  271. package/tools/agents/providers/grok-build.mjs +249 -9
  272. package/tools/agents/providers/model-role.mjs +6 -3
  273. package/tools/agents/providers/muse-hooks.mjs +785 -0
  274. package/tools/agents/providers/muse-paths.mjs +112 -0
  275. package/tools/agents/providers/muse.mjs +336 -0
@@ -0,0 +1,401 @@
1
+ import { createHash } from 'node:crypto';
2
+ import Ajv2020 from 'ajv/dist/2020.js';
3
+ import { canonicalJson } from '../../security/artifact-trust.js';
4
+ import { JevDecisionAdapter } from '../adapters/jev.js';
5
+ import { decisionBatchQuestionId } from '../batch.js';
6
+ import { batchAccountingTotals } from '../batch-receipts/accounting.js';
7
+ import { MemoryBatchResultStore } from '../batch-receipts/result-store.js';
8
+ import { MemoryBatchReceiptStore } from '../batch-receipts/store.js';
9
+ import { CanonicalJsonByteEstimator, planDecisionContext } from '../context-plan.js';
10
+ import { evaluateDecisionRuleset } from '../evaluate.js';
11
+ import { MemoryDecisionReceiptStore } from '../receipts.js';
12
+ import { resolveJsonPointer } from '../validate.js';
13
+ import { getDecisionPatternPack, decisionPatternPacks } from './catalog.js';
14
+ import { governedPatternArtifacts, resolveDecisionPatternArtifact, validateGovernedPatternArtifacts } from './artifacts.js';
15
+ import { RecordedJevTransport, resolveOfflineRecordedCredential } from './recorded.js';
16
+ import { PATTERN_SPECS } from './specs.js';
17
+ const record = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : {};
18
+ const pointerValue = (root, pointer) => { const resolved = resolveJsonPointer(root, pointer); return resolved.found ? resolved.value : undefined; };
19
+ const RESTRICTIVENESS = { accept: 0, review: 1, deny: 2 };
20
+ export function listDecisionPatterns() {
21
+ return decisionPatternPacks.map(({ id, version, status, summary, limitations }) => ({ id, version, status, summary, limitations: [...limitations] }));
22
+ }
23
+ const validationCache = new Map();
24
+ /** Validate a pack manifest. Results are memoized by the validated content (fixture expectations are not validated). */
25
+ export function validateDecisionPattern(pack) {
26
+ const key = canonicalJson({ ...pack, fixtures: pack.fixtures.map(({ expected: _expected, ...fixture }) => fixture) });
27
+ if (!validationCache.has(key))
28
+ validationCache.set(key, validateDecisionPatternUncached(pack));
29
+ return [...validationCache.get(key)];
30
+ }
31
+ function validateDecisionPatternUncached(pack) {
32
+ const errors = [];
33
+ if (pack.schema !== 'decision-pattern-pack/v1')
34
+ errors.push('unsupported-schema');
35
+ if (!/^\d+\.\d+\.\d+$/.test(pack.version))
36
+ errors.push('invalid-version');
37
+ for (const [name, value] of Object.entries(pack.artifacts)) {
38
+ if (Array.isArray(value) ? value.length === 0 || value.some(item => !item) : !value)
39
+ errors.push(`missing-artifact:${name}`);
40
+ for (const reference of Array.isArray(value) ? value : [value]) {
41
+ try {
42
+ const resolved = resolveDecisionPatternArtifact(reference);
43
+ if (resolved.patternId !== pack.id || resolved.patternVersion !== pack.version)
44
+ errors.push(`artifact-identity-mismatch:${name}`);
45
+ }
46
+ catch {
47
+ errors.push(`unresolvable-artifact:${name}`);
48
+ }
49
+ }
50
+ }
51
+ if (pack.status !== 'unavailable' && pack.fixtures.length === 0)
52
+ errors.push('missing-offline-fixture');
53
+ if (!pack.failurePath || !pack.rollback || pack.limitations.length === 0)
54
+ errors.push('missing-governance-guidance');
55
+ if (pack.live && (pack.live.limits.maxCalls < 1 || pack.live.limits.maxTokens < 1 || pack.live.limits.maxCostUsd <= 0 || pack.live.limits.allowUnknownCost !== false || pack.live.limits.maxAttempts < 1 || pack.live.limits.deadlineMs < 1))
56
+ errors.push('invalid-live-limits');
57
+ try {
58
+ validateGovernedPatternArtifacts(pack.id, pack.version);
59
+ const inputSchema = resolveDecisionPatternArtifact(pack.artifacts.inputSchema).content;
60
+ const outputSchema = resolveDecisionPatternArtifact(pack.artifacts.outputSchema).content;
61
+ if (typeof inputSchema === 'string' || typeof outputSchema === 'string')
62
+ throw new Error('schema artifact must be JSON');
63
+ const ajv = new Ajv2020({ strict: true, allErrors: true });
64
+ const validateInput = ajv.compile(inputSchema);
65
+ const validateOutput = ajv.compile(outputSchema);
66
+ for (const candidate of pack.fixtures) {
67
+ if (!validateInput(candidate.input))
68
+ errors.push(`fixture-input-schema:${candidate.id}`);
69
+ if (!validateOutput(candidate.recordedEvidence))
70
+ errors.push(`fixture-output-schema:${candidate.id}`);
71
+ }
72
+ }
73
+ catch {
74
+ errors.push('invalid-artifact-schema');
75
+ }
76
+ return errors;
77
+ }
78
+ /**
79
+ * Run one offline fixture through the production decision runtime: the governed
80
+ * definitions, ruleset and offline binding are evaluated by `evaluateDecisionRuleset`
81
+ * with the production Jev adapter over a recorded-replay transport. Deterministic
82
+ * gates declared in the pack's candidate policy may only narrow the outcome.
83
+ */
84
+ export async function runOfflineDecisionPattern(id, fixtureId, overrides = {}) {
85
+ const pack = getDecisionPatternPack(id);
86
+ const errors = validateDecisionPattern(pack);
87
+ if (errors.length)
88
+ throw new Error(`Invalid decision pattern: ${errors.join(', ')}`);
89
+ if (pack.status === 'unavailable')
90
+ throw new Error(`Decision pattern unavailable: ${id}`);
91
+ const selected = fixtureId ? pack.fixtures.find(candidate => candidate.id === fixtureId) : pack.fixtures[0];
92
+ if (!selected)
93
+ throw new Error(`Unknown fixture for decision pattern: ${id}`);
94
+ const input = structuredClone(overrides.input ?? selected.input);
95
+ const recordedEvidence = structuredClone(overrides.recordedEvidence ?? selected.recordedEvidence);
96
+ const spec = PATTERN_SPECS[id];
97
+ const aliases = spec.evaluations.map(evaluation => evaluation.alias);
98
+ const gates = [];
99
+ const base = { pack, fixtureId: selected.id, subjectId: selected.subjectId, spec };
100
+ let batchSubject;
101
+ if (spec.gates.batchSubjectsFrom) {
102
+ const subjects = aliases.map(alias => record(pointerValue(input, spec.gates.batchSubjectsFrom))[alias]);
103
+ if (!subjects.every(subject => typeof subject === 'string' && subject && subject === subjects[0])) {
104
+ // Unrelated subjects never share a request; the anti-example is rejected before any dispatch.
105
+ gates.push({ gate: 'batch-subject', outcome: 'rejected-before-dispatch', reason: 'multi-subject-batch-rejected' });
106
+ return offlineReceipt({ ...base, route: 'deny', reason: 'multi-subject-batch-rejected', gates, result: null, invocations: 0, transportCalls: 0, candidate: null, usage: unavailableUsage() });
107
+ }
108
+ batchSubject = subjects[0];
109
+ gates.push({ gate: 'batch-subject', outcome: 'pass' });
110
+ }
111
+ const transport = new RecordedJevTransport(recordedEvidence, aliases);
112
+ const artifacts = governedPatternArtifacts(id, pack.version);
113
+ const request = {
114
+ ruleset: artifacts.ruleset, binding: artifacts.offlineBinding, definitions: artifacts.definitions, input,
115
+ runId: `pattern-${id}`, invocationId: `pattern-${id}-${selected.id}`,
116
+ adapters: { jev: new JevDecisionAdapter({ fetch: transport.fetch }) }, resolveCredential: resolveOfflineRecordedCredential,
117
+ // Recorded replay never leaves the process, so the host opts out of projection explicitly (D10).
118
+ projection: { mode: 'unprojected-local' },
119
+ };
120
+ let batchStore;
121
+ if (batchSubject !== undefined) {
122
+ batchStore = new MemoryBatchReceiptStore();
123
+ Object.assign(request, nativeBatchPolicy(id, pack.version, spec, input, batchSubject, batchStore));
124
+ }
125
+ let invocations = 1;
126
+ let result;
127
+ if (spec.gates.replay) {
128
+ // Idempotent resume through the production receipt store: every resume after the
129
+ // first must return the stored result without another transport request.
130
+ const resumes = Number(pointerValue(input, spec.gates.replay.resumeCountFrom));
131
+ request.receiptStore = new MemoryDecisionReceiptStore();
132
+ request.receiptProjectId = `pattern-${id}`;
133
+ request.invocationId = `pattern-${id}-${String(input.invocationId ?? selected.id)}`;
134
+ result = await evaluateDecisionRuleset(request);
135
+ for (invocations = 1; invocations < resumes; invocations += 1) {
136
+ const replayed = await evaluateDecisionRuleset(request);
137
+ if (canonicalJson(replayed) !== canonicalJson(result))
138
+ throw new Error('Durable replay returned a different result');
139
+ }
140
+ gates.push({ gate: 'durable-replay', outcome: transport.calls.length <= aliases.length ? 'pass' : 'narrowed', reason: `${invocations}-invocations` });
141
+ }
142
+ else {
143
+ result = await evaluateDecisionRuleset(request);
144
+ }
145
+ const gated = applyGates(spec, input, result, gates);
146
+ const usage = batchStore ? await requestUsage(result, batchStore, request) : attemptUsage(result);
147
+ return offlineReceipt({ ...base, route: gated.route, reason: gated.reason, gates, result, invocations, transportCalls: transport.calls.length, candidate: gated.candidate, usage });
148
+ }
149
+ function nativeBatchPolicy(id, version, spec, input, subject, store) {
150
+ const estimator = new CanonicalJsonByteEstimator();
151
+ const state = pointerValue(input, spec.evaluations[0].inputPointer);
152
+ const contextPlan = planDecisionContext({
153
+ subject, authorizedState: state, authorizationDigest: `sha256:${createHash('sha256').update(`pattern.${id}@${version}`).digest('hex')}`, incompleteContext: false,
154
+ questions: spec.evaluations.map(evaluation => ({ id: decisionBatchQuestionId(evaluation.alias), subject, entry: { question: evaluation.alias } })),
155
+ }, { id: 'jev', version: '1', estimator: { id: estimator.id, version: estimator.version }, limits: { aggregateTokens: 100_000, stateAndLongestQuestionTokens: 100_000 }, safetyMarginBps: 0, requestEnvelopeTokens: 0 }, estimator);
156
+ return {
157
+ batching: { enabled: true, evaluations: Object.fromEntries(spec.evaluations.map(evaluation => [evaluation.alias,
158
+ { decisionSubject: subject, independent: true, egressPolicy: 'pattern-offline-recorded', hostPolicy: `pattern.${id}@${version}` }])) },
159
+ batchReceipts: { store, resultStore: new MemoryBatchResultStore(), tenantId: 'pattern-playground', projectId: `pattern-${id}`, contextPlan,
160
+ subjectHash: `sha256:${createHash('sha256').update(subject).digest('hex')}` },
161
+ };
162
+ }
163
+ /** Deterministic gates run after evaluation and can only make the route more restrictive. */
164
+ function applyGates(spec, input, result, gates) {
165
+ const outcome = rulesetOutcome(result);
166
+ let route = outcome.route;
167
+ let reason = outcome.reason;
168
+ const narrow = (next, nextReason) => {
169
+ if (RESTRICTIVENESS[next] > RESTRICTIVENESS[route]) {
170
+ route = next;
171
+ reason = nextReason;
172
+ }
173
+ };
174
+ if (spec.gates.deterministicPolicyFrom) {
175
+ const denied = pointerValue(input, spec.gates.deterministicPolicyFrom) === 'deny';
176
+ gates.push({ gate: 'deterministic-policy', outcome: denied ? 'narrowed' : 'pass', ...(denied ? { reason: 'deterministic-policy-deny' } : {}) });
177
+ if (denied && !(route === 'deny' && reason === 'deterministic-policy-deny')) {
178
+ route = 'deny';
179
+ reason = 'deterministic-policy-deny';
180
+ }
181
+ }
182
+ let candidate = null;
183
+ const gate = spec.gates.candidateGate;
184
+ if (gate) {
185
+ if (route !== 'accept')
186
+ gates.push({ gate: 'candidate-membership', outcome: 'not-applicable' });
187
+ else {
188
+ const proposed = gate.candidateFrom.source === 'decision'
189
+ ? result.spec.evaluations[gate.candidateFrom.alias]?.spec.value : pointerValue(input, gate.candidateFrom.pointer);
190
+ const allowed = pointerValue(input, gate.allowedFrom);
191
+ const member = Array.isArray(allowed) && allowed.some(entry => gate.matchKeys
192
+ ? gate.matchKeys.every(key => canonicalJson(record(entry)[key] ?? null) === canonicalJson(record(proposed)[key] ?? null))
193
+ && Object.entries(gate.requireAllowed ?? {}).every(([key, value]) => canonicalJson(record(entry)[key] ?? null) === canonicalJson(value))
194
+ : typeof proposed === 'string' && entry === proposed);
195
+ if (member) {
196
+ const label = gate.labelKey ? record(proposed)[gate.labelKey] : proposed;
197
+ candidate = typeof label === 'string' ? label : null;
198
+ gates.push({ gate: 'candidate-membership', outcome: 'pass' });
199
+ }
200
+ else {
201
+ gates.push({ gate: 'candidate-membership', outcome: 'narrowed', reason: gate.reason });
202
+ narrow(spec.unauthorizedEvidenceRoute, gate.reason);
203
+ }
204
+ }
205
+ }
206
+ const argumentGate = spec.gates.argumentGate;
207
+ if (argumentGate) {
208
+ if (route !== 'accept')
209
+ gates.push({ gate: 'typed-arguments', outcome: 'not-applicable' });
210
+ else {
211
+ const selected = result.spec.evaluations[argumentGate.functionAlias]?.spec.value;
212
+ const schemas = record(pointerValue(input, argumentGate.schemasFrom));
213
+ let valid = false;
214
+ try {
215
+ valid = typeof selected === 'string' && Object.prototype.hasOwnProperty.call(schemas, selected)
216
+ && Boolean(new Ajv2020({ strict: true, allErrors: true }).compile(record(schemas[selected]))(pointerValue(input, argumentGate.argumentsFrom)));
217
+ }
218
+ catch {
219
+ valid = false;
220
+ }
221
+ gates.push({ gate: 'typed-arguments', outcome: valid ? 'pass' : 'narrowed', ...(valid ? {} : { reason: argumentGate.reason }) });
222
+ if (!valid) {
223
+ narrow(spec.unauthorizedEvidenceRoute, argumentGate.reason);
224
+ candidate = null;
225
+ }
226
+ }
227
+ }
228
+ return { route, reason, candidate: route === 'accept' ? candidate : null };
229
+ }
230
+ function rulesetOutcome(result) {
231
+ const outcome = record(result.spec.outcome);
232
+ const route = outcome.route;
233
+ return (route === 'accept' || route === 'review' || route === 'deny') && typeof outcome.reason === 'string'
234
+ ? { route, reason: outcome.reason } : { route: 'review', reason: `ruleset-${result.spec.reason}` };
235
+ }
236
+ async function requestUsage(result, store, request) {
237
+ const reference = Object.values(result.spec.evaluations).find(evaluation => evaluation.spec.batchResult)?.spec.batchResult;
238
+ const receipt = reference ? await store.read(reference.batchId, request.batchReceipts.tenantId, request.batchReceipts.projectId) : null;
239
+ if (!receipt)
240
+ return unavailableUsage();
241
+ const totals = batchAccountingTotals(receipt).usage;
242
+ return totals.inputTokens === null && totals.outputTokens === null ? unavailableUsage()
243
+ : { inputTokens: totals.inputTokens, outputTokens: totals.outputTokens, costUsd: null, availability: 'recorded', scope: 'request' };
244
+ }
245
+ function attemptUsage(result) {
246
+ const attempts = Object.values(result.spec.evaluations).flatMap(evaluation => evaluation.spec.attempts);
247
+ const sum = (key) => attempts.some(attempt => attempt.usage[key] !== null)
248
+ ? attempts.reduce((total, attempt) => total + (attempt.usage[key] ?? 0), 0) : null;
249
+ const inputTokens = sum('inputTokens');
250
+ const outputTokens = sum('outputTokens');
251
+ return inputTokens === null && outputTokens === null ? unavailableUsage()
252
+ : { inputTokens, outputTokens, costUsd: null, availability: 'recorded', scope: 'attempts' };
253
+ }
254
+ function unavailableUsage() { return { inputTokens: null, outputTokens: null, costUsd: null, availability: 'unavailable', scope: null }; }
255
+ function evaluationEvidence(spec, result) {
256
+ if (!result)
257
+ return [];
258
+ return spec.evaluations.flatMap(evaluation => {
259
+ const value = result.spec.evaluations[evaluation.alias];
260
+ if (!value)
261
+ return [];
262
+ const attempts = value.spec.attempts;
263
+ return [{
264
+ alias: evaluation.alias, primitive: evaluation.answer.kind, status: value.spec.status, reason: value.spec.reason,
265
+ value: value.spec.value ?? null, distribution: value.spec.uncertainty?.distribution ? { ...value.spec.uncertainty.distribution } : null,
266
+ acceptance: value.spec.acceptance ? { disposition: value.spec.acceptance.disposition, matchedRule: value.spec.acceptance.matchedRule, reason: value.spec.acceptance.reason } : null,
267
+ attempts: attempts.length,
268
+ usage: attempts.at(-1)?.usage ? { ...attempts.at(-1).usage } : { inputTokens: null, outputTokens: null, costUsd: null },
269
+ }];
270
+ });
271
+ }
272
+ function offlineReceipt(input) {
273
+ const evaluations = evaluationEvidence(input.spec, input.result);
274
+ const outcome = input.result ? rulesetOutcome(input.result) : null;
275
+ const checks = [
276
+ ...(input.result ? ['evaluated-by-decision-runtime', 'acceptance-policy-applied'] : ['rejected-before-dispatch']),
277
+ ...input.gates.map(gate => gate.gate === 'batch-subject' ? 'same-subject' : gate.gate === 'deterministic-policy' ? 'deterministic-policy-precedence'
278
+ : gate.gate === 'durable-replay' ? 'idempotent-resume' : gate.gate),
279
+ ...(input.usage.scope === 'request' ? ['request-usage-once'] : []),
280
+ 'action-not-executed',
281
+ ];
282
+ return {
283
+ schema: 'decision-pattern-receipt/v2', pattern: { id: input.pack.id, version: input.pack.version }, fixtureId: input.fixtureId,
284
+ subjectId: input.subjectId, executionMode: 'offline-recorded', evidenceOrigin: 'sanitized-recorded-fixture',
285
+ primitive: input.pack.primitive, requestedModel: 'offline:recorded-fixture', actualModel: null,
286
+ runtime: { evaluator: 'evaluateDecisionRuleset', adapter: 'jev@1.0.0', transport: 'recorded-replay', invocations: input.invocations, transportCalls: input.transportCalls },
287
+ uncertainty: { provenance: 'recorded-uncalibrated', calibration: 'unavailable', distribution: evaluations.length === 1 ? evaluations[0].distribution : null },
288
+ route: input.route, reason: input.reason,
289
+ rulesetOutcome: input.result && outcome ? { status: input.result.spec.status, route: outcome.route, reason: outcome.reason, matchedRules: [...input.result.spec.matchedRules] } : null,
290
+ gates: input.gates, evaluations, attempts: evaluations.reduce((sum, evaluation) => sum + evaluation.attempts, 0), usage: input.usage,
291
+ action: { status: 'unexecuted', candidate: input.candidate }, checks: [...new Set(checks)], result: input.result,
292
+ };
293
+ }
294
+ export function planLiveDecisionPattern(id, options) {
295
+ const pack = getDecisionPatternPack(id);
296
+ if (!pack.live)
297
+ return { mode: 'live', status: 'skipped', reason: 'live-binding-unavailable', credentialRef: null, limits: null, executes: false };
298
+ const base = { mode: 'live', credentialRef: pack.live.credentialRef, limits: { ...pack.live.limits }, executes: false };
299
+ if (!options.explicitOptIn)
300
+ return { ...base, status: 'skipped', reason: 'explicit-opt-in-required' };
301
+ if (!options.egressApproved)
302
+ return { ...base, status: 'denied', reason: 'egress-denied' };
303
+ if (!options.credentialResolved)
304
+ return { ...base, status: 'skipped', reason: 'credential-unavailable' };
305
+ return { ...base, status: 'ready', reason: 'ready' };
306
+ }
307
+ /**
308
+ * Execute one explicitly approved synthetic live probe through the production
309
+ * evaluator and Jev adapter. Pack limits become a scheduler admission policy, so
310
+ * every transport call is admitted before it starts: a call that would exceed the
311
+ * call, token or cost reservation is never started, unknown cost is never admitted,
312
+ * and the deadline aborts the in-flight transport through the evaluator's signal.
313
+ */
314
+ export async function runLiveDecisionPattern(id, request, options, transport) {
315
+ const pack = getDecisionPatternPack(id);
316
+ const plan = planLiveDecisionPattern(id, options);
317
+ if (plan.status !== 'ready' || !pack.live || !plan.limits)
318
+ throw new Error(`Live decision pattern not ready: ${plan.reason}`);
319
+ if (request.synthetic !== true)
320
+ throw new Error('Live decision pattern input must be explicitly synthetic');
321
+ if (!transport.model || !transport.model.trim())
322
+ throw new Error('Live decision pattern requires an explicit requested model');
323
+ const limits = tightenedLimits(plan.limits, options.limits);
324
+ const spec = PATTERN_SPECS[id];
325
+ const artifacts = governedPatternArtifacts(id, pack.version);
326
+ const binding = structuredClone(artifacts.liveBindingTemplate);
327
+ binding.spec.totalTimeoutMs = limits.deadlineMs;
328
+ binding.spec.maxAttempts = spec.evaluations.length * limits.maxAttempts;
329
+ for (const evaluation of Object.values(binding.spec.evaluations)) {
330
+ for (const target of evaluation.targets) {
331
+ target.model = transport.model;
332
+ target.timeoutMs = limits.deadlineMs;
333
+ target.retry = { ...target.retry, maxRetries: limits.maxAttempts - 1 };
334
+ }
335
+ }
336
+ let calls = 0;
337
+ const admission = [];
338
+ let reservedTokens = 0;
339
+ let reservedCostUsd = 0;
340
+ const countedFetch = (async (url, init) => { calls += 1; return transport.fetch(url, init); });
341
+ const scheduler = {
342
+ // Admission profiles are registered process-wide per workspace; a revision names exactly one set of limits.
343
+ enabled: true, profileVersion: `pattern.${id}@${pack.version}.live.${createHash('sha256').update(canonicalJson(limits)).digest('hex').slice(0, 16)}`,
344
+ workspace: { id: 'pattern-playground', limits: { concurrency: 1 } },
345
+ principal: { id: 'pattern-playground-operator', limits: { concurrency: 1 } },
346
+ providers: { jev: { concurrency: 1, maxAttempts: limits.maxCalls, maxTokens: limits.maxTokens, maxCostUsd: limits.maxCostUsd, allowUnknownCost: false } },
347
+ estimate: alias => {
348
+ const estimate = transport.estimate(alias);
349
+ return { tokens: estimate.tokens, costUsd: estimate.costUsd, attempts: 1, batchSize: 1, items: 1 };
350
+ },
351
+ onEvidence: (alias, evidence) => {
352
+ admission.push({ alias, decision: evidence.decision, reason: evidence.reason });
353
+ if (evidence.decision === 'admit') {
354
+ reservedTokens += evidence.estimatedTokens ?? 0;
355
+ reservedCostUsd += evidence.estimatedCostUsd ?? 0;
356
+ }
357
+ },
358
+ };
359
+ const result = await evaluateDecisionRuleset({
360
+ ruleset: artifacts.ruleset, binding, definitions: artifacts.definitions, input: structuredClone(request.input),
361
+ runId: `pattern-${id}-live`, invocationId: `pattern-${id}-live-${createHash('sha256').update(canonicalJson(request.input)).digest('hex').slice(0, 16)}-${Date.now()}`,
362
+ adapters: { jev: new JevDecisionAdapter({ fetch: countedFetch, ...(transport.region ? { region: transport.region } : {}) }) },
363
+ resolveCredential: transport.resolveCredential,
364
+ scheduler, ...(transport.signal ? { signal: transport.signal } : {}),
365
+ ...(transport.projection ? { projection: transport.projection } : {}),
366
+ });
367
+ const attempts = Object.values(result.spec.evaluations).flatMap(evaluation => evaluation.spec.attempts);
368
+ const sum = (key) => attempts.some(attempt => attempt.usage[key] !== null)
369
+ ? attempts.reduce((total, attempt) => total + (attempt.usage[key] ?? 0), 0) : null;
370
+ const usage = { inputTokens: sum('inputTokens'), outputTokens: sum('outputTokens'), reservedTokens, reservedCostUsd, reportedCostUsd: null };
371
+ const limitBreaches = [
372
+ ...(calls > limits.maxCalls ? ['calls'] : []),
373
+ ...((usage.inputTokens ?? 0) + (usage.outputTokens ?? 0) > limits.maxTokens ? ['tokens'] : []),
374
+ ];
375
+ const models = [...new Set(attempts.map(attempt => attempt.actualModel).filter((model) => Boolean(model)))];
376
+ const gated = applyGates(spec, request.input, result, []);
377
+ const identityMissing = attempts.some(attempt => attempt.status === 'success' && !attempt.actualModel);
378
+ const route = limitBreaches.length || identityMissing ? (gated.route === 'deny' ? 'deny' : 'review') : gated.route;
379
+ const reason = limitBreaches.length && gated.route !== 'deny' ? 'live-usage-exceeded-limit' : identityMissing && gated.route !== 'deny' ? 'model-identity-missing' : gated.reason;
380
+ return {
381
+ schema: 'decision-pattern-live-receipt/v2', pattern: { id, version: pack.version }, executionMode: 'live', evidenceOrigin: 'live-synthetic',
382
+ requestedModel: transport.model, actualModel: models.length === 1 ? models[0] : null, calls, attempts: attempts.length, limits, admission,
383
+ usage, limitBreaches, deadlineMs: limits.deadlineMs, route, reason, action: { status: 'unexecuted' }, result,
384
+ };
385
+ }
386
+ function tightenedLimits(pack, requested = {}) {
387
+ const next = { ...pack };
388
+ for (const key of ['maxCalls', 'maxTokens', 'maxCostUsd', 'maxAttempts', 'deadlineMs']) {
389
+ const value = requested[key];
390
+ if (value === undefined)
391
+ continue;
392
+ if (!Number.isFinite(value) || value <= 0 || value > pack[key] || (key !== 'maxCostUsd' && !Number.isInteger(value))) {
393
+ throw new Error(`Live decision pattern limit ${key} may only be tightened`);
394
+ }
395
+ next[key] = value;
396
+ }
397
+ if (requested.allowUnknownCost !== undefined && requested.allowUnknownCost !== false)
398
+ throw new Error('Live decision pattern never admits unknown cost');
399
+ return next;
400
+ }
401
+ //# sourceMappingURL=playground.js.map
@@ -0,0 +1,43 @@
1
+ import { decisionBatchQuestionId } from '../batch.js';
2
+ import { OFFLINE_RECORDED_CREDENTIAL_REF, OFFLINE_RECORDED_MODEL } from './artifacts.js';
3
+ /**
4
+ * Offline Jev transport that replays sanitized recorded answers. It is the only
5
+ * transport the offline playground uses: the production Jev adapter still builds
6
+ * the request and validates and normalizes every recorded answer.
7
+ */
8
+ export class RecordedJevTransport {
9
+ aliases;
10
+ calls = [];
11
+ answers;
12
+ usage;
13
+ constructor(recordedEvidence, aliases) {
14
+ this.aliases = aliases;
15
+ const answers = recordedEvidence.answers;
16
+ this.answers = answers && typeof answers === 'object' && !Array.isArray(answers) ? structuredClone(answers) : {};
17
+ this.usage = recordedEvidence.usage === undefined ? undefined : structuredClone(recordedEvidence.usage);
18
+ }
19
+ fetch = (async (_url, init) => {
20
+ if (init?.signal?.aborted)
21
+ throw new DOMException('Aborted', 'AbortError');
22
+ const body = JSON.parse(String(init?.body ?? '{}'));
23
+ const byQuestionId = new Map(this.aliases.flatMap(alias => [[alias, alias], [decisionBatchQuestionId(alias), alias]]));
24
+ const answers = {};
25
+ const aliases = [];
26
+ for (const questionId of Object.keys(body.questions ?? {})) {
27
+ const alias = byQuestionId.get(questionId);
28
+ aliases.push(alias ?? questionId);
29
+ if (alias !== undefined && Object.prototype.hasOwnProperty.call(this.answers, alias))
30
+ answers[questionId] = structuredClone(this.answers[alias]);
31
+ }
32
+ this.calls.push({ aliases, model: body.model ?? '' });
33
+ // Unrecorded usage is reported as an empty usage object, which the adapter normalizes to null counts.
34
+ return new Response(JSON.stringify({ answers, model: OFFLINE_RECORDED_MODEL, usage: this.usage ?? {} }), { status: 200, headers: { 'content-type': 'application/json' } });
35
+ });
36
+ }
37
+ /** Resolves only the offline logical reference, to a fixed non-secret marker. */
38
+ export async function resolveOfflineRecordedCredential(logicalRef) {
39
+ if (logicalRef !== OFFLINE_RECORDED_CREDENTIAL_REF)
40
+ throw new Error('Offline playground resolves only its recorded-fixture reference');
41
+ return new TextEncoder().encode('offline-recorded-fixture');
42
+ }
43
+ //# sourceMappingURL=recorded.js.map
@@ -0,0 +1,174 @@
1
+ const objectSchema = (properties, required = []) => ({
2
+ $schema: 'https://json-schema.org/draft/2020-12/schema', type: 'object', properties, required, additionalProperties: false,
3
+ });
4
+ const stringArray = { type: 'array', minItems: 1, uniqueItems: true, items: { type: 'string', minLength: 1 } };
5
+ const policy = { enum: ['allow', 'deny'] };
6
+ const decision = (alias, pointer = '/value') => ({ source: 'decision', alias, pointer });
7
+ const valueEq = (alias, right) => ({ op: 'eq', left: decision(alias), right });
8
+ const valueGte = (alias, right) => ({ op: 'gte', left: decision(alias), right });
9
+ const valueLte = (alias, right) => ({ op: 'lte', left: decision(alias), right });
10
+ const valueExists = (alias) => ({ op: 'exists', left: decision(alias) });
11
+ const policyDeny = {
12
+ id: 'deterministic-deny', priority: 1000, when: { op: 'eq', left: { source: 'input', pointer: '/deterministicPolicy' }, right: 'deny' },
13
+ route: 'deny', reason: 'deterministic-policy-deny',
14
+ };
15
+ const choice = (...options) => ({ kind: 'choice', options: options.map(id => ({ id, description: `Synthetic evidence option: ${id}` })) });
16
+ const truth = (trueDescription, falseDescription) => ({ kind: 'truth-probability', trueDescription, falseDescription });
17
+ const notAccepted = { route: 'review', reason: 'evidence-not-accepted' };
18
+ export const PATTERN_SPECS = {
19
+ 'intent-routing': {
20
+ inputSchema: objectSchema({ authorizedCandidates: stringArray, request: { type: 'string' } }, ['authorizedCandidates']),
21
+ evaluations: [{ alias: 'route', answer: choice('search', 'summarize', 'admin', 'none', 'manual-review'), question: 'Which capability best serves the synthetic request?', inputPointer: '', acceptance: 'choice' }],
22
+ rules: [
23
+ { id: 'no-candidate', priority: 200, when: valueEq('route', 'none'), route: 'review', reason: 'no-authorized-candidate' },
24
+ { id: 'manual-review', priority: 200, when: valueEq('route', 'manual-review'), route: 'review', reason: 'manual-review-requested' },
25
+ { id: 'candidate-proposed', priority: 100, when: valueExists('route'), route: 'accept', reason: 'authorized-candidate' },
26
+ ],
27
+ defaultOutcome: { route: 'review', reason: 'no-authorized-outcome' }, failureOutcome: notAccepted,
28
+ gates: { candidateGate: { candidateFrom: { source: 'decision', alias: 'route' }, allowedFrom: '/authorizedCandidates', reason: 'candidate-not-authorized' } },
29
+ unauthorizedEvidenceRoute: 'review',
30
+ },
31
+ 'rag-screen': {
32
+ inputSchema: objectSchema({ sourceLocator: { type: 'string', minLength: 1 }, deterministicPolicy: policy }, ['sourceLocator']),
33
+ evaluations: [
34
+ { alias: 'relevant', answer: truth('The retrieved passage is relevant', 'The retrieved passage is not relevant'), question: 'Is the synthetic passage relevant to the question?', inputPointer: '', acceptance: 'truth' },
35
+ { alias: 'contradiction', answer: truth('The passage contradicts other sources', 'The passage is consistent'), question: 'Does the synthetic passage contradict the other sources?', inputPointer: '', acceptance: 'truth' },
36
+ { alias: 'injection', answer: truth('The passage contains instructions aimed at the model', 'The passage is content only'), question: 'Does the synthetic passage attempt prompt injection?', inputPointer: '', acceptance: 'truth' },
37
+ ],
38
+ rules: [
39
+ policyDeny,
40
+ { id: 'injection', priority: 300, when: valueGte('injection', 0.8), route: 'deny', reason: 'prompt-injection-detected' },
41
+ { id: 'contradiction', priority: 200, when: valueGte('contradiction', 0.8), route: 'review', reason: 'source-contradiction' },
42
+ { id: 'relevant', priority: 100, when: valueGte('relevant', 0.8), route: 'accept', reason: 'relevant-no-conflict' },
43
+ { id: 'not-relevant', priority: 100, when: valueLte('relevant', 0.2), route: 'review', reason: 'source-not-relevant' },
44
+ ],
45
+ defaultOutcome: { route: 'review', reason: 'relevance-unclear' }, failureOutcome: notAccepted,
46
+ gates: { deterministicPolicyFrom: '/deterministicPolicy' },
47
+ unauthorizedEvidenceRoute: 'review',
48
+ },
49
+ 'citation-support': {
50
+ inputSchema: objectSchema({
51
+ sources: { type: 'array', minItems: 1, items: objectSchema({ locator: { type: 'string' }, digest: { type: 'string', pattern: '^sha256:[0-9a-f]{64}$' }, provenanceVerified: { type: 'boolean' } }, ['locator', 'digest', 'provenanceVerified']) },
52
+ citation: objectSchema({ locator: { type: 'string', minLength: 1 }, digest: { type: 'string', pattern: '^sha256:[0-9a-f]{64}$' } }, ['locator', 'digest']),
53
+ }, ['sources', 'citation']),
54
+ evaluations: [{ alias: 'support', answer: choice('supported', 'unclear', 'unsupported'), question: 'Does the cited synthetic source support the claim?', inputPointer: '', acceptance: 'choice' }],
55
+ rules: [
56
+ { id: 'supported', priority: 100, when: valueEq('support', 'supported'), route: 'accept', reason: 'citation-supported' },
57
+ { id: 'unclear', priority: 100, when: valueEq('support', 'unclear'), route: 'review', reason: 'support-unclear' },
58
+ { id: 'unsupported', priority: 100, when: valueEq('support', 'unsupported'), route: 'review', reason: 'citation-unsupported' },
59
+ ],
60
+ defaultOutcome: { route: 'review', reason: 'support-unclear' }, failureOutcome: notAccepted,
61
+ gates: { candidateGate: { candidateFrom: { source: 'input', pointer: '/citation' }, allowedFrom: '/sources', matchKeys: ['locator', 'digest'], requireAllowed: { provenanceVerified: true }, labelKey: 'locator', reason: 'citation-provenance-unverified' } },
62
+ unauthorizedEvidenceRoute: 'review',
63
+ },
64
+ guardrails: {
65
+ inputSchema: objectSchema({ deterministicPolicy: policy, content: { type: 'string' } }, ['deterministicPolicy']),
66
+ evaluations: [{ alias: 'screen', answer: truth('Advisory screen permits the synthetic content', 'Advisory screen flags the synthetic content'), question: 'Does the synthetic content pass the advisory guardrail?', inputPointer: '', acceptance: 'truth' }],
67
+ rules: [
68
+ policyDeny,
69
+ { id: 'permit', priority: 100, when: valueGte('screen', 0.8), route: 'accept', reason: 'advisory-permit' },
70
+ { id: 'flagged', priority: 100, when: valueLte('screen', 0.2), route: 'deny', reason: 'guardrail-flagged' },
71
+ ],
72
+ defaultOutcome: { route: 'review', reason: 'guardrail-inconclusive' }, failureOutcome: notAccepted,
73
+ gates: { deterministicPolicyFrom: '/deterministicPolicy' },
74
+ unauthorizedEvidenceRoute: 'review',
75
+ },
76
+ 'tool-risk-preflight': {
77
+ inputSchema: objectSchema({ deterministicPolicy: policy, authorizedTools: { type: 'array', maxItems: 0 } }, ['deterministicPolicy', 'authorizedTools']),
78
+ evaluations: [{ alias: 'risk', answer: choice('allow', 'deny', 'review'), question: 'What is the advisory risk disposition of the synthetic tool call?', inputPointer: '', acceptance: 'choice' }],
79
+ rules: [
80
+ policyDeny,
81
+ { id: 'advisory-deny', priority: 200, when: valueEq('risk', 'deny'), route: 'deny', reason: 'advisory-deny' },
82
+ { id: 'advisory-review', priority: 200, when: valueEq('risk', 'review'), route: 'review', reason: 'advisory-review' },
83
+ { id: 'advisory-allow', priority: 100, when: valueEq('risk', 'allow'), route: 'accept', reason: 'advisory-allow' },
84
+ ],
85
+ defaultOutcome: { route: 'review', reason: 'advisory-review' }, failureOutcome: notAccepted,
86
+ gates: { deterministicPolicyFrom: '/deterministicPolicy' },
87
+ unauthorizedEvidenceRoute: 'review',
88
+ },
89
+ 'bounded-classification': {
90
+ inputSchema: objectSchema({ allowedOptions: stringArray }, ['allowedOptions']),
91
+ evaluations: [{ alias: 'category', answer: choice('bug', 'feature', 'sales', 'none'), question: 'Which category fits the synthetic ticket?', inputPointer: '', acceptance: 'choice' }],
92
+ rules: [
93
+ { id: 'none', priority: 200, when: valueEq('category', 'none'), route: 'review', reason: 'no-category' },
94
+ { id: 'category', priority: 100, when: valueExists('category'), route: 'accept', reason: 'authorized-candidate' },
95
+ ],
96
+ defaultOutcome: { route: 'review', reason: 'no-category' }, failureOutcome: notAccepted,
97
+ gates: { candidateGate: { candidateFrom: { source: 'decision', alias: 'category' }, allowedFrom: '/allowedOptions', reason: 'candidate-not-authorized' } },
98
+ unauthorizedEvidenceRoute: 'review',
99
+ },
100
+ 'ordinal-scoring': {
101
+ inputSchema: objectSchema({ report: { type: 'string', minLength: 1 } }, ['report']),
102
+ evaluations: [{ alias: 'severity', answer: { kind: 'ordinal-score', levels: ['low', 'medium', 'high'] }, question: 'How severe is the synthetic report?', inputPointer: '', acceptance: 'ordinal' }],
103
+ rules: [{ id: 'scored', priority: 100, when: valueExists('severity'), route: 'accept', reason: 'distribution-preserved' }],
104
+ defaultOutcome: { route: 'review', reason: 'incomplete-distribution' }, failureOutcome: notAccepted,
105
+ gates: {},
106
+ unauthorizedEvidenceRoute: 'review',
107
+ },
108
+ 'function-selection': {
109
+ inputSchema: objectSchema({
110
+ legalFunctions: stringArray, argumentSchemas: { type: 'object', minProperties: 1, additionalProperties: { type: 'object' } },
111
+ proposedArguments: { type: 'object' },
112
+ }, ['legalFunctions', 'argumentSchemas', 'proposedArguments']),
113
+ evaluations: [{ alias: 'function', answer: choice('lookup', 'deleteAll', 'none'), question: 'Which function fits the synthetic request?', inputPointer: '', acceptance: 'choice' }],
114
+ rules: [
115
+ { id: 'none', priority: 200, when: valueEq('function', 'none'), route: 'review', reason: 'no-function' },
116
+ { id: 'function', priority: 100, when: valueExists('function'), route: 'accept', reason: 'authorized-function' },
117
+ ],
118
+ defaultOutcome: { route: 'deny', reason: 'no-authorized-outcome' }, failureOutcome: { route: 'deny', reason: 'evidence-not-accepted' },
119
+ gates: {
120
+ candidateGate: { candidateFrom: { source: 'decision', alias: 'function' }, allowedFrom: '/legalFunctions', reason: 'function-not-authorized' },
121
+ argumentGate: { functionAlias: 'function', argumentsFrom: '/proposedArguments', schemasFrom: '/argumentSchemas', reason: 'arguments-invalid' },
122
+ },
123
+ unauthorizedEvidenceRoute: 'deny',
124
+ },
125
+ 'same-subject-batch': {
126
+ inputSchema: objectSchema({
127
+ subjects: objectSchema({ risk: { type: 'string', minLength: 1 }, route: { type: 'string', minLength: 1 }, urgent: { type: 'string', minLength: 1 } }, ['risk', 'route', 'urgent']),
128
+ record: objectSchema({ summary: { type: 'string', minLength: 1 } }, ['summary']),
129
+ }, ['subjects', 'record']),
130
+ evaluations: [
131
+ { alias: 'risk', answer: { kind: 'ordinal-score', levels: ['low', 'medium', 'high'] }, question: 'How risky is the synthetic case?', inputPointer: '/record', inputSchema: objectSchema({ summary: { type: 'string', minLength: 1 } }, ['summary']), acceptance: 'ordinal' },
132
+ { alias: 'route', answer: choice('self-serve', 'escalate'), question: 'Which queue fits the synthetic case?', inputPointer: '/record', inputSchema: objectSchema({ summary: { type: 'string', minLength: 1 } }, ['summary']), acceptance: 'choice' },
133
+ { alias: 'urgent', answer: truth('The synthetic case is time critical', 'The synthetic case can wait'), question: 'Is the synthetic case time critical?', inputPointer: '/record', inputSchema: objectSchema({ summary: { type: 'string', minLength: 1 } }, ['summary']), acceptance: 'truth' },
134
+ ],
135
+ rules: [
136
+ { id: 'escalate', priority: 200, when: valueEq('route', 'escalate'), route: 'review', reason: 'escalation-recommended' },
137
+ { id: 'answered', priority: 100, when: { all: [valueExists('risk'), valueExists('route'), valueExists('urgent')] }, route: 'accept', reason: 'same-subject-batch' },
138
+ ],
139
+ defaultOutcome: { route: 'review', reason: 'incomplete-batch' }, failureOutcome: notAccepted,
140
+ gates: { batchSubjectsFrom: '/subjects' },
141
+ unauthorizedEvidenceRoute: 'deny',
142
+ },
143
+ 'dependent-two-stage': {
144
+ inputSchema: objectSchema({ stageOne: { type: 'object' } }, ['stageOne']),
145
+ evaluations: [{ alias: 'stage', answer: choice('continue', 'stop'), question: 'Should the synthetic second stage run?', inputPointer: '', acceptance: 'choice' }],
146
+ rules: [{ id: 'stage', priority: 100, when: valueExists('stage'), route: 'review', reason: 'dependent-runtime-unavailable' }],
147
+ defaultOutcome: { route: 'review', reason: 'dependent-runtime-unavailable' }, failureOutcome: notAccepted,
148
+ gates: {},
149
+ unauthorizedEvidenceRoute: 'review',
150
+ },
151
+ 'durable-review': {
152
+ inputSchema: objectSchema({ reviewRequired: { const: true }, invocationId: { type: 'string', minLength: 1 }, resumeCount: { type: 'integer', minimum: 1, maximum: 5 } }, ['reviewRequired', 'invocationId', 'resumeCount']),
153
+ evaluations: [{ alias: 'review', answer: choice('route-to-reviewer', 'insufficient-context'), question: 'How should the synthetic review item be queued?', inputPointer: '', acceptance: 'choice' }],
154
+ rules: [
155
+ { id: 'insufficient', priority: 200, when: valueEq('review', 'insufficient-context'), route: 'review', reason: 'review-context-insufficient' },
156
+ { id: 'review', priority: 100, when: valueExists('review'), route: 'review', reason: 'durable-review-required' },
157
+ ],
158
+ defaultOutcome: { route: 'review', reason: 'durable-review-required' }, failureOutcome: notAccepted,
159
+ gates: { replay: { resumeCountFrom: '/resumeCount' } },
160
+ unauthorizedEvidenceRoute: 'review',
161
+ },
162
+ 'candidate-selection': {
163
+ inputSchema: objectSchema({ extractedCandidates: stringArray }, ['extractedCandidates']),
164
+ evaluations: [{ alias: 'candidate', answer: choice('alpha', 'beta', 'gamma', 'none'), question: 'Which extracted synthetic candidate fits best?', inputPointer: '', acceptance: 'choice' }],
165
+ rules: [
166
+ { id: 'none', priority: 200, when: valueEq('candidate', 'none'), route: 'review', reason: 'no-candidate' },
167
+ { id: 'candidate', priority: 100, when: valueExists('candidate'), route: 'accept', reason: 'authorized-candidate' },
168
+ ],
169
+ defaultOutcome: { route: 'review', reason: 'no-candidate' }, failureOutcome: notAccepted,
170
+ gates: { candidateGate: { candidateFrom: { source: 'decision', alias: 'candidate' }, allowedFrom: '/extractedCandidates', reason: 'candidate-not-authorized' } },
171
+ unauthorizedEvidenceRoute: 'review',
172
+ },
173
+ };
174
+ //# sourceMappingURL=specs.js.map
@@ -0,0 +1,2 @@
1
+ export const DECISION_PATTERN_PACK_VERSION = 'decision-pattern-pack/v1';
2
+ //# sourceMappingURL=types.js.map