@aiwg/cli 2026.10.3 → 2026.10.5
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/README.md +2 -2
- package/agentic/code/providers/model-capabilities.v1.json +96 -4
- package/bin/aiwg.mjs +50 -27
- package/dist/src/agents/grounding/knowledge-base.js +54 -6
- package/dist/src/artifacts/browser-export.js +6 -0
- package/dist/src/artifacts/checksum-manifest.js +1 -0
- package/dist/src/artifacts/citation-parser.js +173 -92
- package/dist/src/artifacts/citation-table-schema.js +59 -0
- package/dist/src/artifacts/cli.js +69 -18
- package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
- package/dist/src/artifacts/corpus-tools/vision-extract.js +2 -0
- package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
- package/dist/src/artifacts/corpus-views/renderers.js +3 -3
- package/dist/src/artifacts/discovery-eval.js +6 -4
- package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
- package/dist/src/artifacts/fortemi-core-sync.js +166 -40
- package/dist/src/artifacts/framework-capabilities.js +171 -0
- package/dist/src/artifacts/framework-index-paths.js +73 -0
- package/dist/src/artifacts/framework-source-digest.js +75 -0
- package/dist/src/artifacts/index-builder.js +286 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +130 -0
- package/dist/src/artifacts/index-refresh.js +591 -0
- package/dist/src/artifacts/index-worker.mjs +11 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
- package/dist/src/artifacts/prebuilt-deployment.js +46 -0
- package/dist/src/artifacts/prebuilt-relations.js +80 -0
- package/dist/src/artifacts/query-engine.js +410 -88
- package/dist/src/artifacts/relations-builder.js +168 -0
- package/dist/src/artifacts/relations.js +148 -0
- package/dist/src/artifacts/stats.js +5 -2
- package/dist/src/artifacts/types.js +30 -10
- package/dist/src/artifacts/watcher.js +1 -1
- package/dist/src/auth/credential-store.js +2 -1
- package/dist/src/catalog/cli.d.mts +5 -0
- package/dist/src/channel/manager.d.mts +20 -0
- package/dist/src/channel/manager.mjs +19 -2
- package/dist/src/cli/command-log.js +2 -3
- package/dist/src/cli/handlers/artifacts.js +3 -0
- package/dist/src/cli/handlers/auth.js +3 -0
- package/dist/src/cli/handlers/decision.js +19 -12
- package/dist/src/cli/handlers/diagnose.js +4 -3
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/init.js +4 -1
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/models.js +104 -8
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/refresh.js +12 -0
- package/dist/src/cli/handlers/resource-versions.js +3 -0
- package/dist/src/cli/handlers/rules.js +195 -0
- package/dist/src/cli/handlers/run.js +4 -0
- package/dist/src/cli/handlers/runtime-info.js +1 -2
- package/dist/src/cli/handlers/script-runner.js +3 -2
- package/dist/src/cli/handlers/session.js +19 -9
- package/dist/src/cli/handlers/sessions.js +137 -43
- package/dist/src/cli/handlers/setup-manifest.js +39 -44
- package/dist/src/cli/handlers/setup.js +41 -0
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +57 -2
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +294 -55
- package/dist/src/cli/handlers/utilities.js +26 -10
- package/dist/src/cli/handlers/version.js +8 -0
- package/dist/src/cli/router.js +15 -1
- package/dist/src/cli/services/deployment-verification.js +19 -9
- package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
- package/dist/src/cli/skill-usage.js +2 -3
- package/dist/src/config/aiwg-config.js +87 -37
- package/dist/src/config/cli.js +111 -5
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
- package/dist/src/config/gitignore.js +50 -0
- package/dist/src/config/index-graph-overrides.js +36 -0
- package/dist/src/config/project-artifacts-health.d.mts +37 -0
- package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
- package/dist/src/config/user-config-dir.d.mts +9 -0
- package/dist/src/config/user-config.js +2 -1
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +13 -8
- package/dist/src/decision/ensemble/aggregate.js +23 -10
- package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
- package/dist/src/decision/entry.js +2 -15
- package/dist/src/decision/graph-live-qualification.js +1 -1
- package/dist/src/decision/graph-run.js +9 -0
- package/dist/src/decision/heldout/collector.js +5 -13
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +131 -28
- package/dist/src/decision/offload-activation.js +137 -0
- package/dist/src/decision/offload.js +550 -0
- package/dist/src/decision/qualification/load.js +4 -6
- package/dist/src/decision/savings.js +28 -0
- package/dist/src/effects/types.js +5 -1
- package/dist/src/effects/verifiers/git.js +2 -1
- package/dist/src/effects/verifiers/tracker.js +26 -14
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/extensions/project-local-remove.js +36 -1
- package/dist/src/gates/providers/loader.js +61 -17
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +14 -2
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +78 -3
- package/dist/src/issues/cli.js +4 -4
- package/dist/src/issues/local.js +24 -5
- package/dist/src/jobs/runner.js +1 -1
- package/dist/src/lint/cli.js +27 -1
- package/dist/src/lint/loader.js +2 -1
- package/dist/src/lint/reporters.js +25 -1
- package/dist/src/lint/rule-activation.js +43 -0
- package/dist/src/lint/runner.js +137 -62
- package/dist/src/mcp/adapters/codex-runtime.js +7 -1
- package/dist/src/mcp/claude-install.d.mts +4 -0
- package/dist/src/mcp/claude-install.mjs +41 -0
- package/dist/src/mcp/cli.d.mts +1 -0
- package/dist/src/mcp/cli.mjs +36 -11
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +1 -1
- package/dist/src/mcp/helpers.mjs +3 -2
- package/dist/src/mcp/muse-native-config.d.mts +13 -0
- package/dist/src/mcp/muse-native-config.mjs +1 -0
- package/dist/src/mcp/omp-config.d.mts +4 -0
- package/dist/src/mcp/registry.js +3 -93
- package/dist/src/mcp/registry.mjs +3 -87
- package/dist/src/mcp/server-config.d.mts +3 -0
- package/dist/src/mcp/server-config.mjs +89 -0
- package/dist/src/mcp/server.mjs +37 -375
- package/dist/src/mcp/test-simple.mjs +2 -1
- package/dist/src/mcp/toml-editor.d.mts +4 -0
- package/dist/src/mcp/toml-string.d.mts +1 -0
- package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -0
- package/dist/src/mcp/tools/decision.mjs +12 -11
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +12 -2
- package/dist/src/mcp/tools/worker-invoke.mjs +1340 -23
- package/dist/src/mcp/tools/worker-shell-control.mjs +52 -21
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +3 -3
- package/dist/src/mission-protocol/codecs.js +8 -0
- package/dist/src/models/config-loader.js +7 -2
- package/dist/src/models/model-capabilities.v1.json +96 -4
- package/dist/src/models/model-discovery.js +76 -11
- package/dist/src/models/provider-policy.js +101 -7
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/resolver.js +12 -10
- package/dist/src/models/worker-direct-readiness.js +65 -44
- package/dist/src/models/worker-dispatch-offer.js +3 -3
- package/dist/src/models/worker-health-readiness.js +16 -3
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +135 -28
- package/dist/src/process/bounded-child-process.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +43 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -0
- package/dist/src/providers/deployed-skill-roots.js +20 -0
- package/dist/src/providers/grok-build-acp.js +3 -2
- package/dist/src/providers/grok-build-headless.js +12 -4
- package/dist/src/providers/grok-build-inspect.js +1 -0
- package/dist/src/providers/omp-agent.d.mts +8 -0
- package/dist/src/providers/omp-diagnostics.d.mts +1 -0
- package/dist/src/providers/omp-paths.d.mts +13 -0
- package/dist/src/providers/skill-ownership.d.mts +9 -0
- package/dist/src/providers/skill-ownership.mjs +88 -0
- package/dist/src/rules/activation.d.mts +21 -0
- package/dist/src/rules/activation.mjs +62 -0
- package/dist/src/rules/inline-budget.d.mts +26 -0
- package/dist/src/rules/inline-budget.mjs +66 -0
- package/dist/src/rules/inventory.js +129 -0
- package/dist/src/rules/pin-state.js +73 -0
- package/dist/src/rules/pins.d.mts +14 -0
- package/dist/src/rules/pins.mjs +112 -0
- package/dist/src/rules/rule-frontmatter.schema.json +197 -0
- package/dist/src/serve/mission-conductor.js +38 -12
- package/dist/src/serve/worker-invocation.js +30 -10
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/sessions/adapter-registry.js +23 -19
- package/dist/src/sessions/adapters/claude.js +39 -54
- package/dist/src/sessions/adapters/codex.js +95 -67
- package/dist/src/sessions/adapters/copilot.js +4 -4
- package/dist/src/sessions/adapters/cursor.js +40 -57
- package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
- package/dist/src/sessions/adapters/factory.js +31 -27
- package/dist/src/sessions/adapters/generic.js +10 -10
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot.js +24 -13
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +31 -11
- package/dist/src/sessions/adapters/muse.js +150 -93
- package/dist/src/sessions/adapters/omp.js +28 -14
- package/dist/src/sessions/adapters/openclaw.js +89 -79
- package/dist/src/sessions/adapters/opencode.js +27 -24
- package/dist/src/sessions/adapters/openhuman.js +18 -60
- package/dist/src/sessions/adapters/pi.js +29 -14
- package/dist/src/sessions/adapters/warp.js +3 -3
- package/dist/src/sessions/adapters/windsurf.js +19 -48
- package/dist/src/sessions/batch-contracts.js +16 -8
- package/dist/src/sessions/batch-import.js +252 -12
- package/dist/src/sessions/contracts.js +50 -3
- package/dist/src/sessions/coverage-preflight.js +27 -2
- package/dist/src/sessions/export-plan.js +4 -3
- package/dist/src/sessions/import-exclusions.js +72 -0
- package/dist/src/sessions/import-lease.js +34 -18
- package/dist/src/sessions/import-limits.js +37 -0
- package/dist/src/sessions/importer.js +60 -38
- package/dist/src/sessions/lifecycle.js +45 -0
- package/dist/src/sessions/policy.js +145 -26
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +105 -0
- package/dist/src/sessions/repository.js +151 -54
- package/dist/src/sessions/workspace-discovery.js +292 -60
- package/dist/src/sessions/workspace-matching.js +107 -0
- package/dist/src/skills/importer.js +1 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
- package/dist/src/smiths/context-pipeline/finalization.js +6 -5
- package/dist/src/smiths/context-pipeline/generator.js +21 -4
- package/dist/src/smiths/context-pipeline/parallelism-section.js +7 -15
- package/dist/src/smiths/mcpsmith/generator.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +76 -18
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/update/checker.d.mts +2 -0
- package/dist/src/update/service.d.mts +22 -0
- package/dist/src/utils/contained-filesystem.d.mts +37 -0
- package/dist/src/utils/contained-filesystem.mjs +683 -0
- package/dist/src/utils/temporary-directory.js +36 -0
- package/dist/src/writing/example-generator.js +1 -1
- package/package.json +15 -2
- package/schemas/models/model-catalog.v1.schema.json +1 -1
- package/schemas/models/provider-model-capabilities.v1.schema.json +6 -0
- package/tools/_resolve-impl.mjs +13 -15
- package/tools/agents/deploy-agents.mjs +49 -21
- package/tools/agents/providers/base.mjs +104 -134
- package/tools/agents/providers/claude.mjs +8 -24
- package/tools/agents/providers/codex.mjs +18 -11
- package/tools/agents/providers/cursor.mjs +19 -18
- package/tools/agents/providers/grok-build.mjs +3 -1
- package/tools/agents/providers/hermes.mjs +57 -47
- package/tools/agents/providers/openclaw.mjs +9 -4
- package/tools/agents/providers/pi.mjs +5 -0
- package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
- package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
- package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
- package/tools/agents/providers/rule-delivery.mjs +1270 -0
- package/tools/agents/providers/warp.mjs +20 -26
- package/tools/cli/decision-offload-report.mjs +40 -0
- package/tools/cli/workspace-status.mjs +862 -0
- package/tools/plugin/package-plugins.mjs +186 -8
- package/tools/plugin/plugin-links.mjs +114 -0
- package/tools/providers/antigravity-transport.mjs +3 -1
- package/tools/providers/deepseek-harness-transport.mjs +6 -4
- package/tools/providers/omp-transport.mjs +4 -2
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +1 -1
- package/tools/ralph-external/lib/pi-adapter.mjs +0 -4
- package/tools/ralph-external/lib/provider-adapter.mjs +59 -1
- package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
- package/tools/ralph-external/lib/session-environment.mjs +28 -0
- package/tools/ralph-external/lib/systemd-client.mjs +49 -0
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
- package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
- package/tools/ralph-external/process-monitor.mjs +1017 -0
- package/tools/ralph-external/session-launcher.mjs +5057 -0
- package/tools/scripts/resource-preflight.mjs +541 -0
- package/tools/skills/deploy-skills-codex.mjs +2 -1
- package/vscode-extension/schemas/aiwg.config.v1.json +1194 -231
package/dist/src/decision/ask.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { aggregateModalAnswers } from './ensemble/aggregate.js';
|
|
2
|
+
import { readAiwgConfigCandidate } from '../config/aiwg-config.js';
|
|
1
3
|
import { readFile } from 'node:fs/promises';
|
|
2
4
|
import { JEV_ENDPOINT, JevDecisionAdapter } from './adapters/jev.js';
|
|
3
5
|
import { evaluateDecisionRuleset } from './evaluate.js';
|
|
4
6
|
import { artifactPin } from './validate.js';
|
|
5
|
-
import { JEV_CREDENTIAL_REF, JEV_DEFAULT_REGION, lookupJevCredential, removeJevCredentialFile,
|
|
7
|
+
import { JEV_CREDENTIAL_REF, JEV_DEFAULT_REGION, lookupJevCredential, removeJevCredentialFile, writeJevCredentialFile, } from './jev-credentials.js';
|
|
6
8
|
const CONTEXT_LIMIT_BYTES = 32 * 1024;
|
|
7
9
|
const MODEL = 'jev-latest';
|
|
8
10
|
const PURPOSE = 'agent-bounded-decision';
|
|
@@ -39,9 +41,22 @@ export function validateDecisionAskInput(input) {
|
|
|
39
41
|
if (!Number.isFinite(threshold) || threshold < 0 || threshold > 1) {
|
|
40
42
|
usageError('invalid-threshold', '--threshold must be a probability from 0 to 1');
|
|
41
43
|
}
|
|
44
|
+
for (const key of ['minAgreement', 'agreementConfidenceFloor']) {
|
|
45
|
+
const value = input[key];
|
|
46
|
+
if (value !== undefined && (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > 1)) {
|
|
47
|
+
usageError('invalid-agreement-policy', `${key} must be a probability from 0 to 1`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
42
50
|
const timeoutMs = input.timeoutMs ?? 15_000;
|
|
43
51
|
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0)
|
|
44
52
|
usageError('invalid-timeout', '--timeout-ms must be a positive integer');
|
|
53
|
+
const passes = input.passes ?? DEFAULT_DECISION_PASSES;
|
|
54
|
+
const escalationPasses = input.escalationPasses ?? defaultEscalationPasses(passes);
|
|
55
|
+
if (!Number.isSafeInteger(passes) || passes <= 0)
|
|
56
|
+
usageError('invalid-passes', '--passes must be a positive safe integer');
|
|
57
|
+
if (!Number.isSafeInteger(escalationPasses) || escalationPasses < passes) {
|
|
58
|
+
usageError('invalid-escalation-passes', '--escalation-passes must be a positive safe integer >= passes');
|
|
59
|
+
}
|
|
45
60
|
if (input.mode.kind === 'choices') {
|
|
46
61
|
if (input.mode.choices.length < 2 || input.mode.choices.length > 255)
|
|
47
62
|
usageError('invalid-choices', '--choices requires 2..255 ids');
|
|
@@ -239,66 +254,209 @@ function publicAnswer(mode, value, distribution) {
|
|
|
239
254
|
}
|
|
240
255
|
return typeof value === 'string' ? { answer: value } : { answer: null };
|
|
241
256
|
}
|
|
257
|
+
/** Product default: 5 passes, escalating to 10 when the first round is not decisive. */
|
|
258
|
+
export const DEFAULT_DECISION_PASSES = 5;
|
|
259
|
+
/** Escalation defaults to twice the initial pass count (5 -> 10, 10 -> 20). */
|
|
260
|
+
export function defaultEscalationPasses(passes) {
|
|
261
|
+
return passes * 2;
|
|
262
|
+
}
|
|
242
263
|
export async function askDecision(input, options = {}) {
|
|
264
|
+
if (options.cwd) {
|
|
265
|
+
let config;
|
|
266
|
+
try {
|
|
267
|
+
config = await readAiwgConfigCandidate(options.cwd);
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
usageError('invalid-config', error instanceof Error ? error.message : 'Invalid project config');
|
|
271
|
+
}
|
|
272
|
+
input = { ...input, passes: input.passes ?? config?.decision?.ask?.passes,
|
|
273
|
+
escalationPasses: input.escalationPasses ?? config?.decision?.ask?.escalation_passes,
|
|
274
|
+
minAgreement: input.minAgreement ?? config?.decision?.ask?.min_agreement,
|
|
275
|
+
agreementConfidenceFloor: input.agreementConfidenceFloor ?? config?.decision?.ask?.agreement_confidence_floor };
|
|
276
|
+
}
|
|
243
277
|
validateDecisionAskInput(input);
|
|
244
278
|
const started = options.now?.() ?? Date.now();
|
|
279
|
+
const wallStarted = Date.now();
|
|
280
|
+
const passesRequested = input.passes ?? DEFAULT_DECISION_PASSES;
|
|
281
|
+
const escalationPasses = input.escalationPasses ?? defaultEscalationPasses(passesRequested);
|
|
282
|
+
const threshold = input.threshold ?? 0.8;
|
|
283
|
+
const minAgreement = input.minAgreement;
|
|
284
|
+
const agreementConfidenceFloor = input.agreementConfidenceFloor ?? 0.55;
|
|
285
|
+
const timeoutMs = input.timeoutMs ?? 15_000;
|
|
286
|
+
const passes = [];
|
|
287
|
+
let escalated = false;
|
|
288
|
+
let earlyStop = null;
|
|
289
|
+
const labels = input.mode.kind === 'yes-no' ? ['false', 'true']
|
|
290
|
+
: input.mode.kind === 'choices' ? input.mode.choices.map(parseChoice).map(choice => choice.id)
|
|
291
|
+
: Array.from({ length: input.mode.high - input.mode.low + 1 }, (_, index) => String(input.mode.kind === 'scale' ? input.mode.low + index : index));
|
|
292
|
+
const aggregate = () => {
|
|
293
|
+
const successful = passes.filter(pass => pass.status === 'succeeded');
|
|
294
|
+
const modal = aggregateModalAnswers(labels, successful.map(pass => String(pass.answer)));
|
|
295
|
+
const confidences = successful.flatMap(pass => pass.confidence === null ? [] : [pass.confidence]);
|
|
296
|
+
return {
|
|
297
|
+
algorithm: 'majority-v1', semantics: 'stability-signal-not-correctness',
|
|
298
|
+
passesRequested, escalationPasses, passesRun: passes.length, escalated, earlyStop,
|
|
299
|
+
minAgreement: minAgreement ?? null, agreementConfidenceFloor,
|
|
300
|
+
// Failed/abstaining passes have no vote but remain in the denominator.
|
|
301
|
+
agreementRatio: passes.length ? Math.max(0, ...Object.values(modal.votes)) / passes.length : 0,
|
|
302
|
+
votes: modal.votes,
|
|
303
|
+
answer: modal.value === null ? null : input.mode.kind === 'yes-no' ? modal.value === 'true'
|
|
304
|
+
: input.mode.kind === 'scale' ? Number(modal.value) : modal.value,
|
|
305
|
+
confidenceSummary: { count: confidences.length, missing: passes.length - confidences.length,
|
|
306
|
+
mean: confidences.length ? confidences.reduce((sum, value) => sum + value, 0) / confidences.length : null,
|
|
307
|
+
min: confidences.length ? Math.min(...confidences) : null, max: confidences.length ? Math.max(...confidences) : null },
|
|
308
|
+
passes: [...passes],
|
|
309
|
+
};
|
|
310
|
+
};
|
|
311
|
+
// Sampling agreement is stability evidence, not calibrated correctness.
|
|
312
|
+
// Confidence acceptance retains the original requirement that every pass succeeds.
|
|
313
|
+
const answeredBy = (summary) => {
|
|
314
|
+
const successful = passes.filter(pass => pass.status === 'succeeded');
|
|
315
|
+
if (successful.length < passesRequested || summary.answer === null)
|
|
316
|
+
return null;
|
|
317
|
+
const mean = summary.confidenceSummary.mean;
|
|
318
|
+
if (successful.length === passes.length && !summary.confidenceSummary.missing
|
|
319
|
+
&& summary.agreementRatio >= threshold && mean !== null && mean >= threshold)
|
|
320
|
+
return 'confidence';
|
|
321
|
+
if (minAgreement !== undefined && summary.agreementRatio >= minAgreement
|
|
322
|
+
&& summary.confidenceSummary.count === successful.length && mean !== null
|
|
323
|
+
&& mean + Number.EPSILON >= agreementConfidenceFloor)
|
|
324
|
+
return 'agreement';
|
|
325
|
+
return null;
|
|
326
|
+
};
|
|
327
|
+
const problem = (summary) => {
|
|
328
|
+
if (answeredBy(summary))
|
|
329
|
+
return null;
|
|
330
|
+
const failed = passes.find(pass => pass.status !== 'succeeded');
|
|
331
|
+
if (failed)
|
|
332
|
+
return failed.reason;
|
|
333
|
+
if (summary.agreementRatio < threshold)
|
|
334
|
+
return 'low-agreement';
|
|
335
|
+
return 'low-confidence';
|
|
336
|
+
};
|
|
337
|
+
const finish = (forcedReason) => {
|
|
338
|
+
const summary = aggregate();
|
|
339
|
+
const reason = forcedReason ?? problem(summary);
|
|
340
|
+
const latencyMs = Math.max(0, (options.now?.() ?? Date.now()) - started);
|
|
341
|
+
const model = passes.map(pass => pass.model).filter(model => model !== null).at(-1) ?? null;
|
|
342
|
+
const totalTokens = (key) => {
|
|
343
|
+
const values = passes.flatMap(pass => pass.usage?.[key] == null ? [] : [pass.usage[key]]);
|
|
344
|
+
return values.length ? values.reduce((sum, value) => sum + value, 0) : null;
|
|
345
|
+
};
|
|
346
|
+
const combinedUsage = passes.some(pass => pass.usage !== null)
|
|
347
|
+
? { inputTokens: totalTokens('inputTokens'), outputTokens: totalTokens('outputTokens') } : null;
|
|
348
|
+
const result = reason ? fallback(reason, latencyMs) : {
|
|
349
|
+
schema: 'aiwg-decision-ask/v1', status: 'answered', answer: summary.answer,
|
|
350
|
+
confidence: summary.agreementRatio * summary.confidenceSummary.mean, fallback: null, reason: 'none',
|
|
351
|
+
model, usage: combinedUsage, latencyMs,
|
|
352
|
+
...(input.mode.kind === 'yes-no' ? { probability: passes.reduce((sum, pass) => sum + (pass.probability ?? 0), 0)
|
|
353
|
+
/ passes.filter(pass => pass.status === 'succeeded').length } : {}),
|
|
354
|
+
};
|
|
355
|
+
return { ...result, model, usage: combinedUsage, aggregate: summary, answeredBy: reason ? null : answeredBy(summary) };
|
|
356
|
+
};
|
|
245
357
|
let credential;
|
|
246
358
|
try {
|
|
247
359
|
credential = await lookupJevCredential(options);
|
|
248
360
|
}
|
|
249
361
|
catch {
|
|
250
|
-
return
|
|
362
|
+
return finish('not-configured');
|
|
251
363
|
}
|
|
252
364
|
if (!credential?.enabled || !credential.token)
|
|
253
|
-
return
|
|
254
|
-
const timeoutMs = input.timeoutMs ?? 15_000;
|
|
365
|
+
return finish('not-configured');
|
|
255
366
|
const def = definition(input);
|
|
256
367
|
const rules = ruleset(def);
|
|
257
|
-
const bind = binding(rules, def.spec.answer.kind, timeoutMs);
|
|
258
368
|
const adapter = options.adapter ?? options.createAdapter?.({ region: credential.region, endpoint: credential.endpoint })
|
|
259
369
|
?? new JevDecisionAdapter({ region: credential.region, ...(credential.endpoint ? {
|
|
260
|
-
endpoint: credential.endpoint,
|
|
261
|
-
allowedOrigins: [new URL(credential.endpoint).origin],
|
|
370
|
+
endpoint: credential.endpoint, allowedOrigins: [new URL(credential.endpoint).origin],
|
|
262
371
|
} : {}) });
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
372
|
+
const controller = new AbortController();
|
|
373
|
+
const remainingMs = () => timeoutMs - (Date.now() - wallStarted);
|
|
374
|
+
const timer = setTimeout(() => controller.abort(), Math.max(0, remainingMs()));
|
|
375
|
+
const invocationId = options.invocationId ?? `ask-${Date.now()}`;
|
|
376
|
+
const runPass = async (index) => {
|
|
377
|
+
const passStarted = Date.now();
|
|
378
|
+
let removeListener = () => { };
|
|
379
|
+
const interrupted = new Promise((_resolve, reject) => {
|
|
380
|
+
const abort = () => reject(new Error('ask deadline'));
|
|
381
|
+
if (controller.signal.aborted)
|
|
382
|
+
abort();
|
|
383
|
+
else {
|
|
384
|
+
controller.signal.addEventListener('abort', abort, { once: true });
|
|
385
|
+
removeListener = () => controller.signal.removeEventListener('abort', abort);
|
|
386
|
+
}
|
|
275
387
|
});
|
|
388
|
+
try {
|
|
389
|
+
const result = await Promise.race([interrupted, (options.evaluate ?? evaluateDecisionRuleset)({
|
|
390
|
+
ruleset: rules, binding: binding(rules, def.spec.answer.kind, Math.max(1, remainingMs())),
|
|
391
|
+
definitions: { [def.metadata.id]: def }, input: { message: contextMessage(input) },
|
|
392
|
+
runId: options.runId ?? 'aiwg-decision-ask', invocationId: `${invocationId}-pass-${index}`,
|
|
393
|
+
signal: controller.signal, adapters: { jev: adapter },
|
|
394
|
+
resolveCredential: options.resolveCredential ?? (() => Promise.resolve(new TextEncoder().encode(credential.token))),
|
|
395
|
+
projection: { resolve: () => projectionPolicy(credential.region, credential.endpoint) },
|
|
396
|
+
})]);
|
|
397
|
+
const evaluation = result.spec.evaluations.answer;
|
|
398
|
+
const attempt = evaluation?.spec.attempts.at(-1);
|
|
399
|
+
const succeeded = evaluation?.spec.status === 'success';
|
|
400
|
+
return { index, status: succeeded ? 'succeeded' : evaluation?.spec.status === 'abstained' ? 'abstained' : 'failed',
|
|
401
|
+
reason: succeeded ? 'none' : evaluation?.spec.reason ?? result.spec.reason,
|
|
402
|
+
...(succeeded ? publicAnswer(input.mode, evaluation.spec.value, evaluation.spec.uncertainty?.distribution) : { answer: null }),
|
|
403
|
+
confidence: succeeded ? confidenceFor(input.mode, evaluation) : null,
|
|
404
|
+
model: attempt?.actualModel ?? null, usage: usage(attempt?.usage), latencyMs: Date.now() - passStarted };
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
return { index, status: 'failed', reason: controller.signal.aborted ? 'timeout' : 'jev-error', answer: null,
|
|
408
|
+
confidence: null, model: null, usage: null, latencyMs: Date.now() - passStarted };
|
|
409
|
+
}
|
|
410
|
+
finally {
|
|
411
|
+
removeListener();
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
const certainFailure = (total) => {
|
|
415
|
+
const summary = aggregate();
|
|
416
|
+
const remaining = total - passes.length;
|
|
417
|
+
const bestVotes = Math.max(0, ...Object.values(summary.votes));
|
|
418
|
+
const successful = passes.filter(pass => pass.status === 'succeeded');
|
|
419
|
+
const confidenceSum = successful.reduce((sum, pass) => sum + (pass.confidence ?? 0), 0);
|
|
420
|
+
// Give every unspent pass a matching vote and perfect confidence. Stop only
|
|
421
|
+
// when even that best case cannot answer, allowing roundoff at the boundary.
|
|
422
|
+
const bestAgreement = (bestVotes + remaining) / total;
|
|
423
|
+
const knownConfidence = successful.every(pass => pass.confidence !== null);
|
|
424
|
+
const confidenceReachable = successful.length === passes.length && knownConfidence
|
|
425
|
+
&& bestAgreement + 1e-12 >= threshold && (confidenceSum + remaining) / total + 1e-12 >= threshold;
|
|
426
|
+
const agreementReachable = minAgreement !== undefined && knownConfidence
|
|
427
|
+
&& successful.length + remaining >= passesRequested
|
|
428
|
+
&& bestAgreement + 1e-12 >= minAgreement
|
|
429
|
+
&& (confidenceSum + remaining) / (successful.length + remaining) + 1e-12 >= agreementConfidenceFloor;
|
|
430
|
+
return !confidenceReachable && !agreementReachable;
|
|
431
|
+
};
|
|
432
|
+
try {
|
|
433
|
+
let target = passesRequested;
|
|
434
|
+
while (passes.length < target) {
|
|
435
|
+
if (controller.signal.aborted || remainingMs() <= 0) {
|
|
436
|
+
earlyStop = 'timeout';
|
|
437
|
+
return finish('timeout');
|
|
438
|
+
}
|
|
439
|
+
const offset = passes.length;
|
|
440
|
+
if (offset >= passesRequested)
|
|
441
|
+
escalated = true;
|
|
442
|
+
passes.push(...await Promise.all(Array.from({ length: Math.min(4, target - offset) }, (_, index) => runPass(offset + index))));
|
|
443
|
+
if (controller.signal.aborted || remainingMs() <= 0) {
|
|
444
|
+
earlyStop = 'timeout';
|
|
445
|
+
return finish('timeout');
|
|
446
|
+
}
|
|
447
|
+
if (passes.length >= passesRequested && problem(aggregate()) && escalationPasses > passesRequested) {
|
|
448
|
+
target = escalationPasses;
|
|
449
|
+
}
|
|
450
|
+
if (passes.length < target && (passes.length >= passesRequested || certainFailure(passesRequested)) && certainFailure(escalationPasses)) {
|
|
451
|
+
earlyStop = 'certain-failure';
|
|
452
|
+
return finish();
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return finish();
|
|
276
456
|
}
|
|
277
|
-
|
|
278
|
-
|
|
457
|
+
finally {
|
|
458
|
+
clearTimeout(timer);
|
|
279
459
|
}
|
|
280
|
-
const latencyMs = Math.max(0, (options.now?.() ?? Date.now()) - started);
|
|
281
|
-
const evaluation = result.spec.evaluations.answer;
|
|
282
|
-
const attempt = evaluation?.spec.attempts.at(-1);
|
|
283
|
-
if (!evaluation || evaluation.spec.status !== 'success')
|
|
284
|
-
return fallback(evaluation?.spec.reason ?? result.spec.reason, latencyMs, attempt);
|
|
285
|
-
const confidence = confidenceFor(input.mode, evaluation);
|
|
286
|
-
const threshold = input.threshold ?? 0.8;
|
|
287
|
-
if (confidence === null || confidence < threshold)
|
|
288
|
-
return fallback('low-confidence', latencyMs, attempt);
|
|
289
|
-
const answer = publicAnswer(input.mode, evaluation.spec.value, evaluation.spec.uncertainty?.distribution);
|
|
290
|
-
return {
|
|
291
|
-
schema: 'aiwg-decision-ask/v1',
|
|
292
|
-
status: 'answered',
|
|
293
|
-
answer: answer.answer,
|
|
294
|
-
confidence,
|
|
295
|
-
...(answer.probability === undefined ? {} : { probability: answer.probability }),
|
|
296
|
-
fallback: null,
|
|
297
|
-
reason: 'none',
|
|
298
|
-
model: attempt?.actualModel ?? null,
|
|
299
|
-
usage: usage(attempt?.usage),
|
|
300
|
-
latencyMs,
|
|
301
|
-
};
|
|
302
460
|
}
|
|
303
461
|
export async function readDecisionContextFile(filePath) {
|
|
304
462
|
const content = await readFile(filePath, 'utf8');
|
|
@@ -342,6 +500,8 @@ export async function setupJev(input = {}, options = {}) {
|
|
|
342
500
|
mode: { kind: 'yes-no' },
|
|
343
501
|
threshold: 0,
|
|
344
502
|
timeoutMs: 15_000,
|
|
503
|
+
passes: 1,
|
|
504
|
+
escalationPasses: 1,
|
|
345
505
|
}, options);
|
|
346
506
|
result.verified = verified.status === 'answered';
|
|
347
507
|
result.model = verified.model;
|
|
@@ -223,7 +223,7 @@ export function validateContextLiveApproval(approval, corpus) {
|
|
|
223
223
|
throw new Error('TV-12 collection margin is below the preregistered maximum');
|
|
224
224
|
}
|
|
225
225
|
export async function assertContextLiveSource(root, expected) {
|
|
226
|
-
const git = (...args) => execFileSync('git', args, { cwd: root, encoding: 'utf8', timeout: 10_000 }).trim();
|
|
226
|
+
const git = (...args) => execFileSync('git', args, { cwd: root, encoding: 'utf8', timeout: 10_000, killSignal: 'SIGKILL' }).trim();
|
|
227
227
|
if (git('rev-parse', 'HEAD') !== expected || git('status', '--porcelain', '--untracked-files=all'))
|
|
228
228
|
throw new Error('TV-12 requires clean exact source commit');
|
|
229
229
|
}
|
|
@@ -452,7 +452,7 @@ export async function collectContextLiveCase(item, corpus, approval, host, budge
|
|
|
452
452
|
}
|
|
453
453
|
/** `exact` (run evidence) requires the canonical root itself; `within` (prepared inputs) allows a descendant. */
|
|
454
454
|
export async function assertContextArtifactRoot(sourceRoot, artifactRoot, mode = 'exact') {
|
|
455
|
-
const route = JSON.parse(execFileSync('aiwg', ['artifacts', 'path', '--json', '--check-write'], { cwd: sourceRoot, encoding: 'utf8', timeout: 10_000 }));
|
|
455
|
+
const route = JSON.parse(execFileSync('aiwg', ['artifacts', 'path', '--json', '--check-write'], { cwd: sourceRoot, encoding: 'utf8', timeout: 10_000, killSignal: 'SIGKILL' }));
|
|
456
456
|
if (typeof route.artifact_root !== 'string')
|
|
457
457
|
throw new Error('Canonical artifact root unavailable');
|
|
458
458
|
const delta = relative(await realpath(route.artifact_root), await realpath(artifactRoot));
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import { resolveDecisionEnablement } from '../config/decision-config.js';
|
|
1
2
|
import { existsSync } from 'node:fs';
|
|
2
3
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
6
|
import { askDecision, setupJev, DecisionAskUsageError, readDecisionContextFile, validateDecisionAskInput, } from './ask.js';
|
|
6
|
-
import { recordDecisionAsk, decisionLedgerHistory, decisionLedgerStats, resolveDecisionSessionId, parseDecisionLedgerSince, DecisionLedgerSinceError, DECISION_LEDGER_SCHEMA, ATTRIBUTION_FIELD_PATTERN, MAX_ATTRIBUTION_FIELD_LENGTH, MAX_RECORDED_CHOICE_IDS, MAX_RECORD_TEXT_BYTES, MAX_LEDGER_RECORD_BYTES, DECISION_LEDGER_RECORD_TIMEOUT_MS, DECISION_SAVINGS_REASONING_ALLOWANCE_TOKENS, DECISION_SAVINGS_ESTIMATION_METHOD, buildDecisionLedgerRecord, appendDecisionLedgerRecord, filterDecisionLedgerRecords, } from './ledger.js';
|
|
7
|
+
import { recordDecisionAsk, decisionLedgerHistory, decisionLedgerStats, resolveDecisionSessionId, resolveDecisionSession, DECISION_SESSION_ENV_VARS, parseDecisionLedgerSince, DecisionLedgerSinceError, DECISION_LEDGER_SCHEMA, DECISION_LEDGER_SCHEMA_V1, ATTRIBUTION_FIELD_PATTERN, MAX_ATTRIBUTION_FIELD_LENGTH, MAX_RECORDED_CHOICE_IDS, MAX_RECORD_TEXT_BYTES, MAX_LEDGER_RECORD_BYTES, DECISION_LEDGER_RECORD_TIMEOUT_MS, DECISION_SAVINGS_REASONING_ALLOWANCE_TOKENS, DECISION_SAVINGS_ESTIMATION_METHOD, buildDecisionLedgerRecord, appendDecisionLedgerRecord, filterDecisionLedgerRecords, } from './ledger.js';
|
|
8
|
+
export { DEFAULT_DECISION_SAVINGS_WEIGHTS, decisionSavingsWeights } from './savings.js';
|
|
7
9
|
import { resolveActiveProvider } from '../cli/provider-resolution.js';
|
|
10
|
+
export { appendDecisionOpportunity, buildDecisionOffloadSnapshot, decisionOffloadCategory, DECISION_OFFLOAD_CHECKPOINT_COVERAGE, DECISION_OFFLOAD_EXECUTABLE_COVERAGE, DECISION_OFFLOAD_SNAPSHOT_SCHEMA, DECISION_OPPORTUNITY_SCHEMA, runDecisionOffloadCheckpoint, } from './offload.js';
|
|
8
11
|
import { assertDecisionEvaluateDispatcherConfig, decisionPatternPacks, getDecisionPatternPack, governedPatternArtifacts, listDecisionPatterns, patternInputSchema, patternOutputSchema, planLiveDecisionPattern, runOfflineDecisionPattern, validateAgainstSchema, validateDecisionDocument, validateDefinition, validateRuleset, } from './index.js';
|
|
9
|
-
export { askDecision, setupJev, readDecisionContextFile, validateDecisionAskInput, DecisionAskUsageError, recordDecisionAsk, decisionLedgerHistory, decisionLedgerStats, resolveDecisionSessionId, parseDecisionLedgerSince, DecisionLedgerSinceError, DECISION_LEDGER_SCHEMA, ATTRIBUTION_FIELD_PATTERN, MAX_ATTRIBUTION_FIELD_LENGTH, MAX_RECORDED_CHOICE_IDS, MAX_RECORD_TEXT_BYTES, MAX_LEDGER_RECORD_BYTES, DECISION_LEDGER_RECORD_TIMEOUT_MS, DECISION_SAVINGS_REASONING_ALLOWANCE_TOKENS, DECISION_SAVINGS_ESTIMATION_METHOD, buildDecisionLedgerRecord, appendDecisionLedgerRecord, filterDecisionLedgerRecords, resolveActiveProvider, };
|
|
12
|
+
export { askDecision, setupJev, readDecisionContextFile, validateDecisionAskInput, DecisionAskUsageError, recordDecisionAsk, decisionLedgerHistory, decisionLedgerStats, resolveDecisionSessionId, resolveDecisionSession, DECISION_SESSION_ENV_VARS, parseDecisionLedgerSince, DecisionLedgerSinceError, DECISION_LEDGER_SCHEMA, DECISION_LEDGER_SCHEMA_V1, ATTRIBUTION_FIELD_PATTERN, MAX_ATTRIBUTION_FIELD_LENGTH, MAX_RECORDED_CHOICE_IDS, MAX_RECORD_TEXT_BYTES, MAX_LEDGER_RECORD_BYTES, DECISION_LEDGER_RECORD_TIMEOUT_MS, DECISION_SAVINGS_REASONING_ALLOWANCE_TOKENS, DECISION_SAVINGS_ESTIMATION_METHOD, buildDecisionLedgerRecord, appendDecisionLedgerRecord, filterDecisionLedgerRecords, resolveActiveProvider, };
|
|
10
13
|
const DECISION_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
11
14
|
const VALIDATION_TARGETS = ['request', 'definition', 'ruleset', 'binding'];
|
|
12
15
|
const OFFLINE_ADAPTER = 'agentic/code/addons/decision-engine/examples/fixture-jev-adapter.mjs';
|
|
@@ -224,9 +227,11 @@ export function decisionCapabilities(options = {}) {
|
|
|
224
227
|
const env = driverEnv(options);
|
|
225
228
|
const profiles = parseMcpRequestProfiles(env.AIWG_DECISION_MCP_REQUESTS, options.cwd ?? process.cwd());
|
|
226
229
|
const packs = decisionPatternPacks;
|
|
230
|
+
const enablement = resolveDecisionEnablement(options);
|
|
227
231
|
return {
|
|
228
232
|
schema: 'aiwg-decision-driver-capabilities/v1',
|
|
229
|
-
enabled:
|
|
233
|
+
enabled: enablement.enabled,
|
|
234
|
+
enabledSource: enablement.source,
|
|
230
235
|
offlineReady: true,
|
|
231
236
|
primitives: [...new Set(packs.map(pack => pack.primitive))].sort(),
|
|
232
237
|
patterns: {
|
|
@@ -238,19 +243,19 @@ export function decisionCapabilities(options = {}) {
|
|
|
238
243
|
features: {
|
|
239
244
|
patterns: ['list', 'show', 'offline-run', 'live-plan'],
|
|
240
245
|
validation: [...VALIDATION_TARGETS],
|
|
241
|
-
evaluate: {
|
|
246
|
+
evaluate: { requiresEnablement: true, mcpRequiresOptIn: true, mcpProfiles: profiles.map(profile => profile.name) },
|
|
242
247
|
setup: ['synthetic-classification'],
|
|
243
248
|
experimental: packs.filter(pack => pack.status === 'experimental').map(pack => pack.id),
|
|
244
249
|
},
|
|
245
250
|
backend: {
|
|
246
|
-
configured:
|
|
251
|
+
configured: enablement.enabled,
|
|
247
252
|
probed: false,
|
|
248
253
|
status: 'not-probed',
|
|
249
254
|
note: 'Capabilities do not infer credential, network, dispatcher, or backend readiness.',
|
|
250
255
|
},
|
|
251
256
|
configuration: {
|
|
252
257
|
env: {
|
|
253
|
-
AIWG_DECISION_ENABLED: env.AIWG_DECISION_ENABLED
|
|
258
|
+
AIWG_DECISION_ENABLED: ['0', '1'].includes(env.AIWG_DECISION_ENABLED ?? '') ? 'set' : 'unset',
|
|
254
259
|
AIWG_DECISION_MCP_REQUESTS: profiles.length ? 'configured' : 'unset',
|
|
255
260
|
},
|
|
256
261
|
},
|
|
@@ -323,7 +328,7 @@ export async function validateDecisionInput(target, filePath, options = {}) {
|
|
|
323
328
|
export async function evaluateRequestPath(requestPath, options = {}, evaluateOptions = {}) {
|
|
324
329
|
const env = driverEnv(options);
|
|
325
330
|
const resolved = resolvePath(requestPath, options.cwd ?? process.cwd());
|
|
326
|
-
if (
|
|
331
|
+
if (!resolveDecisionEnablement(options).enabled) {
|
|
327
332
|
return { schema: 'aiwg-decision-evaluate/v1', status: 'denied', reason: 'explicit-enable-required', exitCode: 2, result: null };
|
|
328
333
|
}
|
|
329
334
|
const validation = await validateDecisionInput('request', resolved, options);
|
|
@@ -338,7 +343,7 @@ export async function evaluateRequestPath(requestPath, options = {}, evaluateOpt
|
|
|
338
343
|
argv.push('--host-policy-module', resolvePath(evaluateOptions.hostPolicyModulePath, options.cwd ?? process.cwd()));
|
|
339
344
|
const exitCode = await dispatcher.runDecisionEvaluate({
|
|
340
345
|
argv,
|
|
341
|
-
env,
|
|
346
|
+
env: { ...env, AIWG_DECISION_ENABLED: '1' },
|
|
342
347
|
runtime,
|
|
343
348
|
stdout: { write(chunk) { stdout += String(chunk); return true; } },
|
|
344
349
|
stderr: { write(chunk) { stderr += String(chunk); return true; } },
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { admitEntry } from '../entry.js';
|
|
2
2
|
import { compareEnsembleKeys, ensembleContractDigest, EnsembleContractError, validateEnsemblePolicy, } from './contract.js';
|
|
3
|
+
/** Modal buckets, with the existing locale-independent canonical tie break. */
|
|
4
|
+
export function aggregateModalAnswers(labels, answers) {
|
|
5
|
+
const ordered = [...labels].sort(compareEnsembleKeys);
|
|
6
|
+
const votes = Object.fromEntries(ordered.map(label => [label, 0]));
|
|
7
|
+
for (const answer of answers) {
|
|
8
|
+
if (!Object.hasOwn(votes, answer))
|
|
9
|
+
throw new EnsembleContractError('unknown modal answer', 'semantic');
|
|
10
|
+
votes[answer] += 1;
|
|
11
|
+
}
|
|
12
|
+
const top = Math.max(0, ...Object.values(votes));
|
|
13
|
+
const winners = ordered.filter(label => votes[label] === top);
|
|
14
|
+
return { value: answers.length ? winners[0] : null, votes, tie: winners.length > 1,
|
|
15
|
+
agreementRatio: answers.length ? top / answers.length : 0 };
|
|
16
|
+
}
|
|
3
17
|
/** Deterministic reference aggregation over already-recorded member results. It never dispatches.
|
|
4
18
|
* Member order does not matter: results are put in canonical (memberId, sampleIndex) order first,
|
|
5
19
|
* so every sum and tie-break runs in the same sequence for any permutation of the input. */
|
|
@@ -24,12 +38,8 @@ export function aggregateEnsembleResults(policyInput, resultsInput, options = {}
|
|
|
24
38
|
const { metric } = policy.disagreement;
|
|
25
39
|
let candidate = null;
|
|
26
40
|
let tie = false;
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
for (const result of succeeded)
|
|
30
|
-
counts[vote(policy, result)] += 1;
|
|
31
|
-
return counts;
|
|
32
|
-
};
|
|
41
|
+
const modal = policy.primitive === 'ordinal-score' ? null
|
|
42
|
+
: aggregateModalAnswers(labels, succeeded.map(result => vote(policy, result)));
|
|
33
43
|
const distributions = () => succeeded.map(result => distributionOf(policy, labels, result));
|
|
34
44
|
const meanOf = (items) => Object.fromEntries(labels.map(label => [label, n ? round(items.reduce((sum, item) => sum + item[label], 0) / n) : 0]));
|
|
35
45
|
const scores = () => succeeded.map(result => result.value);
|
|
@@ -40,7 +50,7 @@ export function aggregateEnsembleResults(policyInput, resultsInput, options = {}
|
|
|
40
50
|
return n ? winners[0] : null;
|
|
41
51
|
};
|
|
42
52
|
if (algorithm === 'majority-v1' || metric === 'vote-share-v1' || metric === 'normalized-entropy-v1')
|
|
43
|
-
statistics.votes = votes
|
|
53
|
+
statistics.votes = modal.votes;
|
|
44
54
|
if (policy.primitive === 'truth-probability' && (algorithm === 'mean-probability-v1' || metric === 'jensen-shannon-v1')) {
|
|
45
55
|
statistics.meanProbability = n ? round(succeeded.reduce((sum, result) => sum + result.value, 0) / n) : null;
|
|
46
56
|
}
|
|
@@ -55,8 +65,10 @@ export function aggregateEnsembleResults(policyInput, resultsInput, options = {}
|
|
|
55
65
|
const middle = Math.floor(n / 2);
|
|
56
66
|
statistics.medianScore = round(n % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2);
|
|
57
67
|
}
|
|
58
|
-
if (algorithm === 'majority-v1')
|
|
59
|
-
candidate =
|
|
68
|
+
if (algorithm === 'majority-v1') {
|
|
69
|
+
candidate = modal.value;
|
|
70
|
+
tie = modal.tie;
|
|
71
|
+
}
|
|
60
72
|
else if (algorithm === 'mean-probability-v1')
|
|
61
73
|
candidate = policy.primitive === 'choice' ? pick(statistics.meanDistribution) : statistics.meanProbability;
|
|
62
74
|
else if (algorithm === 'score-distribution-mean-v1')
|
|
@@ -68,7 +80,8 @@ export function aggregateEnsembleResults(policyInput, resultsInput, options = {}
|
|
|
68
80
|
tie = n % 2 === 0 && sorted[middle - 1] !== sorted[middle];
|
|
69
81
|
candidate = tie ? sorted[middle - 1] : statistics.medianScore;
|
|
70
82
|
}
|
|
71
|
-
const disagreementValue = n === 0 ? 1 :
|
|
83
|
+
const disagreementValue = n === 0 ? 1 : metric === 'vote-share-v1' ? 1 - modal.agreementRatio
|
|
84
|
+
: disagreement(policy, statistics, distributions, n);
|
|
72
85
|
const valueBps = Math.round(Math.min(1, Math.max(0, disagreementValue)) * 10_000);
|
|
73
86
|
const exceeded = valueBps > policy.disagreement.thresholdBps;
|
|
74
87
|
let disposition = 'accept';
|
|
@@ -249,9 +249,13 @@ function dispatchOrder(rows, seed) {
|
|
|
249
249
|
return [...development, ...test];
|
|
250
250
|
}
|
|
251
251
|
export function prepareD17MultifactStudy(seed, moduleDigest, sourceDigests) {
|
|
252
|
-
if (
|
|
252
|
+
if (typeof seed === 'string' && seed.length > 32)
|
|
253
|
+
refuse('seed-too-long');
|
|
254
|
+
if (typeof seed !== 'string' || !/^[a-z0-9][a-z0-9-]{0,31}$/.test(seed))
|
|
255
|
+
refuse('seed-invalid');
|
|
256
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(moduleDigest)
|
|
253
257
|
|| !Object.keys(sourceDigests).length || Object.values(sourceDigests).some(v => !/^sha256:[a-f0-9]{64}$/.test(v)))
|
|
254
|
-
|
|
258
|
+
refuse('source-pin-mismatch');
|
|
255
259
|
const generated = [];
|
|
256
260
|
const gold = { schemaVersion: 'decision-d17mf-gold/v1', labels: {}, worlds: {} };
|
|
257
261
|
for (let index = 0; index < D17MF_ROWS; index++) {
|
|
@@ -17,10 +17,7 @@ export class EntryAdmissionError extends Error {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* Starts an admission-budget clock
|
|
21
|
-
* `process.threadCpuUsage()` once for this reading. `elapsedAdmissionMs`
|
|
22
|
-
* below reads the *type* of what this returns rather than re-detecting the
|
|
23
|
-
* feature, so one admission check always uses one clock end to end.
|
|
20
|
+
* Starts an admission-budget clock using per-thread CPU time.
|
|
24
21
|
*
|
|
25
22
|
* Per-thread CPU time measures only the work the admitting call itself did
|
|
26
23
|
* on this thread, so it is immune both to host scheduling contention
|
|
@@ -29,22 +26,12 @@ export class EntryAdmissionError extends Error {
|
|
|
29
26
|
* other threads' or background GC's CPU time landing on this same process
|
|
30
27
|
* (which a process-wide `process.cpuUsage()` clock would still charge here
|
|
31
28
|
* -- not a safe substitute for the same reason `performance.now()` was not).
|
|
32
|
-
*
|
|
33
|
-
* `process.threadCpuUsage()` was added in Node v22.19.0 (per the installed
|
|
34
|
-
* `@types/node` `@since` annotation) and is absent on this project's
|
|
35
|
-
* `engines.node` floor (`>=20.0.0` in package.json) and on Node
|
|
36
|
-
* 22.0.0-22.18.x. On a runtime without it, this falls back to the
|
|
37
|
-
* pre-#3047 monotonic wall-clock guard as an explicitly documented
|
|
38
|
-
* compatibility fallback: it restores the original host-load sensitivity on
|
|
39
|
-
* those older runtimes, it does not resolve it there.
|
|
40
29
|
*/
|
|
41
30
|
export function admissionClockStart() {
|
|
42
|
-
return
|
|
31
|
+
return process.threadCpuUsage();
|
|
43
32
|
}
|
|
44
33
|
/** Elapsed time since `start` (from `admissionClockStart`), in milliseconds. */
|
|
45
34
|
export function elapsedAdmissionMs(start) {
|
|
46
|
-
if (typeof start === 'number')
|
|
47
|
-
return performance.now() - start;
|
|
48
35
|
const diff = process.threadCpuUsage(start);
|
|
49
36
|
return (diff.user + diff.system) / 1000;
|
|
50
37
|
}
|
|
@@ -796,7 +796,7 @@ export async function dagLivePriorSpend(artifactRoot) {
|
|
|
796
796
|
export async function assertDagLiveArtifactRoot(sourceRoot, artifactRoot) {
|
|
797
797
|
let route;
|
|
798
798
|
try {
|
|
799
|
-
route = JSON.parse(execFileSync('aiwg', ['artifacts', 'path', '--json', '--check-write'], { cwd: sourceRoot, encoding: 'utf8', timeout: 30_000 }));
|
|
799
|
+
route = JSON.parse(execFileSync('aiwg', ['artifacts', 'path', '--json', '--check-write'], { cwd: sourceRoot, encoding: 'utf8', timeout: 30_000, killSignal: 'SIGKILL' }));
|
|
800
800
|
}
|
|
801
801
|
catch {
|
|
802
802
|
throw new Error('D12 requires the canonical artifact root');
|
|
@@ -99,6 +99,15 @@ export function finalizeDecisionGraphRun(graph, plan, report, records, ceilings
|
|
|
99
99
|
report.realizedResources.nodeRuns !== flowRecords.length + approvedGates ||
|
|
100
100
|
report.realizedResources.tokens !== flowRecords.reduce((sum, item) => sum + item.response.usage.tokens, 0) ||
|
|
101
101
|
Math.abs(report.realizedResources.costUsd - flowRecords.reduce((sum, item) => sum + item.response.usage.costUsd, 0)) > 1e-9) {
|
|
102
|
+
// Admission or settlement can reject (for example after a wall-clock deadline)
|
|
103
|
+
// before the adapter emits an observation. Flow retains that failure in its
|
|
104
|
+
// trace; surface the cause instead of misdiagnosing it as lost evidence.
|
|
105
|
+
// Still reject the run: an unobserved invocation cannot produce a receipt.
|
|
106
|
+
const failure = report.status === 'failed' && report.trace.find(event => event.type === 'node-attempt-failed' && event.nodeId && !observed.has(event.nodeId) &&
|
|
107
|
+
graph.nodes.some(node => node.id === event.nodeId) &&
|
|
108
|
+
report.checkpoint.failed?.includes(event.nodeId) && typeof event.message === 'string');
|
|
109
|
+
if (failure)
|
|
110
|
+
throw new DecisionGraphError(`Flow graph node '${failure.nodeId}' failed before observation: ${failure.message}`);
|
|
102
111
|
throw new DecisionGraphError('missing Flow graph observation');
|
|
103
112
|
}
|
|
104
113
|
const candidates = graph.terminals.filter(id => observed.get(id)?.status === 'ok');
|
|
@@ -10,7 +10,7 @@ import { QUALIFICATION_PRIVACY_SURFACES, scanQualificationPrivacy } from '../qua
|
|
|
10
10
|
import { executeQualificationPlan, verifyQualificationArtifacts, writeQualificationEvidenceManifest } from '../qualification/runner.js';
|
|
11
11
|
import { qualificationIntegrityAllowlistProblems } from '../qualification/release.js';
|
|
12
12
|
import { redactText } from '../../governance/redaction.js';
|
|
13
|
-
import { heldoutDigest, heldoutRequest, heldoutReservationMicros, heldoutReservationTokens, HeldoutError, HELDOUT_ENV_GATE, heldoutRowsInScope, planHeldoutCollection, validateHeldoutBundle } from './contract.js';
|
|
13
|
+
import { heldoutDigest, heldoutRequest, heldoutReservationMicros, heldoutReservationTokens, HeldoutError, HELDOUT_ENV_GATE, heldoutRowsInScope, heldoutResumeAttempts, heldoutPendingRequests, planHeldoutCollection, validateHeldoutBundle } from './contract.js';
|
|
14
14
|
import { appendHeldoutSpend, appendHeldoutEvent, heldoutDirectory, heldoutEvidenceDigest, heldoutRunsRoot, readHeldoutFile, readHeldoutJournal, reconcileHeldoutBaseline, scanHeldoutSpend, writeHeldoutFile, validateHeldoutJournal, heldoutCollectionAllowance } from './journal.js';
|
|
15
15
|
import { readHeldoutFrozen, sealHeldoutCalibrationPhase, validateHeldoutPhaseAccess } from './calibration.js';
|
|
16
16
|
import { D17_MULTIFACT_CALIBRATION_NOT_AFTER, usesD17MultifactGenerator } from './study-generators.js';
|
|
@@ -70,13 +70,7 @@ export async function collectHeldoutStudy(options) {
|
|
|
70
70
|
throw new HeldoutError('prior-stop');
|
|
71
71
|
if (!offline && prior.runs.some(run => run.corpusDigest === a.corpusDigest && run.source === 'injected-transport'))
|
|
72
72
|
throw new HeldoutError('injected-prior');
|
|
73
|
-
const old = prior.attempts
|
|
74
|
-
if (old.some(attempt => attempt.preregistrationDigest !== a.preregistrationDigest))
|
|
75
|
-
throw new HeldoutError('changed-preregistration');
|
|
76
|
-
const latest = new Map();
|
|
77
|
-
for (const attempt of old)
|
|
78
|
-
if (attempt.ordinal >= (latest.get(key(attempt))?.ordinal ?? 0))
|
|
79
|
-
latest.set(key(attempt), attempt);
|
|
73
|
+
const { old, latest } = heldoutResumeAttempts(bundle, prior.attempts);
|
|
80
74
|
const now = options.now ?? Date.now, sleep = options.sleep ?? delay, started = now();
|
|
81
75
|
// D17-MF (#2850) is scored only through the registered D17 member calibrator: a session whose whole dispatch window
|
|
82
76
|
// (start plus the preregistered session limit) does not end before the calibrator expires never starts. A seed
|
|
@@ -127,20 +121,18 @@ export async function collectHeldoutStudy(options) {
|
|
|
127
121
|
}
|
|
128
122
|
if (failed.has(row.id) || overSlice(row.slice, row.split))
|
|
129
123
|
continue;
|
|
130
|
-
const remaining = row
|
|
124
|
+
const remaining = heldoutPendingRequests(row, plan, latest);
|
|
131
125
|
if (!remaining.length)
|
|
132
126
|
continue;
|
|
133
127
|
// Checkpoint before a subject/paired arms: enough time for every bounded attempt and interval.
|
|
134
|
-
const pairBound = remaining.
|
|
128
|
+
const pairBound = remaining.reduce((n, request) => n + request.attempts, 0) * (plan.requestTimeoutMs + plan.minDispatchIntervalMs);
|
|
135
129
|
if (now() - started + pairBound > plan.sessionLimitMs * 0.8) {
|
|
136
130
|
checkpoint = true;
|
|
137
131
|
break;
|
|
138
132
|
}
|
|
139
|
-
for (const request of remaining) {
|
|
133
|
+
for (const { request, first } of remaining) {
|
|
140
134
|
if (reason || failed.has(row.id))
|
|
141
135
|
break;
|
|
142
|
-
const previous = latest.get(`${row.id}/${request.id}`);
|
|
143
|
-
const first = previous ? previous.ordinal + 1 : 1;
|
|
144
136
|
for (let ordinal = first; ordinal <= 1 + plan.providerFailurePolicy.maxRetries; ordinal++) {
|
|
145
137
|
if (options.signal?.aborted) {
|
|
146
138
|
reason = 'cancelled';
|