@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.
- package/agentic/code/providers/capability-matrix.yaml +58 -6
- package/agentic/code/providers/model-capabilities.v1.json +34 -0
- package/agentic/code/providers/model-catalog.v1.json +55 -13
- package/bin/aiwg.mjs +2 -0
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/cli.js +23 -18
- package/dist/src/artifacts/enrichment/cli.js +1 -1
- package/dist/src/artifacts/fortemi-core-query-adapter.js +6 -2
- package/dist/src/artifacts/query-engine.js +14 -4
- package/dist/src/artifacts/views/cli.js +1 -1
- package/dist/src/audit/operator-decision.js +10 -0
- package/dist/src/auth/credential-store.js +127 -27
- package/dist/src/catalog/builtin-models.json +272 -40
- package/dist/src/catalog/cli.js +1 -1
- package/dist/src/catalog/cli.mjs +1 -1
- package/dist/src/cli/handlers/effect.js +872 -0
- package/dist/src/cli/handlers/help.js +2 -0
- package/dist/src/cli/handlers/index.js +4 -2
- package/dist/src/cli/handlers/models.js +41 -10
- package/dist/src/cli/handlers/ralph.js +1 -1
- package/dist/src/cli/handlers/sessions.js +113 -37
- package/dist/src/cli/handlers/subcommands.js +31 -0
- package/dist/src/cli/handlers/use.js +46 -9
- package/dist/src/cli/handlers/utilities.js +10 -0
- package/dist/src/cli/handlers/workspace.js +3 -1
- package/dist/src/cli/scope-resolver.js +112 -9
- package/dist/src/cli/services/deployment-verification.js +5 -0
- package/dist/src/config/aiwg-config.js +3 -0
- package/dist/src/decision/acceptance-rollout.js +93 -0
- package/dist/src/decision/acceptance.js +175 -0
- package/dist/src/decision/adapters/jev.js +234 -13
- package/dist/src/decision/adapters/llm-subagent.js +60 -12
- package/dist/src/decision/admission.js +550 -0
- package/dist/src/decision/batch-benchmark.js +47 -0
- package/dist/src/decision/batch-receipts/accounting.js +75 -0
- package/dist/src/decision/batch-receipts/export.js +40 -0
- package/dist/src/decision/batch-receipts/index.js +9 -0
- package/dist/src/decision/batch-receipts/protection.js +40 -0
- package/dist/src/decision/batch-receipts/receipt.js +44 -0
- package/dist/src/decision/batch-receipts/result-store.js +341 -0
- package/dist/src/decision/batch-receipts/store.js +362 -0
- package/dist/src/decision/batch-receipts/types.js +2 -0
- package/dist/src/decision/batch-receipts/validate.js +127 -0
- package/dist/src/decision/batch.js +68 -0
- package/dist/src/decision/calibration/index.js +4 -0
- package/dist/src/decision/calibration/receipts.js +130 -0
- package/dist/src/decision/calibration/registry.js +258 -0
- package/dist/src/decision/calibration/types.js +2 -0
- package/dist/src/decision/compile-cache/benchmark.js +62 -0
- package/dist/src/decision/compile-cache/compatibility.js +65 -0
- package/dist/src/decision/compile-cache/file-store.js +280 -0
- package/dist/src/decision/compile-cache/identity.js +26 -0
- package/dist/src/decision/compile-cache/index.js +9 -0
- package/dist/src/decision/compile-cache/prefix.js +21 -0
- package/dist/src/decision/compile-cache/records.js +86 -0
- package/dist/src/decision/compile-cache/runtime.js +68 -0
- package/dist/src/decision/compile-cache/store.js +108 -0
- package/dist/src/decision/compile-cache/types.js +2 -0
- package/dist/src/decision/context-plan.js +234 -0
- package/dist/src/decision/context-qualification.js +60 -0
- package/dist/src/decision/ensemble/aggregate.js +215 -0
- package/dist/src/decision/ensemble/contract.js +316 -0
- package/dist/src/decision/ensemble/drift.js +51 -0
- package/dist/src/decision/ensemble/index.js +6 -0
- package/dist/src/decision/ensemble/report.js +71 -0
- package/dist/src/decision/ensemble/types.js +2 -0
- package/dist/src/decision/evaluate.js +1024 -78
- package/dist/src/decision/file-lifecycle-store.js +168 -0
- package/dist/src/decision/graph-beam.js +40 -0
- package/dist/src/decision/graph-budget.js +110 -0
- package/dist/src/decision/graph-decision-bridge.js +93 -0
- package/dist/src/decision/graph-evidence.js +123 -0
- package/dist/src/decision/graph-flow-adapter.js +56 -0
- package/dist/src/decision/graph-flow.js +105 -0
- package/dist/src/decision/graph-parallel.js +79 -0
- package/dist/src/decision/graph-patterns.js +20 -0
- package/dist/src/decision/graph-run-store.js +66 -0
- package/dist/src/decision/graph-run.js +166 -0
- package/dist/src/decision/graph-skill-bridge.js +91 -0
- package/dist/src/decision/graph-templates.js +56 -0
- package/dist/src/decision/graph.js +115 -0
- package/dist/src/decision/index.js +35 -0
- package/dist/src/decision/job-accounting.js +93 -0
- package/dist/src/decision/job-contract.js +186 -0
- package/dist/src/decision/job-effects.js +95 -0
- package/dist/src/decision/job-evaluate.js +80 -0
- package/dist/src/decision/job-gateway.js +121 -0
- package/dist/src/decision/job-http.js +194 -0
- package/dist/src/decision/job-payload-store.js +258 -0
- package/dist/src/decision/job-poll-limiter.js +92 -0
- package/dist/src/decision/job-quota.js +158 -0
- package/dist/src/decision/job-runtime.js +240 -0
- package/dist/src/decision/job-scheduler.js +26 -0
- package/dist/src/decision/job-service.js +117 -0
- package/dist/src/decision/job-store.js +264 -0
- package/dist/src/decision/job-worker.js +141 -0
- package/dist/src/decision/lifecycle.js +148 -0
- package/dist/src/decision/patterns/artifacts.js +173 -0
- package/dist/src/decision/patterns/catalog.js +108 -0
- package/dist/src/decision/patterns/durable-review.js +237 -0
- package/dist/src/decision/patterns/index.js +8 -0
- package/dist/src/decision/patterns/playground.js +401 -0
- package/dist/src/decision/patterns/recorded.js +43 -0
- package/dist/src/decision/patterns/specs.js +174 -0
- package/dist/src/decision/patterns/types.js +2 -0
- package/dist/src/decision/portable-secrets.js +53 -0
- package/dist/src/decision/projection.js +210 -0
- package/dist/src/decision/qualification/capture.js +67 -0
- package/dist/src/decision/qualification/drift.js +68 -0
- package/dist/src/decision/qualification/gate-evidence.js +172 -0
- package/dist/src/decision/qualification/gates.js +83 -0
- package/dist/src/decision/qualification/index.js +12 -0
- package/dist/src/decision/qualification/load.js +282 -0
- package/dist/src/decision/qualification/manifest.js +37 -0
- package/dist/src/decision/qualification/privacy.js +41 -0
- package/dist/src/decision/qualification/quality.js +213 -0
- package/dist/src/decision/qualification/release.js +116 -0
- package/dist/src/decision/qualification/runner.js +408 -0
- package/dist/src/decision/qualification/types.js +2 -0
- package/dist/src/decision/receipts.js +30 -7
- package/dist/src/decision/result-cache/index.js +6 -0
- package/dist/src/decision/result-cache/integrity.js +28 -0
- package/dist/src/decision/result-cache/key.js +28 -0
- package/dist/src/decision/result-cache/service.js +92 -0
- package/dist/src/decision/result-cache/store.js +327 -0
- package/dist/src/decision/result-cache/types.js +3 -0
- package/dist/src/decision/review/authorization.js +110 -0
- package/dist/src/decision/review/effect-ledger-adapter.js +321 -0
- package/dist/src/decision/review/index.js +12 -0
- package/dist/src/decision/review/migrate.js +34 -0
- package/dist/src/decision/review/operator-audit.js +59 -0
- package/dist/src/decision/review/recovery-adapters.js +252 -0
- package/dist/src/decision/review/recovery.js +33 -0
- package/dist/src/decision/review/service.js +498 -0
- package/dist/src/decision/review/store.js +219 -0
- package/dist/src/decision/review/types.js +2 -0
- package/dist/src/decision/review/validate.js +205 -0
- package/dist/src/decision/scheduler.js +115 -0
- package/dist/src/decision/telemetry/context.js +42 -0
- package/dist/src/decision/telemetry/debug-sidecar.js +152 -0
- package/dist/src/decision/telemetry/exporter.js +77 -0
- package/dist/src/decision/telemetry/file-debug-backend.js +112 -0
- package/dist/src/decision/telemetry/index.js +13 -0
- package/dist/src/decision/telemetry/mapping.js +146 -0
- package/dist/src/decision/telemetry/metrics.js +164 -0
- package/dist/src/decision/telemetry/otlp-http.js +183 -0
- package/dist/src/decision/telemetry/redaction.js +93 -0
- package/dist/src/decision/telemetry/retention.js +109 -0
- package/dist/src/decision/telemetry/runtime.js +310 -0
- package/dist/src/decision/telemetry/trace.js +158 -0
- package/dist/src/decision/telemetry/types.js +3 -0
- package/dist/src/decision/validate.js +135 -5
- package/dist/src/effects/checkpoint-sinks.js +94 -0
- package/dist/src/effects/checkpoint.js +76 -0
- package/dist/src/effects/decision-links.js +54 -0
- package/dist/src/effects/errors.js +48 -0
- package/dist/src/effects/identity.js +178 -0
- package/dist/src/effects/index.js +25 -0
- package/dist/src/effects/keyring.js +160 -0
- package/dist/src/effects/keys.js +130 -0
- package/dist/src/effects/ledger.js +435 -0
- package/dist/src/effects/lifecycle.js +110 -0
- package/dist/src/effects/lock-recovery.js +269 -0
- package/dist/src/effects/reader.js +103 -0
- package/dist/src/effects/records.js +114 -0
- package/dist/src/effects/redaction.js +51 -0
- package/dist/src/effects/schema.js +73 -0
- package/dist/src/effects/store.js +239 -0
- package/dist/src/effects/types.js +49 -0
- package/dist/src/effects/verifiers/decision.js +168 -0
- package/dist/src/effects/verifiers/file.js +106 -0
- package/dist/src/effects/verifiers/git.js +206 -0
- package/dist/src/effects/verifiers/index.js +90 -0
- package/dist/src/effects/verifiers/registry.js +148 -0
- package/dist/src/effects/verifiers/review.js +123 -0
- package/dist/src/effects/verifiers/tracker.js +299 -0
- package/dist/src/effects/verifiers/types.js +21 -0
- package/dist/src/effects/verify.js +295 -0
- package/dist/src/evidence/bundle.js +2 -0
- package/dist/src/extensions/commands/definitions.js +28 -0
- package/dist/src/extensions/manifest.js +1 -0
- package/dist/src/marketplace/provenance.js +25 -27
- package/dist/src/mcp/cli.mjs +11 -1
- package/dist/src/mcp/grok-build-config.mjs +189 -0
- package/dist/src/mcp/registry.js +21 -1
- package/dist/src/mcp/registry.mjs +20 -1
- package/dist/src/models/grok-build-models.js +356 -0
- package/dist/src/models/index.js +1 -0
- package/dist/src/models/model-capabilities.v1.json +34 -0
- package/dist/src/models/model-catalog.v1.json +55 -13
- package/dist/src/models/model-discovery.js +41 -5
- package/dist/src/models/provider-policy.js +3 -2
- package/dist/src/plugin/skill-command-translator.js +1 -0
- package/dist/src/providers/capability-matrix.yaml +58 -6
- package/dist/src/providers/grok-build-acp.js +192 -0
- package/dist/src/providers/grok-build-dispatch.js +251 -0
- package/dist/src/providers/grok-build-headless.js +170 -0
- package/dist/src/providers/muse-paths.js +105 -0
- package/dist/src/providers/provider-definitions.js +93 -7
- package/dist/src/providers/provider-definitions.mjs +21 -1
- package/dist/src/providers/provider-inventory.js +3 -0
- package/dist/src/security/artifact-attestation.js +4 -8
- package/dist/src/security/signing.js +119 -0
- package/dist/src/sessions/adapters/grok-build.js +187 -0
- package/dist/src/sessions/adapters/muse.js +412 -0
- package/dist/src/sessions/contracts.js +1 -1
- package/dist/src/sessions/grok-build-cli.js +196 -0
- package/dist/src/sessions/index.js +3 -0
- package/dist/src/sessions/workspace-discovery.js +9 -5
- package/dist/src/skills/deployer.js +33 -5
- package/dist/src/smiths/context-pipeline/claude-hook.js +2 -0
- package/dist/src/smiths/context-pipeline/context-file-safety.js +18 -0
- package/dist/src/smiths/context-pipeline/finalization.js +1 -32
- package/dist/src/smiths/context-pipeline/generator.js +12 -0
- package/dist/src/smiths/context-pipeline/index.js +1 -1
- package/dist/src/smiths/context-pipeline/managed-hook.js +2 -0
- package/dist/src/smiths/context-pipeline/muse-bridge.js +50 -0
- package/dist/src/smiths/context-pipeline/provider-policy.js +5 -0
- package/dist/src/smiths/context-pipeline/workspace-context.js +22 -16
- package/dist/src/smiths/hook-bridge/grok-build-translator.js +64 -0
- package/dist/src/smiths/hook-bridge/index.js +3 -0
- package/dist/src/smiths/hook-bridge/shim.js +2 -0
- package/dist/src/smiths/hook-bridge/types.js +5 -0
- package/dist/src/smiths/skillsmith/collision-detector.js +1 -0
- package/dist/src/storage/protected-files.js +102 -0
- package/dist/src/tracker/forge-http.js +371 -0
- package/dist/src/tracker/project-inputs.js +41 -0
- package/package.json +2 -1
- package/schemas/decision/BatchReceipt.v1.schema.json +50 -0
- package/schemas/decision/CalibrationArtifact.v1.schema.json +76 -0
- package/schemas/decision/CalibrationCompatibility.v1.schema.json +18 -0
- package/schemas/decision/CalibrationGovernanceReceipt.v1.schema.json +44 -0
- package/schemas/decision/DecisionAdmissionEvidence.v1.schema.json +43 -0
- package/schemas/decision/DecisionBinding.schema.json +309 -0
- package/schemas/decision/DecisionBinding.v1alpha2.schema.json +369 -0
- package/schemas/decision/DecisionCacheTelemetry.v1.schema.json +20 -0
- package/schemas/decision/DecisionChampionChallenger.v1.schema.json +100 -0
- package/schemas/decision/DecisionCompileCacheEntry.v1.schema.json +35 -0
- package/schemas/decision/DecisionCompileCacheIdentity.v1.schema.json +26 -0
- package/schemas/decision/DecisionCompileCacheTombstone.v1.schema.json +31 -0
- package/schemas/decision/DecisionContextEvidence.v1.schema.json +136 -0
- package/schemas/decision/DecisionDefinition.schema.json +159 -0
- package/schemas/decision/DecisionDefinition.v1alpha2.schema.json +203 -0
- package/schemas/decision/DecisionDriftResponse.v1.schema.json +55 -0
- package/schemas/decision/DecisionEnsembleAggregate.v1.schema.json +86 -0
- package/schemas/decision/DecisionEnsembleIntegrityReport.v1.schema.json +64 -0
- package/schemas/decision/DecisionEnsemblePolicy.v1.schema.json +130 -0
- package/schemas/decision/DecisionGraph.v1.schema.json +77 -0
- package/schemas/decision/DecisionJob.v1.schema.json +71 -0
- package/schemas/decision/DecisionLifecyclePolicy.v1.schema.json +52 -0
- package/schemas/decision/DecisionProjectionPolicy.v1.schema.json +65 -0
- package/schemas/decision/DecisionProviderPrefixCompatibility.v1.schema.json +45 -0
- package/schemas/decision/DecisionProviderPrefixEvidence.v1.schema.json +17 -0
- package/schemas/decision/DecisionProviderPrefixIdentity.v1.schema.json +36 -0
- package/schemas/decision/DecisionResult.schema.json +524 -0
- package/schemas/decision/DecisionResult.v1alpha2.schema.json +619 -0
- package/schemas/decision/DecisionResultCacheEntry.v1.schema.json +53 -0
- package/schemas/decision/DecisionReview.schema.json +29 -0
- package/schemas/decision/DecisionRuleset.schema.json +313 -0
- package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +313 -0
- package/schemas/decision/DecisionTelemetryTrace.v1.schema.json +95 -0
- package/schemas/decision/RulesetResult.schema.json +721 -0
- package/schemas/decision/RulesetResult.v1alpha2.schema.json +891 -0
- package/schemas/security/pr-evidence-receipt.v1.schema.json +77 -0
- package/schemas/security/threat-assessment-report.v1.schema.json +71 -1
- package/tools/agents/deploy-agents.mjs +21 -6
- package/tools/agents/providers/base.mjs +7 -4
- package/tools/agents/providers/codex.mjs +1 -1
- package/tools/agents/providers/grok-build.d.mts +7 -0
- package/tools/agents/providers/grok-build.mjs +249 -9
- package/tools/agents/providers/model-role.mjs +6 -3
- package/tools/agents/providers/muse-hooks.mjs +785 -0
- package/tools/agents/providers/muse-paths.mjs +112 -0
- package/tools/agents/providers/muse.mjs +336 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
2
|
+
import { artifactDigest, validateBinding, validateDefinition, validateRuleset } from '../validate.js';
|
|
3
|
+
import { PATTERN_SPECS } from './specs.js';
|
|
4
|
+
const PREFIX = 'aiwg://decision-patterns/';
|
|
5
|
+
const KINDS = new Set(['definition', 'input-schema', 'output-schema', 'candidate-policy', 'ruleset', 'offline-binding', 'live-binding-template', 'expected-receipt', 'readme']);
|
|
6
|
+
const PATTERNS = new Set(Object.keys(PATTERN_SPECS));
|
|
7
|
+
/** Logical credential for the recorded offline transport. It resolves to a fixed, non-secret marker. */
|
|
8
|
+
export const OFFLINE_RECORDED_CREDENTIAL_REF = 'offline.recorded-fixture';
|
|
9
|
+
export const OFFLINE_RECORDED_MODEL = 'offline:recorded-fixture';
|
|
10
|
+
export const LIVE_TEMPLATE_MODEL = 'jev:explicit-opt-in-required';
|
|
11
|
+
const objectSchema = (properties, required = []) => ({ $schema: 'https://json-schema.org/draft/2020-12/schema', type: 'object', properties, required, additionalProperties: false });
|
|
12
|
+
const nested = (properties, required = []) => ({ type: 'object', properties, required, additionalProperties: false });
|
|
13
|
+
const probability = { type: 'number', minimum: 0, maximum: 1 };
|
|
14
|
+
const probabilities = { type: 'object', minProperties: 1, additionalProperties: probability };
|
|
15
|
+
const pointer = { type: 'string', pattern: '^(/.*)?$' };
|
|
16
|
+
/** Sanitized recorded Jev answers, keyed by ruleset alias. */
|
|
17
|
+
function recordedAnswerSchema(evaluation) {
|
|
18
|
+
switch (evaluation.answer.kind) {
|
|
19
|
+
case 'choice': return nested({ type: { const: 'choice' }, choice: { type: 'string' }, probabilities, confidence: probability }, ['type', 'choice', 'probabilities', 'confidence']);
|
|
20
|
+
case 'ordinal-score': return nested({ type: { const: 'score' }, score: { type: 'number', minimum: 0 }, probabilities, legend: { type: 'object' }, confidence: probability }, ['type', 'score', 'probabilities', 'legend', 'confidence']);
|
|
21
|
+
case 'truth-probability': return nested({ type: { const: 'noul' }, noul: probability }, ['type', 'noul']);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function outputSchemaFor(id) {
|
|
25
|
+
const spec = PATTERN_SPECS[id];
|
|
26
|
+
return objectSchema({
|
|
27
|
+
answers: nested(Object.fromEntries(spec.evaluations.map(evaluation => [evaluation.alias, recordedAnswerSchema(evaluation)])), spec.evaluations.map(evaluation => evaluation.alias)),
|
|
28
|
+
usage: nested({ input_tokens: { type: 'integer', minimum: 0 }, output_tokens: { type: 'integer', minimum: 0 } }, ['input_tokens', 'output_tokens']),
|
|
29
|
+
}, ['answers']);
|
|
30
|
+
}
|
|
31
|
+
const decisionSource = { type: 'object', properties: { source: { const: 'decision' }, alias: { type: 'string', minLength: 1 } }, required: ['source', 'alias'], additionalProperties: false };
|
|
32
|
+
const inputSource = { type: 'object', properties: { source: { const: 'input' }, pointer }, required: ['source', 'pointer'], additionalProperties: false };
|
|
33
|
+
export const PATTERN_CANDIDATE_POLICY_SCHEMA = objectSchema({
|
|
34
|
+
schema: { const: 'decision-pattern-candidate-policy/v2' }, patternId: { type: 'string', enum: [...PATTERNS] }, patternVersion: { type: 'string', pattern: '^\\d+\\.\\d+\\.\\d+$' },
|
|
35
|
+
authority: { const: 'deterministic-code' }, authorizedCandidatesFrom: { type: 'string', minLength: 1 }, evidenceMayExpandCandidates: { const: false }, actionsExecute: { const: false },
|
|
36
|
+
deterministicDenyOverrides: { const: true }, unauthorizedEvidenceRoute: { enum: ['review', 'deny'] },
|
|
37
|
+
deterministicPolicyFrom: pointer,
|
|
38
|
+
candidateGate: nested({
|
|
39
|
+
candidateFrom: { oneOf: [decisionSource, inputSource] }, allowedFrom: pointer,
|
|
40
|
+
matchKeys: { type: 'array', minItems: 1, items: { type: 'string', minLength: 1 } }, requireAllowed: { type: 'object' },
|
|
41
|
+
labelKey: { type: 'string', minLength: 1 }, reason: { type: 'string', minLength: 1 },
|
|
42
|
+
}, ['candidateFrom', 'allowedFrom', 'reason']),
|
|
43
|
+
argumentGate: nested({ functionAlias: { type: 'string', minLength: 1 }, argumentsFrom: pointer, schemasFrom: pointer, reason: { type: 'string', minLength: 1 } }, ['functionAlias', 'argumentsFrom', 'schemasFrom', 'reason']),
|
|
44
|
+
batchSubjectsFrom: pointer,
|
|
45
|
+
replay: nested({ resumeCountFrom: pointer }, ['resumeCountFrom']),
|
|
46
|
+
}, ['schema', 'patternId', 'patternVersion', 'authority', 'authorizedCandidatesFrom', 'evidenceMayExpandCandidates', 'actionsExecute', 'deterministicDenyOverrides', 'unauthorizedEvidenceRoute']);
|
|
47
|
+
export function decisionPatternArtifactUri(id, version, kind, alias) {
|
|
48
|
+
return `${PREFIX}${id}/${version}/${kind}${alias ? `.${alias}` : ''}`;
|
|
49
|
+
}
|
|
50
|
+
/** Resolve an artifact entirely from the installed module, without checkout-relative paths or I/O. */
|
|
51
|
+
export function resolveDecisionPatternArtifact(reference) {
|
|
52
|
+
if (!reference.startsWith(PREFIX))
|
|
53
|
+
throw new Error('Unsupported decision pattern artifact reference');
|
|
54
|
+
const parts = reference.slice(PREFIX.length).split('/');
|
|
55
|
+
if (parts.length !== 3)
|
|
56
|
+
throw new Error('Malformed decision pattern artifact reference');
|
|
57
|
+
const [patternId, patternVersion, rawKindWithAlias] = parts;
|
|
58
|
+
const [rawKind, alias, ...extra] = (rawKindWithAlias ?? '').split('.');
|
|
59
|
+
if (!PATTERNS.has(patternId) || !/^\d+\.\d+\.\d+$/.test(patternVersion ?? '') || !KINDS.has(rawKind) || extra.length)
|
|
60
|
+
throw new Error('Malformed decision pattern artifact reference');
|
|
61
|
+
const id = patternId;
|
|
62
|
+
const kind = rawKind;
|
|
63
|
+
if ((kind === 'definition') !== (alias !== undefined) || (alias !== undefined && !PATTERN_SPECS[id].evaluations.some(evaluation => evaluation.alias === alias))) {
|
|
64
|
+
throw new Error('Malformed decision pattern artifact reference');
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
schema: 'decision-pattern-artifact/v1', patternId: id, patternVersion: patternVersion, kind, ...(alias ? { alias } : {}),
|
|
68
|
+
mediaType: kind === 'readme' ? 'text/markdown' : 'application/json', content: artifactContent(id, patternVersion, kind, alias),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const governedCache = new Map();
|
|
72
|
+
/** Governed runtime artifacts for one pack, as consumed by `evaluateDecisionRuleset`. Callers receive a private copy. */
|
|
73
|
+
export function governedPatternArtifacts(id, version) {
|
|
74
|
+
const key = `${id}@${version}`;
|
|
75
|
+
if (!governedCache.has(key))
|
|
76
|
+
governedCache.set(key, buildGovernedArtifacts(id, version));
|
|
77
|
+
return structuredClone(governedCache.get(key));
|
|
78
|
+
}
|
|
79
|
+
function buildGovernedArtifacts(id, version) {
|
|
80
|
+
const definitions = Object.fromEntries(PATTERN_SPECS[id].evaluations.map(evaluation => {
|
|
81
|
+
const definition = definitionFor(id, version, evaluation);
|
|
82
|
+
return [definition.metadata.id, definition];
|
|
83
|
+
}));
|
|
84
|
+
const ruleset = rulesetFor(id, version, definitions);
|
|
85
|
+
return { definitions, ruleset, offlineBinding: bindingFor(id, version, ruleset, false), liveBindingTemplate: bindingFor(id, version, ruleset, true) };
|
|
86
|
+
}
|
|
87
|
+
let validatePolicy;
|
|
88
|
+
/** Validate pins, schemas and cross-artifact references using the production decision validators. */
|
|
89
|
+
export function validateGovernedPatternArtifacts(id, version) {
|
|
90
|
+
const { definitions, ruleset, offlineBinding, liveBindingTemplate } = governedPatternArtifacts(id, version);
|
|
91
|
+
Object.values(definitions).forEach(validateDefinition);
|
|
92
|
+
validateRuleset(ruleset);
|
|
93
|
+
validateBinding(offlineBinding, ruleset);
|
|
94
|
+
validateBinding(liveBindingTemplate, ruleset);
|
|
95
|
+
validatePolicy ??= new Ajv2020({ strict: true, allErrors: true }).compile(PATTERN_CANDIDATE_POLICY_SCHEMA);
|
|
96
|
+
if (!validatePolicy(candidatePolicyFor(id, version)))
|
|
97
|
+
throw new Error('candidate policy is invalid');
|
|
98
|
+
}
|
|
99
|
+
export function patternInputSchema(id) { return structuredClone(PATTERN_SPECS[id].inputSchema); }
|
|
100
|
+
export function patternOutputSchema(id) { return outputSchemaFor(id); }
|
|
101
|
+
function artifactContent(id, version, kind, alias) {
|
|
102
|
+
const artifacts = governedPatternArtifacts(id, version);
|
|
103
|
+
switch (kind) {
|
|
104
|
+
case 'definition': return Object.values(artifacts.definitions).find(definition => definition.metadata.id === definitionId(id, alias));
|
|
105
|
+
case 'input-schema': return patternInputSchema(id);
|
|
106
|
+
case 'output-schema': return outputSchemaFor(id);
|
|
107
|
+
case 'candidate-policy': return candidatePolicyFor(id, version);
|
|
108
|
+
case 'ruleset': return artifacts.ruleset;
|
|
109
|
+
case 'offline-binding': return artifacts.offlineBinding;
|
|
110
|
+
case 'live-binding-template': return artifacts.liveBindingTemplate;
|
|
111
|
+
case 'expected-receipt': return { schema: 'decision-pattern-receipt/v2', pattern: { id, version }, executionMode: 'offline-recorded', evidenceOrigin: 'sanitized-recorded-fixture', runtime: { evaluator: 'evaluateDecisionRuleset', adapter: 'jev@1.0.0', transport: 'recorded-replay' }, action: { status: 'unexecuted' } };
|
|
112
|
+
case 'readme': return `# ${id}\n\nOffline fixtures replay sanitized recorded Jev answers through the production decision evaluator. Recorded evidence is illustrative, not qualification, universal calibration, permission, or proof of deterministic model behavior. Failures abstain or route to review. Disable ${id}@${version} from discovery and retain receipts.\n`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const definitionId = (id, alias) => `pattern.${id}.${alias}`;
|
|
116
|
+
function definitionFor(id, version, evaluation) {
|
|
117
|
+
return { apiVersion: 'decision.aiwg.io/v1alpha2', kind: 'DecisionDefinition', metadata: { id: definitionId(id, evaluation.alias), version, description: `Governed ${id} ${evaluation.alias} evidence definition` },
|
|
118
|
+
spec: { purpose: `Collect bounded advisory evidence for ${id}`, inputSchema: evaluation.inputSchema ?? PATTERN_SPECS[id].inputSchema, question: evaluation.question, answer: evaluation.answer, requiredCapabilities: [] } };
|
|
119
|
+
}
|
|
120
|
+
function rulesetFor(id, version, definitions) {
|
|
121
|
+
const spec = PATTERN_SPECS[id];
|
|
122
|
+
const outputSchema = objectSchema({ route: { enum: ['accept', 'review', 'deny'] }, reason: { type: 'string', minLength: 1 }, action: { const: 'unexecuted' } }, ['route', 'reason', 'action']);
|
|
123
|
+
const outcome = (value) => ({ route: value.route, reason: value.reason, action: 'unexecuted' });
|
|
124
|
+
return { apiVersion: 'decision.aiwg.io/v1alpha2', kind: 'DecisionRuleset', metadata: { id: `pattern.${id}.ruleset`, version, description: `Deterministic policy boundary for ${id}` },
|
|
125
|
+
spec: { purpose: `Keep ${id} evidence subordinate to deterministic authority`, inputSchema: spec.inputSchema,
|
|
126
|
+
evaluations: spec.evaluations.map(evaluation => {
|
|
127
|
+
const definition = definitions[definitionId(id, evaluation.alias)];
|
|
128
|
+
return { alias: evaluation.alias, decision: { id: definition.metadata.id, version, digest: artifactDigest(definition) }, inputPointer: evaluation.inputPointer };
|
|
129
|
+
}),
|
|
130
|
+
rules: spec.rules.map(rule => ({ id: rule.id, priority: rule.priority, when: rule.when, outcome: outcome(rule) })),
|
|
131
|
+
composition: 'first-match', conflict: 'review', defaultOutcome: outcome(spec.defaultOutcome), failureOutcome: outcome(spec.failureOutcome), outputSchema } };
|
|
132
|
+
}
|
|
133
|
+
/** Primitive-aware acceptance. A truth probability of 0.5 always falls to review, never to an accept band or severity label. */
|
|
134
|
+
function acceptanceFor(profile) {
|
|
135
|
+
const review = { disposition: 'review' };
|
|
136
|
+
const base = { mode: 'primitive-policy', version: '1.0.0', precedence: 'first-match', calibration: 'advisory',
|
|
137
|
+
defaultRoute: review, missingEvidenceRoute: review, invalidEvidenceRoute: review, tieRoute: review };
|
|
138
|
+
if (profile === 'truth') {
|
|
139
|
+
return { ...base, compatibleUncertaintyProfiles: ['typesafe-truth-v1'], rules: [
|
|
140
|
+
{ id: 'confident-true', primitive: 'truth-probability', all: [{ metric: 'yes-probability', op: 'gte', thresholdBps: 8000 }], route: { disposition: 'act' } },
|
|
141
|
+
{ id: 'confident-false', primitive: 'truth-probability', all: [{ metric: 'yes-probability', op: 'lte', thresholdBps: 2000 }], route: { disposition: 'act' } },
|
|
142
|
+
] };
|
|
143
|
+
}
|
|
144
|
+
if (profile === 'ordinal') {
|
|
145
|
+
return { ...base, compatibleUncertaintyProfiles: ['typesafe-distribution-v1'], rules: [
|
|
146
|
+
{ id: 'concentrated', primitive: 'ordinal-score', all: [{ metric: 'dispersion', op: 'lte', thresholdBps: 2000 }], route: { disposition: 'act' } },
|
|
147
|
+
] };
|
|
148
|
+
}
|
|
149
|
+
return { ...base, compatibleUncertaintyProfiles: ['typesafe-distribution-v1'], rules: [
|
|
150
|
+
{ id: 'selected-confident', primitive: 'choice', all: [{ metric: 'selected-probability', op: 'gte', thresholdBps: 8000 }], route: { disposition: 'act' } },
|
|
151
|
+
] };
|
|
152
|
+
}
|
|
153
|
+
function bindingFor(id, version, ruleset, live) {
|
|
154
|
+
const spec = PATTERN_SPECS[id];
|
|
155
|
+
const target = (evaluation) => ({
|
|
156
|
+
adapter: 'jev', adapterVersion: '1.0.0', model: live ? LIVE_TEMPLATE_MODEL : OFFLINE_RECORDED_MODEL,
|
|
157
|
+
credentialRef: live ? 'typesafe.jev.playground' : OFFLINE_RECORDED_CREDENTIAL_REF,
|
|
158
|
+
requiredCapabilities: [], acceptance: acceptanceFor(evaluation.acceptance), timeoutMs: live ? 15_000 : 5_000,
|
|
159
|
+
retry: { maxRetries: 0, initialDelayMs: 1, maxDelayMs: 1 },
|
|
160
|
+
});
|
|
161
|
+
return { apiVersion: 'decision.aiwg.io/v1alpha2', kind: 'DecisionBinding', metadata: { id: `pattern.${id}.${live ? 'live' : 'offline'}`, version, description: `${live ? 'Disabled opt-in live' : 'Recorded offline'} binding for ${id}` },
|
|
162
|
+
spec: { ruleset: { id: ruleset.metadata.id, version, digest: artifactDigest(ruleset) }, totalTimeoutMs: live ? 15_000 : 10_000, maxAttempts: spec.evaluations.length, concurrency: 1,
|
|
163
|
+
evaluations: Object.fromEntries(spec.evaluations.map(evaluation => [evaluation.alias, { targets: [target(evaluation)], fallbackOn: [] }])) } };
|
|
164
|
+
}
|
|
165
|
+
export function candidatePolicyFor(id, version) {
|
|
166
|
+
const spec = PATTERN_SPECS[id];
|
|
167
|
+
const gate = spec.gates.candidateGate;
|
|
168
|
+
return { schema: 'decision-pattern-candidate-policy/v2', patternId: id, patternVersion: version, authority: 'deterministic-code',
|
|
169
|
+
authorizedCandidatesFrom: gate?.allowedFrom ?? 'deterministic-policy', evidenceMayExpandCandidates: false, actionsExecute: false,
|
|
170
|
+
deterministicDenyOverrides: true, unauthorizedEvidenceRoute: spec.unauthorizedEvidenceRoute,
|
|
171
|
+
...structuredClone(spec.gates) };
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=artifacts.js.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { decisionPatternArtifactUri } from './artifacts.js';
|
|
2
|
+
import { PATTERN_SPECS } from './specs.js';
|
|
3
|
+
const fixture = (id, input, recordedEvidence, route, reason) => ({
|
|
4
|
+
id, subjectId: `synthetic:${id}`, input, recordedEvidence, expected: { route, reason },
|
|
5
|
+
});
|
|
6
|
+
/** Sanitized recorded Jev Choice answer; the unselected mass is spread evenly. */
|
|
7
|
+
export function recordedChoice(options, selected, probability) {
|
|
8
|
+
const rest = options.length > 1 ? (1 - probability) / (options.length - 1) : 0;
|
|
9
|
+
return { type: 'choice', choice: selected, probabilities: Object.fromEntries(options.map(option => [option, option === selected ? probability : rest])), confidence: probability };
|
|
10
|
+
}
|
|
11
|
+
/** Sanitized recorded Jev Noul (truth-probability) answer. */
|
|
12
|
+
export function recordedNoul(probability) { return { type: 'noul', noul: probability }; }
|
|
13
|
+
/** Sanitized recorded Jev Score answer; the score is the distribution's weighted mean. */
|
|
14
|
+
export function recordedScore(levels, distribution, confidence = 0.8) {
|
|
15
|
+
return { type: 'score', score: distribution.reduce((sum, probability, index) => sum + probability * index, 0),
|
|
16
|
+
probabilities: Object.fromEntries(distribution.map((probability, index) => [String(index), probability])),
|
|
17
|
+
legend: Object.fromEntries(levels.map((level, index) => [String(index), level])), confidence };
|
|
18
|
+
}
|
|
19
|
+
const options = (id, alias) => {
|
|
20
|
+
const answer = PATTERN_SPECS[id].evaluations.find(evaluation => evaluation.alias === alias).answer;
|
|
21
|
+
return answer.kind === 'choice' ? answer.options.map(option => option.id) : [];
|
|
22
|
+
};
|
|
23
|
+
const choiceFor = (id, alias, selected, probability) => recordedChoice(options(id, alias), selected, probability);
|
|
24
|
+
const answers = (value, usage) => ({ answers: value, ...(usage ? { usage } : {}) });
|
|
25
|
+
const LEVELS = ['low', 'medium', 'high'];
|
|
26
|
+
const lookupSchema = { lookup: { type: 'object', required: ['query'], properties: { query: { type: 'string' } }, additionalProperties: false } };
|
|
27
|
+
const source = { locator: 'doc:1#p2', digest: `sha256:${'a'.repeat(64)}`, provenanceVerified: true };
|
|
28
|
+
function pack(id, primitive, summary, fixtures, status = 'supported') {
|
|
29
|
+
const artifact = (kind) => decisionPatternArtifactUri(id, '1.0.0', kind);
|
|
30
|
+
const calls = PATTERN_SPECS[id].evaluations.length;
|
|
31
|
+
return {
|
|
32
|
+
schema: 'decision-pattern-pack/v1', id, version: '1.0.0', status, summary, primitive,
|
|
33
|
+
artifacts: {
|
|
34
|
+
definitions: PATTERN_SPECS[id].evaluations.map(evaluation => decisionPatternArtifactUri(id, '1.0.0', 'definition', evaluation.alias)),
|
|
35
|
+
inputSchema: artifact('input-schema'),
|
|
36
|
+
outputSchema: artifact('output-schema'), candidatePolicy: artifact('candidate-policy'),
|
|
37
|
+
ruleset: artifact('ruleset'), offlineBinding: artifact('offline-binding'),
|
|
38
|
+
...(status !== 'unavailable' ? { liveBindingTemplate: artifact('live-binding-template') } : {}),
|
|
39
|
+
expectedReceipt: artifact('expected-receipt'), readme: artifact('readme'),
|
|
40
|
+
},
|
|
41
|
+
fixtures,
|
|
42
|
+
limitations: ['Recorded evidence is illustrative, not workload qualification or universal calibration.', 'Typed output does not guarantee semantic correctness.'],
|
|
43
|
+
failurePath: 'Abstain or route to review; never infer permission from model evidence.',
|
|
44
|
+
rollback: `Remove ${id}@1.0.0 from discovery or restore its pinned predecessor; retain receipts.`,
|
|
45
|
+
...(status !== 'unavailable' ? { live: { syntheticOnly: true, credentialRef: 'typesafe:jev/playground', requiredEgressClass: 'synthetic-decision', limits: { maxCalls: calls, maxTokens: 1024 * calls, maxCostUsd: 0.02 * calls, allowUnknownCost: false, maxAttempts: 1, deadlineMs: 15_000 } } } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export const decisionPatternPacks = [
|
|
49
|
+
pack('intent-routing', 'choice', 'Route only among code-authorized capabilities, with none/review.', [
|
|
50
|
+
fixture('route-authorized', { authorizedCandidates: ['search', 'summarize'] }, answers({ route: choiceFor('intent-routing', 'route', 'search', 0.91) }), 'accept', 'authorized-candidate'),
|
|
51
|
+
fixture('route-unauthorized', { authorizedCandidates: ['summarize'] }, answers({ route: choiceFor('intent-routing', 'route', 'admin', 0.99) }), 'review', 'candidate-not-authorized'),
|
|
52
|
+
fixture('route-low-confidence', { authorizedCandidates: ['search', 'summarize'] }, answers({ route: choiceFor('intent-routing', 'route', 'search', 0.55) }), 'review', 'evidence-not-accepted'),
|
|
53
|
+
]),
|
|
54
|
+
pack('rag-screen', 'composite', 'Advisory relevance, contradiction, and injection evidence.', [
|
|
55
|
+
fixture('rag-relevant', { sourceLocator: 'doc:rag#1' }, answers({ relevant: recordedNoul(0.95), contradiction: recordedNoul(0.03), injection: recordedNoul(0.02) }), 'accept', 'relevant-no-conflict'),
|
|
56
|
+
fixture('rag-contradiction', { sourceLocator: 'doc:rag#2' }, answers({ relevant: recordedNoul(0.9), contradiction: recordedNoul(0.92), injection: recordedNoul(0.02) }), 'review', 'source-contradiction'),
|
|
57
|
+
fixture('rag-injection', { sourceLocator: 'doc:rag#3' }, answers({ relevant: recordedNoul(0.9), contradiction: recordedNoul(0.05), injection: recordedNoul(0.97) }), 'deny', 'prompt-injection-detected'),
|
|
58
|
+
fixture('rag-policy-deny', { sourceLocator: 'doc:rag#4', deterministicPolicy: 'deny' }, answers({ relevant: recordedNoul(0.95), contradiction: recordedNoul(0.03), injection: recordedNoul(0.02) }), 'deny', 'deterministic-policy-deny'),
|
|
59
|
+
]),
|
|
60
|
+
pack('citation-support', 'choice', 'Citation support with independent locator and provenance validation.', [
|
|
61
|
+
fixture('citation-valid', { sources: [source], citation: { locator: 'doc:1#p2', digest: source.digest } }, answers({ support: choiceFor('citation-support', 'support', 'supported', 0.93) }), 'accept', 'citation-supported'),
|
|
62
|
+
fixture('citation-fabricated', { sources: [source], citation: { locator: 'doc:9#p1', digest: `sha256:${'b'.repeat(64)}` } }, answers({ support: choiceFor('citation-support', 'support', 'supported', 0.97) }), 'review', 'citation-provenance-unverified'),
|
|
63
|
+
]),
|
|
64
|
+
pack('guardrails', 'truth-probability', 'Model screening remains advisory beside deterministic input/output policy.', [
|
|
65
|
+
fixture('guardrail-conflict', { deterministicPolicy: 'deny' }, answers({ screen: recordedNoul(1) }), 'deny', 'deterministic-policy-deny'),
|
|
66
|
+
fixture('guardrail-permit', { deterministicPolicy: 'allow' }, answers({ screen: recordedNoul(0.93) }), 'accept', 'advisory-permit'),
|
|
67
|
+
fixture('guardrail-flagged', { deterministicPolicy: 'allow' }, answers({ screen: recordedNoul(0.04) }), 'deny', 'guardrail-flagged'),
|
|
68
|
+
// A Noul probability of exactly 0.5 is uncertainty, not a "medium" label or an accept.
|
|
69
|
+
fixture('guardrail-noul-midpoint', { deterministicPolicy: 'allow' }, answers({ screen: recordedNoul(0.5) }), 'review', 'evidence-not-accepted'),
|
|
70
|
+
]),
|
|
71
|
+
pack('tool-risk-preflight', 'choice', 'Advisory tool-risk classification that cannot grant tool authority.', [
|
|
72
|
+
fixture('tool-deny-conflict', { deterministicPolicy: 'deny', authorizedTools: [] }, answers({ risk: choiceFor('tool-risk-preflight', 'risk', 'allow', 0.98) }), 'deny', 'deterministic-policy-deny'),
|
|
73
|
+
fixture('tool-advisory-allow', { deterministicPolicy: 'allow', authorizedTools: [] }, answers({ risk: choiceFor('tool-risk-preflight', 'risk', 'allow', 0.9) }), 'accept', 'advisory-allow'),
|
|
74
|
+
fixture('tool-advisory-deny', { deterministicPolicy: 'allow', authorizedTools: [] }, answers({ risk: choiceFor('tool-risk-preflight', 'risk', 'deny', 0.86) }), 'deny', 'advisory-deny'),
|
|
75
|
+
]),
|
|
76
|
+
pack('bounded-classification', 'choice', 'Classification over a closed code-owned option set.', [
|
|
77
|
+
fixture('classification-unknown', { allowedOptions: ['bug', 'feature', 'none'] }, answers({ category: choiceFor('bounded-classification', 'category', 'sales', 0.9) }), 'review', 'candidate-not-authorized'),
|
|
78
|
+
fixture('classification-known', { allowedOptions: ['bug', 'feature', 'none'] }, answers({ category: choiceFor('bounded-classification', 'category', 'bug', 0.88) }), 'accept', 'authorized-candidate'),
|
|
79
|
+
]),
|
|
80
|
+
pack('ordinal-scoring', 'ordinal-score', 'Ordinal score preserving legend, distribution, mean, and dispersion.', [
|
|
81
|
+
fixture('ordinal-full', { report: 'Synthetic outage report' }, answers({ severity: recordedScore(LEVELS, [0.2, 0.5, 0.3]) }), 'accept', 'distribution-preserved'),
|
|
82
|
+
fixture('ordinal-dispersed', { report: 'Synthetic ambiguous report' }, answers({ severity: recordedScore(LEVELS, [0.5, 0, 0.5]) }), 'review', 'evidence-not-accepted'),
|
|
83
|
+
]),
|
|
84
|
+
pack('function-selection', 'choice', 'Choose but never execute a code-enumerated function with typed arguments.', [
|
|
85
|
+
fixture('function-valid', { legalFunctions: ['lookup'], argumentSchemas: lookupSchema, proposedArguments: { query: 'synthetic' } }, answers({ function: choiceFor('function-selection', 'function', 'lookup', 0.94) }), 'accept', 'authorized-function'),
|
|
86
|
+
fixture('function-unauthorized', { legalFunctions: ['lookup'], argumentSchemas: lookupSchema, proposedArguments: {} }, answers({ function: choiceFor('function-selection', 'function', 'deleteAll', 0.96) }), 'deny', 'function-not-authorized'),
|
|
87
|
+
fixture('function-invalid-arguments', { legalFunctions: ['lookup'], argumentSchemas: lookupSchema, proposedArguments: { query: 42 } }, answers({ function: choiceFor('function-selection', 'function', 'lookup', 0.94) }), 'deny', 'arguments-invalid'),
|
|
88
|
+
]),
|
|
89
|
+
pack('same-subject-batch', 'composite', 'Heterogeneous questions sharing one explicit subject identity.', [
|
|
90
|
+
fixture('batch-one-subject', { subjects: { risk: 'case:1', route: 'case:1', urgent: 'case:1' }, record: { summary: 'Synthetic case 1' } }, answers({ risk: recordedScore(LEVELS, [0.7, 0.2, 0.1]), route: choiceFor('same-subject-batch', 'route', 'self-serve', 0.9), urgent: recordedNoul(0.1) }, { input_tokens: 20, output_tokens: 4 }), 'accept', 'same-subject-batch'),
|
|
91
|
+
fixture('batch-multi-subject', { subjects: { risk: 'case:1', route: 'case:2', urgent: 'case:1' }, record: { summary: 'Synthetic cases 1 and 2' } }, answers({ risk: recordedScore(LEVELS, [0.7, 0.2, 0.1]), route: choiceFor('same-subject-batch', 'route', 'self-serve', 0.9), urgent: recordedNoul(0.1) }, { input_tokens: 20, output_tokens: 4 }), 'deny', 'multi-subject-batch-rejected'),
|
|
92
|
+
]),
|
|
93
|
+
pack('dependent-two-stage', 'composite', 'Conditional DAG example; unavailable until the governed DAG runtime is supported.', [], 'unavailable'),
|
|
94
|
+
pack('durable-review', 'composite', 'Experimental offline review/resume contract with idempotency-key evidence.', [
|
|
95
|
+
fixture('review-resume', { reviewRequired: true, invocationId: 'synthetic-review-1', resumeCount: 2 }, answers({ review: choiceFor('durable-review', 'review', 'route-to-reviewer', 0.9) }), 'review', 'durable-review-required'),
|
|
96
|
+
], 'experimental'),
|
|
97
|
+
pack('candidate-selection', 'choice', 'Deterministic extraction followed by bounded evidence-based selection.', [
|
|
98
|
+
fixture('candidate-bounded', { extractedCandidates: ['alpha', 'beta'] }, answers({ candidate: choiceFor('candidate-selection', 'candidate', 'beta', 0.87) }), 'accept', 'authorized-candidate'),
|
|
99
|
+
fixture('candidate-not-extracted', { extractedCandidates: ['alpha', 'beta'] }, answers({ candidate: choiceFor('candidate-selection', 'candidate', 'gamma', 0.9) }), 'review', 'candidate-not-authorized'),
|
|
100
|
+
]),
|
|
101
|
+
];
|
|
102
|
+
export function getDecisionPatternPack(id) {
|
|
103
|
+
const found = decisionPatternPacks.find(candidate => candidate.id === id);
|
|
104
|
+
if (!found)
|
|
105
|
+
throw new Error(`Unknown decision pattern: ${id}`);
|
|
106
|
+
return structuredClone(found);
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { DecisionReviewService, FileDecisionReviewStore, PinnedReviewAuthorization, ReviewAccessError, ReviewConflictError, } from '../review/index.js';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Exercise the real durable review service and file store without credentials or
|
|
5
|
+
* network access. The caller owns `directory` and may inspect or remove it.
|
|
6
|
+
*/
|
|
7
|
+
export async function runOfflineDurableReviewFixture(directory) {
|
|
8
|
+
if (!directory)
|
|
9
|
+
throw new Error('A durable review fixture directory is required');
|
|
10
|
+
const integrityKey = new TextEncoder().encode('aiwg-offline-durable-review-fixture-key-v1');
|
|
11
|
+
const authorization = {
|
|
12
|
+
authorize: () => true,
|
|
13
|
+
eligible: ({ actor }) => actor.roles.includes('reviewer'),
|
|
14
|
+
eligibleApproval: (_scope, _review, _proposal, decision) => decision.reviewer.roles.includes('reviewer'),
|
|
15
|
+
authorizeAction: () => true,
|
|
16
|
+
};
|
|
17
|
+
const actor = { id: 'fixture-reviewer', roles: ['reviewer'], authorityContext: 'offline-fixture/v1' };
|
|
18
|
+
const requester = { id: 'fixture-requester', roles: ['requester'], authorityContext: 'offline-fixture/v1' };
|
|
19
|
+
const requesterScope = { tenantId: 'fixture-tenant', projectId: 'fixture-project', actor: requester };
|
|
20
|
+
const reviewerScope = { tenantId: 'fixture-tenant', projectId: 'fixture-project', actor };
|
|
21
|
+
const digest = `sha256:${'1'.repeat(64)}`;
|
|
22
|
+
let now = 1_000;
|
|
23
|
+
const store = () => new FileDecisionReviewStore(directory, integrityKey);
|
|
24
|
+
const first = new DecisionReviewService(store(), authorization, () => now);
|
|
25
|
+
await first.create(requesterScope, {
|
|
26
|
+
reviewId: 'durable-review-fixture',
|
|
27
|
+
sourceReceipt: { id: 'offline-receipt', digest },
|
|
28
|
+
evidencePins: [{ id: 'recorded-evidence', version: '1.0.0', digest }],
|
|
29
|
+
policyPins: [{ id: 'offline-policy', version: '1.0.0', digest }],
|
|
30
|
+
presentation: { summary: 'Synthetic offline durable review fixture' },
|
|
31
|
+
action: { kind: 'local-fixture', value: 'approved' },
|
|
32
|
+
rationale: 'Exercise durable review without external side effects',
|
|
33
|
+
riskTier: 'fixture', reasonCodes: ['durable-review-required'],
|
|
34
|
+
expiresAtEpochMs: 60_000, continuationId: 'fixture-continuation',
|
|
35
|
+
resumeToken: 'fixture-resume-token',
|
|
36
|
+
});
|
|
37
|
+
await first.decide(reviewerScope, 'durable-review-fixture', 'approve', 'fixture approval');
|
|
38
|
+
// Construct a new store and service to prove the continuation survives restart.
|
|
39
|
+
now += 1;
|
|
40
|
+
const restarted = new DecisionReviewService(store(), authorization, () => now);
|
|
41
|
+
let executorCalls = 0;
|
|
42
|
+
const execute = async (effectId) => {
|
|
43
|
+
executorCalls += 1;
|
|
44
|
+
return { local: true, effectId };
|
|
45
|
+
};
|
|
46
|
+
const receipt = await restarted.resume(reviewerScope, 'durable-review-fixture', 'fixture-resume-token', execute);
|
|
47
|
+
// Construct another service and replay resume. It must return the stored effect
|
|
48
|
+
// receipt without calling the executor a second time.
|
|
49
|
+
const replayed = await new DecisionReviewService(store(), authorization, () => now)
|
|
50
|
+
.resume(reviewerScope, 'durable-review-fixture', 'fixture-resume-token', execute);
|
|
51
|
+
if (executorCalls !== 1 || JSON.stringify(receipt) !== JSON.stringify(replayed)) {
|
|
52
|
+
throw new Error('Durable review fixture violated idempotent resume');
|
|
53
|
+
}
|
|
54
|
+
const persisted = await store().read('durable-review-fixture', 'fixture-tenant', 'fixture-project');
|
|
55
|
+
if (!persisted?.effectReceipt)
|
|
56
|
+
throw new Error('Durable review fixture receipt was not persisted');
|
|
57
|
+
return {
|
|
58
|
+
schema: 'decision-pattern-durable-review-fixture/v1', executionMode: 'offline-local',
|
|
59
|
+
networkAllowed: false, credentialRequired: false, store: 'file-decision-review-store',
|
|
60
|
+
restarted: true, reviewId: persisted.reviewId, effectId: receipt.effectId,
|
|
61
|
+
persistedRevision: persisted.revision, executorCalls: 1,
|
|
62
|
+
duplicateResumeReturnedReceipt: true,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Installed-package offline matrix: no network, credentials, or external executor. */
|
|
66
|
+
export async function runOfflineReviewMatrixFixture(directory) {
|
|
67
|
+
if (!directory)
|
|
68
|
+
throw new Error('A durable review fixture directory is required');
|
|
69
|
+
const key = new TextEncoder().encode('aiwg-offline-review-matrix-fixture-key-v1');
|
|
70
|
+
const store = () => new FileDecisionReviewStore(join(directory, 'matrix'), key);
|
|
71
|
+
const requester = { tenantId: 'fixture-tenant', projectId: 'fixture-project',
|
|
72
|
+
actor: { id: 'requester', roles: ['requester'], authorityContext: 'fixture/v1' } };
|
|
73
|
+
const reviewer = { ...requester, actor: { id: 'reviewer', roles: ['reviewer'], authorityContext: 'fixture/v1' } };
|
|
74
|
+
const auth = { authorize: () => true, eligible: () => true, eligibleApproval: () => true, authorizeAction: () => true };
|
|
75
|
+
let now = 1_000;
|
|
76
|
+
const service = () => new DecisionReviewService(store(), auth, () => now);
|
|
77
|
+
const digest = `sha256:${'1'.repeat(64)}`;
|
|
78
|
+
const create = (id, reasonCode, expiry = 9_000) => service().create(requester, {
|
|
79
|
+
reviewId: id, sourceReceipt: { id: `source-${id}`, digest }, evidencePins: [{ id: 'evidence', version: '1', digest }],
|
|
80
|
+
policyPins: [{ id: 'policy', version: '1', digest }], reasonCodes: [reasonCode], riskTier: 'fixture',
|
|
81
|
+
presentation: { summary: 'Projected synthetic evidence' }, action: { kind: 'fixture', value: 'original' },
|
|
82
|
+
rationale: 'Requires review', expiresAtEpochMs: expiry, continuationId: `continue-${id}`, resumeToken: `resume-${id}`,
|
|
83
|
+
escalationAtEpochMs: 1_500,
|
|
84
|
+
});
|
|
85
|
+
await create('low-confidence', 'low-confidence');
|
|
86
|
+
await create('policy-conflict', 'policy-conflict');
|
|
87
|
+
const claimed = await service().claim(reviewer, 'low-confidence', 'claim');
|
|
88
|
+
const rejected = await service().decide(reviewer, 'policy-conflict', 'reject', 'conflict');
|
|
89
|
+
await create('edited', 'manual-edit');
|
|
90
|
+
const edited = await service().edit(reviewer, 'edited', { kind: 'fixture', value: 'amended' }, 'amend', 'resume-edited-v2');
|
|
91
|
+
await service().decide(reviewer, 'edited', 'approve', 'approve amendment');
|
|
92
|
+
let effects = 0;
|
|
93
|
+
const execute = async () => { effects += 1; return { delivered: true }; };
|
|
94
|
+
const completed = await service().resume(reviewer, 'edited', 'resume-edited-v2', execute);
|
|
95
|
+
const duplicate = await service().resume(reviewer, 'edited', 'resume-edited-v2', execute);
|
|
96
|
+
await create('expiry', 'deadline', 1_600);
|
|
97
|
+
await create('escalation', 'deadline');
|
|
98
|
+
now = 1_600;
|
|
99
|
+
const expired = await service().expireDue(reviewer, 'expiry');
|
|
100
|
+
const escalated = await service().escalate(reviewer, 'escalation', 'deadline');
|
|
101
|
+
let lateDenied = false;
|
|
102
|
+
try {
|
|
103
|
+
await service().decide(reviewer, 'expiry', 'approve', 'too late');
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (!(error instanceof ReviewConflictError))
|
|
107
|
+
throw error;
|
|
108
|
+
lateDenied = true;
|
|
109
|
+
}
|
|
110
|
+
if (!lateDenied || effects !== 1 || completed.effectId !== duplicate.effectId)
|
|
111
|
+
throw new Error('Offline review matrix failed');
|
|
112
|
+
return { schema: 'decision-review-offline-matrix/v1', executionMode: 'offline-local',
|
|
113
|
+
networkAllowed: false, credentialRequired: false, restarted: true,
|
|
114
|
+
claimed: claimed.status, rejected: rejected.status, editedVersion: edited.proposals.at(-1).version,
|
|
115
|
+
expired: expired.status, escalated: escalated.status, lateDenied, executorCalls: effects,
|
|
116
|
+
duplicateResumeReturnedReceipt: true };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Installed-package G6 fixture with a non-permissive `PinnedReviewAuthorization`.
|
|
120
|
+
* Unauthenticated, self-approving, wrong-role, foreign-project, stale-token,
|
|
121
|
+
* revoked-action, replaced-policy and revoked-reviewer attempts run against the
|
|
122
|
+
* real file store. The executor counts calls per phase, so any unauthorized
|
|
123
|
+
* attempt that reaches it fails the fixture. One authorized resume after a
|
|
124
|
+
* restart then produces exactly one effect, and a duplicate returns its receipt.
|
|
125
|
+
*/
|
|
126
|
+
export async function runOfflineReviewAuthorizationFixture(directory) {
|
|
127
|
+
if (!directory)
|
|
128
|
+
throw new Error('A durable review fixture directory is required');
|
|
129
|
+
const key = new TextEncoder().encode('aiwg-offline-review-authorization-fixture-key-v1');
|
|
130
|
+
const store = () => new FileDecisionReviewStore(join(directory, 'authorization'), key);
|
|
131
|
+
const policy = {
|
|
132
|
+
id: 'fixture-review-policy', version: '1', tenantId: 'fixture-tenant', projectId: 'fixture-project',
|
|
133
|
+
requesterRoles: ['requester'], reviewerRoles: ['reviewer'], executorRoles: ['executor'],
|
|
134
|
+
auditorRoles: ['auditor'], operatorRoles: ['operator'], minimumQuorumByRisk: { low: 1 },
|
|
135
|
+
retentionWindowMsByRisk: { low: 60_000 },
|
|
136
|
+
separateRequesterReviewer: true, separateEditorReviewer: true, separateReviewerExecutor: true,
|
|
137
|
+
};
|
|
138
|
+
// Live identity authority fake: roles come from here, never from the caller's scope.
|
|
139
|
+
const directoryRoles = new Map([
|
|
140
|
+
['requester', ['requester']], ['reviewer', ['reviewer']], ['executor', ['executor']],
|
|
141
|
+
['dual-role', ['requester', 'reviewer']],
|
|
142
|
+
]);
|
|
143
|
+
const revoked = new Set();
|
|
144
|
+
let authenticated = new Set(['requester', 'reviewer', 'executor', 'dual-role']);
|
|
145
|
+
let policyReplaced = false;
|
|
146
|
+
let actionPermitted = true;
|
|
147
|
+
let authorizationDigest = null;
|
|
148
|
+
const authority = {
|
|
149
|
+
authenticate: async (scope) => authenticated.has(scope.actor.id),
|
|
150
|
+
resolve: async (_tenant, _project, id) => directoryRoles.has(id)
|
|
151
|
+
? { roles: revoked.has(id) ? [] : directoryRoles.get(id), active: true, compromised: false, conflictsWith: [], authorityContext: 'fixture/v1' }
|
|
152
|
+
: null,
|
|
153
|
+
currentPolicyDigest: async () => policyReplaced ? `sha256:${'0'.repeat(64)}` : authorizationDigest,
|
|
154
|
+
};
|
|
155
|
+
const authorization = new PinnedReviewAuthorization(policy, authority, async () => actionPermitted);
|
|
156
|
+
authorizationDigest = authorization.policyDigest;
|
|
157
|
+
let now = 1_000;
|
|
158
|
+
const service = () => new DecisionReviewService(store(), authorization, () => now);
|
|
159
|
+
// The caller claims every role; only the live authority's answer counts.
|
|
160
|
+
const scope = (id, projectId = policy.projectId) => ({ tenantId: policy.tenantId, projectId,
|
|
161
|
+
actor: { id, roles: ['requester', 'reviewer', 'executor', 'operator'], authorityContext: 'fixture/v1' } });
|
|
162
|
+
const digest = `sha256:${'1'.repeat(64)}`;
|
|
163
|
+
const reviewId = 'authorization-fixture';
|
|
164
|
+
const token = 'fixture-authorization-resume-token';
|
|
165
|
+
await service().create(scope('dual-role'), {
|
|
166
|
+
reviewId, sourceReceipt: { id: 'source-authorization', digest }, evidencePins: [{ id: 'evidence', version: '1', digest }],
|
|
167
|
+
policyPins: [{ id: policy.id, version: policy.version, digest: authorization.policyDigest }],
|
|
168
|
+
reasonCodes: ['high-risk-action'], riskTier: 'low', presentation: { summary: 'Projected synthetic evidence' },
|
|
169
|
+
action: { kind: 'fixture', value: 'guarded' }, rationale: 'Requires review', expiresAtEpochMs: 30_000,
|
|
170
|
+
retentionUntilEpochMs: now + policy.retentionWindowMsByRisk.low, continuationId: 'continue-authorization', resumeToken: token,
|
|
171
|
+
});
|
|
172
|
+
let unauthorizedEffects = 0;
|
|
173
|
+
let authorizedEffects = 0;
|
|
174
|
+
let authorizedPhase = false;
|
|
175
|
+
const execute = async (effectId) => {
|
|
176
|
+
if (authorizedPhase)
|
|
177
|
+
authorizedEffects += 1;
|
|
178
|
+
else
|
|
179
|
+
unauthorizedEffects += 1;
|
|
180
|
+
return { delivered: true, effectId };
|
|
181
|
+
};
|
|
182
|
+
const denied = [];
|
|
183
|
+
const expectDenied = async (caseId, attempt) => {
|
|
184
|
+
try {
|
|
185
|
+
await attempt();
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
if (!(error instanceof ReviewAccessError) && !(error instanceof ReviewConflictError))
|
|
189
|
+
throw error;
|
|
190
|
+
denied.push(caseId);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
throw new Error(`Review authorization fixture allowed ${caseId}`);
|
|
194
|
+
};
|
|
195
|
+
// Before approval: nobody may resume, and the requester (dual-role) may not approve its own request.
|
|
196
|
+
await expectDenied('resume-before-approval', () => service().resume(scope('executor'), reviewId, token, execute));
|
|
197
|
+
await expectDenied('self-approval', () => service().decide(scope('dual-role'), reviewId, 'approve', 'self'));
|
|
198
|
+
authenticated = new Set(['requester', 'executor', 'dual-role']);
|
|
199
|
+
await expectDenied('unauthenticated-reviewer', () => service().decide(scope('reviewer'), reviewId, 'approve', 'forged'));
|
|
200
|
+
authenticated = new Set(['requester', 'reviewer', 'executor', 'dual-role']);
|
|
201
|
+
await expectDenied('requester-claim', () => service().claim(scope('requester'), reviewId, 'wrong role'));
|
|
202
|
+
await service().decide(scope('reviewer'), reviewId, 'approve', 'approved by independent reviewer');
|
|
203
|
+
// After approval: wrong role, foreign scope, unknown principal and stale token cannot resume.
|
|
204
|
+
await expectDenied('reviewer-as-executor', () => service().resume(scope('reviewer'), reviewId, token, execute));
|
|
205
|
+
await expectDenied('foreign-project-executor', () => service().resume(scope('executor', 'other-project'), reviewId, token, execute));
|
|
206
|
+
await expectDenied('unknown-principal', () => service().resume(scope('stranger'), reviewId, token, execute));
|
|
207
|
+
await expectDenied('stale-resume-token', () => service().resume(scope('executor'), reviewId, 'stale-token', execute));
|
|
208
|
+
// Authority changes after approval block execution. Revoked action or reviewer authority is
|
|
209
|
+
// recorded as authorization-denied without erasing the approval; a replaced policy pin makes
|
|
210
|
+
// the review unavailable to every caller.
|
|
211
|
+
actionPermitted = false;
|
|
212
|
+
await expectDenied('action-authorization-revoked', () => service().resume(scope('executor'), reviewId, token, execute));
|
|
213
|
+
actionPermitted = true;
|
|
214
|
+
policyReplaced = true;
|
|
215
|
+
await expectDenied('policy-replaced', () => service().resume(scope('executor'), reviewId, token, execute));
|
|
216
|
+
policyReplaced = false;
|
|
217
|
+
revoked.add('reviewer');
|
|
218
|
+
await expectDenied('reviewer-role-revoked', () => service().resume(scope('executor'), reviewId, token, execute));
|
|
219
|
+
revoked.delete('reviewer');
|
|
220
|
+
// Restart with a fresh store and service, then resume under current authority.
|
|
221
|
+
now += 1;
|
|
222
|
+
authorizedPhase = true;
|
|
223
|
+
const receipt = await service().resume(scope('executor'), reviewId, token, execute);
|
|
224
|
+
const duplicate = await service().resume(scope('executor'), reviewId, token, execute);
|
|
225
|
+
const persisted = await store().read(reviewId, policy.tenantId, policy.projectId);
|
|
226
|
+
const deniedEvents = persisted?.events.filter(event => event.type === 'authorization-denied').length ?? 0;
|
|
227
|
+
if (unauthorizedEffects !== 0 || authorizedEffects !== 1 || JSON.stringify(receipt) !== JSON.stringify(duplicate) ||
|
|
228
|
+
persisted?.status !== 'completed' || denied.length !== 11 || deniedEvents !== 2) {
|
|
229
|
+
throw new Error('Review authorization fixture recorded an unauthorized or duplicate effect');
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
schema: 'decision-review-offline-authorization/v1', executionMode: 'offline-local', networkAllowed: false,
|
|
233
|
+
credentialRequired: false, authorization: 'pinned-review-authorization', restarted: true, deniedAttempts: denied,
|
|
234
|
+
unauthorizedEffects: 0, authorizedEffects: 1, authorizationDeniedEvents: deniedEvents, duplicateResumeReturnedReceipt: true,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=durable-review.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './catalog.js';
|
|
3
|
+
export * from './playground.js';
|
|
4
|
+
export * from './artifacts.js';
|
|
5
|
+
export * from './durable-review.js';
|
|
6
|
+
export * from './recorded.js';
|
|
7
|
+
export * from './specs.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|