@aiwg/cli 2026.10.5 → 2026.10.7
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/addons/composition-engine/lib/runtime.mjs +1401 -0
- package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
- package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
- package/agentic/code/providers/model-capabilities.v1.json +74 -50
- package/bin/aiwg.mjs +7 -3
- package/dist/src/a2a/agent-card.js +11 -0
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/browser-export.js +1 -0
- package/dist/src/artifacts/citation-parser.js +8 -1
- package/dist/src/artifacts/cli.js +57 -6
- package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
- package/dist/src/artifacts/fortemi-core-sync.js +26 -8
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/contract.json +103 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-config.schema.json +212 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-set.schema.json +240 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-manifest.schema.json +451 -0
- package/dist/src/artifacts/fortemi-shard-export.js +284 -0
- package/dist/src/artifacts/graph-query.js +18 -8
- package/dist/src/artifacts/index-adapters.js +339 -0
- package/dist/src/artifacts/index-builder.js +143 -14
- package/dist/src/artifacts/index-files.js +15 -4
- package/dist/src/artifacts/index-preparation.js +2 -0
- package/dist/src/artifacts/index-reader.js +44 -0
- package/dist/src/artifacts/index-refresh.js +114 -55
- package/dist/src/artifacts/index-status.js +20 -5
- package/dist/src/artifacts/memory-maintenance.js +503 -0
- package/dist/src/artifacts/prebuilt-relations.js +3 -1
- package/dist/src/artifacts/query-engine.js +263 -108
- package/dist/src/artifacts/relations-builder.js +2 -2
- package/dist/src/artifacts/source-graph.js +2 -2
- package/dist/src/artifacts/types.js +9 -1
- package/dist/src/auth/client.js +5 -1
- package/dist/src/auth/credential-store.js +5 -1
- package/dist/src/auth/sensitive-input.js +151 -0
- package/dist/src/channel/manager.mjs +3 -0
- package/dist/src/cli/handlers/agentcard.js +47 -25
- package/dist/src/cli/handlers/auth.js +22 -2
- package/dist/src/cli/handlers/decision.js +22 -3
- package/dist/src/cli/handlers/effect.js +4 -84
- package/dist/src/cli/handlers/guided-setup.js +470 -0
- package/dist/src/cli/handlers/help.js +2 -2
- package/dist/src/cli/handlers/housekeeper.js +1110 -0
- package/dist/src/cli/handlers/index.js +4 -2
- package/dist/src/cli/handlers/init.js +41 -6
- package/dist/src/cli/handlers/install.js +6 -0
- package/dist/src/cli/handlers/local-executor.js +5 -3
- package/dist/src/cli/handlers/marketplace.js +15 -4
- package/dist/src/cli/handlers/mc.js +509 -53
- package/dist/src/cli/handlers/models.js +32 -2
- package/dist/src/cli/handlers/ralph-launcher.js +76 -4
- package/dist/src/cli/handlers/rules.js +1 -1
- package/dist/src/cli/handlers/script-runner.js +73 -2
- package/dist/src/cli/handlers/serve.js +5 -3
- package/dist/src/cli/handlers/sessions.js +1 -0
- package/dist/src/cli/handlers/setup.js +432 -1
- package/dist/src/cli/handlers/skill-lint.js +1 -1
- package/dist/src/cli/handlers/subcommands.js +118 -8
- package/dist/src/cli/handlers/use.js +335 -107
- package/dist/src/cli/handlers/workspace.js +3 -1
- package/dist/src/cli/prompt-utils.js +81 -0
- package/dist/src/cli/router.js +16 -1
- package/dist/src/config/aiwg-config.js +42 -0
- package/dist/src/config/cli.js +7 -3
- package/dist/src/config/decision-config.js +14 -1
- package/dist/src/config/gitignore.js +4 -0
- package/dist/src/config/native-sandbox-warnings.js +25 -0
- package/dist/src/config/workspace.js +2 -1
- package/dist/src/contributors/validation.js +6 -5
- package/dist/src/decision/adapters/jev.js +20 -31
- package/dist/src/decision/admission.js +7 -3
- package/dist/src/decision/ask.js +6 -1
- package/dist/src/decision/benchmark.js +482 -0
- package/dist/src/decision/ledger.js +22 -3
- package/dist/src/decision/pricing.js +147 -0
- package/dist/src/decision/qualification/gate-evidence.js +23 -18
- package/dist/src/decision/qualification/load-staged.js +576 -0
- package/dist/src/decision/qualification/load.js +109 -33
- package/dist/src/decision/qualification/runner.js +12 -5
- package/dist/src/decision/review/service.js +6 -4
- package/dist/src/decision/review/validate.js +23 -2
- package/dist/src/decision/savings.js +8 -3
- package/dist/src/discovery/index.js +492 -0
- package/dist/src/discovery/interview.js +238 -0
- package/dist/src/discovery/plan-apply.js +567 -0
- package/dist/src/discovery/plan-store.js +171 -0
- package/dist/src/discovery/plan-types.js +2 -0
- package/dist/src/discovery/plan-validation.js +139 -0
- package/dist/src/discovery/types.js +2 -0
- package/dist/src/effects/host.js +91 -0
- package/dist/src/extensions/commands/definitions.js +49 -9
- package/dist/src/extensions/manifest.js +71 -44
- package/dist/src/extensions/project-local-discovery.js +1 -1
- package/dist/src/extensions/project-local-gitignore.js +2 -0
- package/dist/src/extensions/project-quickref.js +18 -17
- package/dist/src/extensions/validation.js +32 -29
- package/dist/src/housekeeper/daemon-config.js +148 -0
- package/dist/src/housekeeper/ipc.js +84 -0
- package/dist/src/housekeeper/lifecycle.js +148 -0
- package/dist/src/housekeeper/platform.js +35 -0
- package/dist/src/housekeeper/status.js +70 -0
- package/dist/src/housekeeper/units.js +114 -0
- package/dist/src/issues/cli.js +16 -5
- package/dist/src/issues/live.js +44 -12
- package/dist/src/jobs/flow.js +22 -21
- package/dist/src/lint/rule-activation.js +20 -5
- package/dist/src/lint/runner.js +1 -1
- package/dist/src/marketplace/exchange.js +18 -6
- package/dist/src/marketplace/provenance.js +3 -2
- package/dist/src/mcp/adapters/codex-runtime.js +46 -76
- package/dist/src/mcp/cli.mjs +22 -96
- package/dist/src/mcp/config-file.mjs +6 -2
- package/dist/src/mcp/config-layers.mjs +23 -7
- package/dist/src/mcp/grok-build-config.mjs +2 -1
- package/dist/src/mcp/muse-native-config.mjs +2 -1
- package/dist/src/mcp/profiles.js +18 -31
- package/dist/src/mcp/profiles.mjs +18 -32
- package/dist/src/mcp/registry.js +7 -27
- package/dist/src/mcp/registry.mjs +7 -28
- package/dist/src/mcp/server.mjs +34 -1
- package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
- package/dist/src/mcp/tools/command-run.mjs +3 -2
- package/dist/src/mcp/tools/decision.mjs +49 -48
- package/dist/src/mcp/tools/discovery.mjs +46 -21
- package/dist/src/mcp/tools/orchestration.mjs +22 -22
- package/dist/src/mcp/tools/subsystems.mjs +9 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +30 -6
- package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
- package/dist/src/memory/audit.js +719 -0
- package/dist/src/memory/canonical-context.js +392 -13
- package/dist/src/memory/canonical-maintenance.js +95 -0
- package/dist/src/memory/cli-result.js +22 -0
- package/dist/src/memory/cli.js +176 -0
- package/dist/src/memory/context-pack.js +57 -6
- package/dist/src/memory/cost.js +512 -0
- package/dist/src/memory/firewall-classification.d.mts +7 -0
- package/dist/src/memory/firewall-classification.mjs +32 -0
- package/dist/src/memory/index.js +8 -0
- package/dist/src/memory/line-maintenance.js +68 -0
- package/dist/src/memory/maintenance-cli.js +286 -0
- package/dist/src/memory/maintenance-contract.js +136 -0
- package/dist/src/memory/maintenance-index.js +18 -0
- package/dist/src/memory/maintenance-owner.js +2 -0
- package/dist/src/memory/maintenance-record.js +138 -0
- package/dist/src/memory/maintenance.js +254 -0
- package/dist/src/memory/project-registry.js +2 -1
- package/dist/src/memory/provider-memory-adapter.js +154 -0
- package/dist/src/memory/recommend-cli.js +83 -0
- package/dist/src/memory/recommend.js +269 -0
- package/dist/src/memory/scheduled.js +81 -0
- package/dist/src/memory/taxonomy.js +44 -0
- package/dist/src/mission-protocol/codecs.js +19 -9
- package/dist/src/mission-protocol/worker-status.js +91 -1
- package/dist/src/models/config-loader.js +11 -1
- package/dist/src/models/model-capabilities.v1.json +74 -50
- package/dist/src/models/provider-policy.js +125 -62
- package/dist/src/models/resolver.js +45 -8
- package/dist/src/models/worker-installation.d.mts +8 -1
- package/dist/src/models/worker-installation.mjs +231 -38
- package/dist/src/models/wrapper-route.js +1 -1
- package/dist/src/output-modes/registry.js +8 -7
- package/dist/src/packages/adapters/git.js +99 -23
- package/dist/src/packages/adapters/gitea.js +8 -27
- package/dist/src/packages/adapters/github.js +7 -25
- package/dist/src/packages/registry.js +4 -4
- package/dist/src/plugin/metadata-validator.js +1 -1
- package/dist/src/process/cli-command.d.mts +5 -0
- package/dist/src/process/cli-command.mjs +42 -0
- package/dist/src/providers/deploy-version.d.mts +1 -0
- package/dist/src/providers/deploy-version.mjs +17 -0
- package/dist/src/providers/provider-definitions.js +19 -16
- package/dist/src/providers/skill-ownership.d.mts +11 -2
- package/dist/src/providers/skill-ownership.mjs +35 -17
- package/dist/src/rules/activation.d.mts +11 -2
- package/dist/src/rules/activation.mjs +82 -6
- package/dist/src/rules/inventory.js +1 -1
- package/dist/src/rules/rule-frontmatter.schema.json +2 -0
- package/dist/src/schema/zod-validation.d.mts +6 -0
- package/dist/src/schema/zod-validation.mjs +50 -0
- package/dist/src/serve/executor-registry.js +297 -20
- package/dist/src/serve/executor-token.d.mts +1 -0
- package/dist/src/serve/executor-token.mjs +8 -0
- package/dist/src/serve/shared-host-scheduler.js +1102 -31
- package/dist/src/serve/worker-invocation.js +124 -15
- package/dist/src/sessions/adapters/claude.js +19 -18
- package/dist/src/sessions/adapters/codex.js +10 -9
- package/dist/src/sessions/adapters/copilot.js +14 -13
- package/dist/src/sessions/adapters/cursor.js +19 -18
- package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
- package/dist/src/sessions/adapters/factory.js +11 -10
- package/dist/src/sessions/adapters/generic.js +32 -23
- package/dist/src/sessions/adapters/grokbot.js +35 -35
- package/dist/src/sessions/adapters/hermes.js +20 -19
- package/dist/src/sessions/adapters/muse.js +50 -49
- package/dist/src/sessions/adapters/omp.js +5 -4
- package/dist/src/sessions/adapters/openclaw.js +30 -29
- package/dist/src/sessions/adapters/opencode.js +26 -26
- package/dist/src/sessions/adapters/openhuman.js +24 -23
- package/dist/src/sessions/adapters/pi.js +5 -4
- package/dist/src/sessions/adapters/windsurf.js +8 -8
- package/dist/src/sessions/analytics.js +10 -9
- package/dist/src/sessions/candidates.js +7 -6
- package/dist/src/sessions/contracts.js +63 -53
- package/dist/src/sessions/output-registration.js +11 -10
- package/dist/src/sessions/workspace-discovery.js +3 -4
- package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
- package/dist/src/smiths/context-pipeline/claude-hook.js +46 -10
- package/dist/src/smiths/context-pipeline/context-file-safety.js +330 -9
- package/dist/src/smiths/context-pipeline/finalization.js +3 -2
- package/dist/src/smiths/context-pipeline/generator.js +7 -19
- package/dist/src/smiths/context-pipeline/legacy-inject.js +3 -10
- package/dist/src/smiths/context-pipeline/managed-hook.js +5 -5
- package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
- package/dist/src/smiths/context-pipeline/workspace-context.js +110 -22
- package/dist/src/tracker/project-inputs.js +2 -0
- package/dist/src/update/checker.d.mts +4 -0
- package/dist/src/update/checker.mjs +14 -0
- package/dist/src/update/notifier.mjs +2 -4
- package/dist/src/writing/exemplar-selection.js +6 -6
- package/dist/src/writing/voice-evaluation.js +24 -23
- package/dist/src/writing/writer-migration.js +14 -13
- package/dist/src/writing/writer-profile.js +19 -18
- package/dist/src/writing/writing-brief.js +18 -17
- package/dist/src/writing/writing-receipt.js +47 -46
- package/package.json +5 -2
- package/schemas/decision/DecisionReview.schema.json +78 -1
- package/schemas/mission-v1.schema.json +3 -0
- package/tools/agents/deploy-agents.mjs +67 -39
- package/tools/agents/providers/antigravity.mjs +6 -1
- package/tools/agents/providers/base.mjs +24 -14
- package/tools/agents/providers/claude.mjs +6 -3
- package/tools/agents/providers/codex.mjs +54 -57
- package/tools/agents/providers/context-write.mjs +28 -0
- package/tools/agents/providers/copilot.mjs +13 -4
- package/tools/agents/providers/cursor.mjs +1 -1
- package/tools/agents/providers/deepseek-harness.mjs +1 -1
- package/tools/agents/providers/factory.mjs +7 -1
- package/tools/agents/providers/grok-build.mjs +18 -2
- package/tools/agents/providers/grokbot.mjs +1 -1
- package/tools/agents/providers/hermes.mjs +12 -15
- package/tools/agents/providers/muse-hooks.mjs +21 -14
- package/tools/agents/providers/muse.mjs +7 -2
- package/tools/agents/providers/omp.mjs +31 -5
- package/tools/agents/providers/opencode.mjs +14 -5
- package/tools/agents/providers/openhuman.mjs +1 -1
- package/tools/agents/providers/pi.mjs +1 -1
- package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
- package/tools/agents/providers/rule-delivery.mjs +289 -80
- package/tools/agents/providers/warp.mjs +4 -1
- package/tools/agents/providers/windsurf.mjs +13 -3
- package/tools/cli/workspace-status.mjs +5 -0
- package/tools/commands/deploy-prompts-codex.mjs +11 -8
- package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +26 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +7 -1
- package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
- package/tools/ralph-external/lib/recovery-probe.mjs +14 -4
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
- package/tools/ralph-external/process-monitor.mjs +49 -8
- package/tools/ralph-external/session-launcher.mjs +405 -35
- package/tools/skills/deploy-skills-codex.mjs +11 -7
- package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
import { cpus, hostname, totalmem } from 'node:os';
|
|
2
|
+
import { canonicalJson } from '../../security/artifact-trust.js';
|
|
3
|
+
import { JevDecisionAdapter, compileJevQuestion } from '../adapters/jev.js';
|
|
4
|
+
import { DecisionAdmissionRegistry } from '../admission.js';
|
|
5
|
+
import { CanonicalJsonByteEstimator } from '../context-plan.js';
|
|
6
|
+
import { assertContextLiveSource } from '../context-live-qualification.js';
|
|
7
|
+
import { tokenCost } from '../pricing.js';
|
|
8
|
+
import { admissionControllerLoadDriver, decisionLoadArrivals, compareDecisionLoadBounds, decisionLoadManifestDigest, decisionLoadPolicy, runDecisionLoadHarness, } from './load.js';
|
|
9
|
+
export const DECISION_STAGED_LOAD_RESULT_VERSIONS = ['decision-load-result/v1', 'decision-load-result/v2'];
|
|
10
|
+
const finite = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0;
|
|
11
|
+
const positive = (value) => finite(value) && value > 0;
|
|
12
|
+
const exactCommit = (value) => /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(value);
|
|
13
|
+
/** Read historical manifests without rewriting them; v3 is the only staged runner input. */
|
|
14
|
+
export function readDecisionLoadManifest(bytes) {
|
|
15
|
+
const value = JSON.parse(bytes);
|
|
16
|
+
if (value?.schema === 'decision-load-manifest/v1' && value.profile && value.bounds && value.duration)
|
|
17
|
+
return value;
|
|
18
|
+
if (value?.schema === 'decision-load-manifest/v2' && value.workspace && value.providers && value.principals && value.arrivalModel && value.bounds)
|
|
19
|
+
return value;
|
|
20
|
+
validateDecisionLoadManifestV3(value);
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
export function validateDecisionLoadManifestV3(value) {
|
|
24
|
+
const m = value;
|
|
25
|
+
if (!m || m.schema !== 'decision-load-manifest/v3' || m.mode !== 'staged-provider' || !m.version || !m.profileVersion
|
|
26
|
+
|| !/^jev-\d+\.\d+\.\d+$/.test(m.model) || !m.budget || !positive(m.budget.maxUsd) || m.budget.stopAtFraction !== 0.8
|
|
27
|
+
|| ![m.budget.maxCalls, m.budget.maxTokens, m.budget.maxWallClockSeconds, m.sampleIntervalMs, m.requestTimeoutMs].every(v => Number.isSafeInteger(v) && v > 0)
|
|
28
|
+
|| !m.stopConditions || m.stopConditions.maximumRetryAmplificationRatio !== 1.25 || m.stopConditions.maximumCrossPrincipalQuotaViolations !== 0
|
|
29
|
+
|| ![m.stopConditions.maximumBreakerOpenMs, m.stopConditions.maximum429StormMs].every(positive)
|
|
30
|
+
|| !m.rollout || !m.rollout.previousProfileVersion || m.rollout.previousProfileVersion === m.profileVersion
|
|
31
|
+
|| ![m.rollout.shadowSeconds, m.rollout.rollbackSeconds].every(positive)
|
|
32
|
+
|| !m.duration || ![m.duration.loadSeconds, m.duration.spikeSeconds, m.duration.soakSeconds, m.duration.evaluatorSampleSeconds].every(positive)
|
|
33
|
+
|| !m.principals?.length || !m.principals.some(p => p.class === 'noisy') || !m.principals.some(p => p.class === 'quiet')
|
|
34
|
+
|| new Set(m.principals.map(p => p.id)).size !== m.principals.length
|
|
35
|
+
|| !m.providers || Object.keys(m.providers).join() !== 'jev' || !m.rollout.providers || Object.keys(m.rollout.providers).join() !== 'jev') {
|
|
36
|
+
throw new Error('Invalid staged v3 manifest: explicit maxUsd and complete budget, stop conditions and profiles required');
|
|
37
|
+
}
|
|
38
|
+
const scopes = [m.workspace, ...Object.values(m.providers), ...m.principals.map(p => p.limits), m.rollout.workspace, ...Object.values(m.rollout.providers)];
|
|
39
|
+
for (const scope of scopes) {
|
|
40
|
+
if (!scope || !Number.isSafeInteger(scope.concurrency) || scope.concurrency < 1 || scope.concurrency >= 8
|
|
41
|
+
|| ![scope.requestsPerMinute, scope.tokensPerSecond, scope.maxCostUsd, scope.maxRetainedWork, scope.maxQueueLength, scope.maxQueueWaitMs].every(positive)) {
|
|
42
|
+
throw new Error('Every v3 scope requires rpm, tps, cost, retained-work and queue bounds; concurrency must be below 8');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const knownBounds = ['maximumActiveCalls', 'maximumQueuedCalls', 'maximumHeapUsedMiB', 'maximumResidentMemoryMiB', 'maximumCpuPercentOneCore',
|
|
46
|
+
'maximumEligibleLaneWaitMs', 'maximumRetryAmplificationRatio', 'maximumCancellationLatencyMs', 'minimumQuietPrincipalAdmissionRatio', 'maximumSampleGapMs'];
|
|
47
|
+
if (!m.bounds || Object.keys(m.bounds).length !== knownBounds.length || !knownBounds.every(k => finite(m.bounds[k]))
|
|
48
|
+
|| m.bounds.maximumRetryAmplificationRatio > 1.25 || m.bounds.minimumQuietPrincipalAdmissionRatio > 1
|
|
49
|
+
|| m.bounds.maximumActiveCalls >= 8 || m.bounds.maximumSampleGapMs < m.sampleIntervalMs)
|
|
50
|
+
throw new Error('Invalid v3 measurement bounds');
|
|
51
|
+
const a = m.arrivalModel;
|
|
52
|
+
if (!a || a.kind !== 'seeded-bursty-poisson' || !Number.isSafeInteger(a.seed)
|
|
53
|
+
|| ![a.averageRequestsPerSecond, a.spikeMultiplier, a.spikeDurationSeconds, a.smallTokens, a.hugeTokens,
|
|
54
|
+
a.serviceTimeMs?.minimum, a.serviceTimeMs?.maximum, a.retryBackoffMs, a.cancelAfterMs].every(positive)
|
|
55
|
+
|| ![a.hugeTokenShare, a.retryableFailureRate, a.cancellationRate].every(v => finite(v) && v <= 1)
|
|
56
|
+
|| !Number.isSafeInteger(a.maxRetries) || a.maxRetries < 0 || a.maxRetries > 3
|
|
57
|
+
|| a.smallTokens < 2048 || a.hugeTokens > 32768 || a.hugeTokens < a.smallTokens || a.serviceTimeMs.maximum < a.serviceTimeMs.minimum
|
|
58
|
+
|| m.sampleIntervalMs > 1000 || m.sampleIntervalMs > m.budget.maxWallClockSeconds * 1000
|
|
59
|
+
|| !m.passFail?.allBoundsMustPass || !m.passFail.liveServiceRequired)
|
|
60
|
+
throw new Error('Invalid v3 arrival model or sampling interval');
|
|
61
|
+
}
|
|
62
|
+
export function validateDecisionLoadPricing(pricing, model) {
|
|
63
|
+
if (!pricing || !['typesafe-docs', 'typesafe-snapshot'].includes(pricing.source ?? '') || pricing.jev_model !== model
|
|
64
|
+
|| !pricing.checked_at || !Number.isFinite(Date.parse(pricing.checked_at))
|
|
65
|
+
|| !finite(pricing.jev_input_per_mtok) || !finite(pricing.jev_output_per_mtok))
|
|
66
|
+
throw new Error('Sourced split Jev cost config for the pinned model is required');
|
|
67
|
+
}
|
|
68
|
+
const price = (tokens, pricing) => tokens * Math.max(pricing.jev_input_per_mtok, pricing.jev_output_per_mtok) / 1e6;
|
|
69
|
+
/** Run-wide conservative reservations are never refunded on unknown provider outcomes. */
|
|
70
|
+
export class DecisionLoadBudgetGuard {
|
|
71
|
+
manifest;
|
|
72
|
+
now;
|
|
73
|
+
start;
|
|
74
|
+
stopped;
|
|
75
|
+
usage = { calls: 0, tokens: 0, usd: 0 };
|
|
76
|
+
stop = null;
|
|
77
|
+
invocations = new Set();
|
|
78
|
+
open = new Map();
|
|
79
|
+
storm = new Map();
|
|
80
|
+
constructor(manifest, now, start, stopped = () => { }) {
|
|
81
|
+
this.manifest = manifest;
|
|
82
|
+
this.now = now;
|
|
83
|
+
this.start = start;
|
|
84
|
+
this.stopped = stopped;
|
|
85
|
+
}
|
|
86
|
+
halt(reason) {
|
|
87
|
+
if (this.stop)
|
|
88
|
+
return;
|
|
89
|
+
this.stop = { reason, atMs: this.now() - this.start };
|
|
90
|
+
this.stopped();
|
|
91
|
+
}
|
|
92
|
+
reserve(id, tokens, usd) {
|
|
93
|
+
this.tick();
|
|
94
|
+
if (!Number.isSafeInteger(tokens) || tokens < 1 || !finite(usd))
|
|
95
|
+
this.halt('invalid-reservation');
|
|
96
|
+
if (this.stop)
|
|
97
|
+
return false;
|
|
98
|
+
const b = this.manifest.budget;
|
|
99
|
+
if (this.usage.usd + usd > b.maxUsd * b.stopAtFraction + 1e-12)
|
|
100
|
+
this.halt('budget-usd-80-percent');
|
|
101
|
+
else if (this.usage.calls + 1 > b.maxCalls)
|
|
102
|
+
this.halt('max-calls');
|
|
103
|
+
else if (this.usage.tokens + tokens > b.maxTokens)
|
|
104
|
+
this.halt('max-tokens');
|
|
105
|
+
if (this.stop)
|
|
106
|
+
return false;
|
|
107
|
+
const invocationCount = this.invocations.size + (this.invocations.has(id) ? 0 : 1);
|
|
108
|
+
if ((this.usage.calls + 1) / invocationCount > this.manifest.stopConditions.maximumRetryAmplificationRatio) {
|
|
109
|
+
this.halt('retry-amplification');
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
this.usage.calls++;
|
|
113
|
+
this.usage.tokens += tokens;
|
|
114
|
+
this.usage.usd += usd;
|
|
115
|
+
this.invocations.add(id);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
observe(providerId, observation, tokens, usd) {
|
|
119
|
+
const u = observation.usage;
|
|
120
|
+
if (((u.inputTokens ?? 0) + (u.outputTokens ?? 0) > tokens)
|
|
121
|
+
|| (u.costUsd !== null && u.costUsd > usd + 1e-12))
|
|
122
|
+
this.halt('usage-exceeds-reservation');
|
|
123
|
+
if (observation.httpStatus === 429) {
|
|
124
|
+
if (!this.storm.has(providerId))
|
|
125
|
+
this.storm.set(providerId, this.now());
|
|
126
|
+
}
|
|
127
|
+
else if (observation.status === 'success')
|
|
128
|
+
this.storm.delete(providerId);
|
|
129
|
+
if (this.usage.usd >= this.manifest.budget.maxUsd * 0.8)
|
|
130
|
+
this.halt('budget-usd-80-percent');
|
|
131
|
+
if (this.usage.calls >= this.manifest.budget.maxCalls)
|
|
132
|
+
this.halt('max-calls');
|
|
133
|
+
if (this.usage.tokens >= this.manifest.budget.maxTokens)
|
|
134
|
+
this.halt('max-tokens');
|
|
135
|
+
this.tick();
|
|
136
|
+
}
|
|
137
|
+
transition(transition) {
|
|
138
|
+
if (transition.to === 'open')
|
|
139
|
+
this.open.set(transition.providerId, transition.atEpochMs);
|
|
140
|
+
else
|
|
141
|
+
this.open.delete(transition.providerId);
|
|
142
|
+
this.tick();
|
|
143
|
+
}
|
|
144
|
+
tick() {
|
|
145
|
+
if (this.now() - this.start >= this.manifest.budget.maxWallClockSeconds * 1000)
|
|
146
|
+
this.halt('max-wall-clock');
|
|
147
|
+
if ([...this.open.values()].some(at => this.now() - at > this.manifest.stopConditions.maximumBreakerOpenMs))
|
|
148
|
+
this.halt('breaker-open');
|
|
149
|
+
if ([...this.storm.values()].some(at => this.now() - at > this.manifest.stopConditions.maximum429StormMs))
|
|
150
|
+
this.halt('429-storm');
|
|
151
|
+
}
|
|
152
|
+
quotaViolation() { this.halt('cross-principal-quota'); }
|
|
153
|
+
}
|
|
154
|
+
/** Fixed synthetic payload; never accepts user/customer state. D06 estimator checks the reservation before dispatch. */
|
|
155
|
+
function syntheticRequest(arrival) {
|
|
156
|
+
return {
|
|
157
|
+
definition: { apiVersion: 'decision.aiwg.io/v1alpha1', kind: 'DecisionDefinition', metadata: { id: 'd05-synthetic', version: '1.0.0', description: 'Synthetic D05 staging fixture' },
|
|
158
|
+
spec: { purpose: 'Synthetic staging load; no action.', inputSchema: { type: 'object' }, question: 'Select synthetic.',
|
|
159
|
+
answer: { kind: 'choice', options: [{ id: 'synthetic', description: 'Synthetic fixture.' }, { id: 'other', description: 'Other.' }] }, requiredCapabilities: ['choice'] } },
|
|
160
|
+
input: { message: 'fixture '.repeat(Math.floor((arrival.tokens - 1024) * 3 / 8)) },
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
export const realDecisionLoadClock = () => ({ now: Date.now, advance: ms => new Promise(resolve => setTimeout(resolve, ms)) });
|
|
164
|
+
export async function runDecisionStagedLoad(options) {
|
|
165
|
+
const manifest = readDecisionLoadManifest(options.manifestBytes);
|
|
166
|
+
validateDecisionLoadManifestV3(manifest);
|
|
167
|
+
const env = options.env ?? process.env;
|
|
168
|
+
// Validate before constructing an adapter, reading a credential value or starting timers.
|
|
169
|
+
if (options.live && !env[options.credentialEnv ?? 'AIWG_JEV_LOAD_API_KEY']?.trim())
|
|
170
|
+
throw new Error('--live requires the caller-supplied environment credential');
|
|
171
|
+
if (options.live && options.fakeBackend)
|
|
172
|
+
throw new Error('Live load cannot use a fake backend');
|
|
173
|
+
if (options.live && env.NODE_TLS_REJECT_UNAUTHORIZED !== undefined && env.NODE_TLS_REJECT_UNAUTHORIZED !== '1')
|
|
174
|
+
throw new Error('TLS verification must remain enabled');
|
|
175
|
+
validateDecisionLoadPricing(options.pricing, manifest.model);
|
|
176
|
+
if (!exactCommit(options.sourceCommit) || !options.networkPath.trim())
|
|
177
|
+
throw new Error('Exact source commit and recorded network path required');
|
|
178
|
+
const assertSource = options.assertSource ?? assertContextLiveSource;
|
|
179
|
+
await assertSource(options.sourceRoot, options.sourceCommit);
|
|
180
|
+
const clock = options.clock ?? realDecisionLoadClock();
|
|
181
|
+
const start = clock.now();
|
|
182
|
+
const abort = new AbortController();
|
|
183
|
+
const guard = new DecisionLoadBudgetGuard(manifest, clock.now, start, () => abort.abort());
|
|
184
|
+
const registry = new DecisionAdmissionRegistry();
|
|
185
|
+
const stages = [];
|
|
186
|
+
const receipts = [];
|
|
187
|
+
const samples = [];
|
|
188
|
+
const transitions = [];
|
|
189
|
+
registry.onBreakerTransition(t => { transitions.push(t); guard.transition(t); });
|
|
190
|
+
const resource = options.sampleResources ?? (() => ({ ...process.memoryUsage(), ...process.resourceUsage() }));
|
|
191
|
+
let previous = resource();
|
|
192
|
+
let previousAt = start;
|
|
193
|
+
const sample = () => {
|
|
194
|
+
const now = clock.now();
|
|
195
|
+
const current = resource();
|
|
196
|
+
const snapshot = registry.controller('load-harness')?.snapshot() ?? { active: 0, queued: 0 };
|
|
197
|
+
samples.push({ atMs: now - start, residentMemoryMiB: current.rss / 1_048_576, heapUsedMiB: current.heapUsed / 1_048_576,
|
|
198
|
+
cpuPercentOneCore: now === previousAt ? 0 : (current.userCPUTime + current.systemCPUTime - previous.userCPUTime - previous.systemCPUTime) / ((now - previousAt) * 10), ...snapshot });
|
|
199
|
+
previous = current;
|
|
200
|
+
previousAt = now;
|
|
201
|
+
guard.tick();
|
|
202
|
+
};
|
|
203
|
+
let sampler;
|
|
204
|
+
let deadline;
|
|
205
|
+
const adapter = options.live ? new JevDecisionAdapter({ now: clock.now }) : null;
|
|
206
|
+
const estimator = new CanonicalJsonByteEstimator();
|
|
207
|
+
const previousManifest = { ...manifest, profileVersion: manifest.rollout.previousProfileVersion,
|
|
208
|
+
workspace: manifest.rollout.workspace, providers: manifest.rollout.providers };
|
|
209
|
+
let phase = 'shadow';
|
|
210
|
+
const inFlight = new Map();
|
|
211
|
+
try {
|
|
212
|
+
for (const principal of manifest.principals)
|
|
213
|
+
registry.register(decisionLoadPolicy(previousManifest, 'load-harness', principal.id), clock.now);
|
|
214
|
+
sampler = setInterval(() => { try {
|
|
215
|
+
sample();
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
guard.halt('driver-error');
|
|
219
|
+
} }, manifest.sampleIntervalMs);
|
|
220
|
+
deadline = setTimeout(() => guard.halt('max-wall-clock'), manifest.budget.maxWallClockSeconds * 1000);
|
|
221
|
+
sample();
|
|
222
|
+
for (phase of ['shadow', 'canary', 'rollback']) {
|
|
223
|
+
const activeManifest = phase === 'rollback' ? previousManifest : manifest;
|
|
224
|
+
const windowSeconds = phase === 'shadow' ? manifest.rollout.shadowSeconds : phase === 'rollback' ? manifest.rollout.rollbackSeconds
|
|
225
|
+
: manifest.duration.loadSeconds + manifest.duration.spikeSeconds + manifest.duration.soakSeconds;
|
|
226
|
+
// Roll back even after a budget or outage stop; no additional provider work follows a stop.
|
|
227
|
+
const scopes = {};
|
|
228
|
+
const observeScopes = () => {
|
|
229
|
+
const controller = registry.controller('load-harness');
|
|
230
|
+
const counts = { workspace: controller.snapshot(),
|
|
231
|
+
...Object.fromEntries(Object.keys(activeManifest.providers).map(id => [`provider.${id}`, controller.snapshot({ providerId: id })])),
|
|
232
|
+
...Object.fromEntries(activeManifest.principals.map(p => [`principal.${p.id}`, controller.snapshot({ principalId: p.id })])) };
|
|
233
|
+
for (const [id, count] of Object.entries(counts))
|
|
234
|
+
scopes[id] = { active: Math.max(scopes[id]?.active ?? 0, count.active), queued: Math.max(scopes[id]?.queued ?? 0, count.queued) };
|
|
235
|
+
return counts.workspace;
|
|
236
|
+
};
|
|
237
|
+
const path = admissionControllerLoadDriver(activeManifest, clock, registry, {
|
|
238
|
+
onStateChange: () => { observeScopes(); },
|
|
239
|
+
onDriverError: () => guard.halt('driver-error'),
|
|
240
|
+
estimateCost: arrival => price(arrival.tokens, options.pricing),
|
|
241
|
+
beforeDispatch: (arrival, attempt) => {
|
|
242
|
+
const request = syntheticRequest(arrival);
|
|
243
|
+
const estimated = estimator.estimate({ state: { untrusted: request.input }, question: compileJevQuestion(request.definition) }).tokens + 256;
|
|
244
|
+
if (estimated > arrival.tokens)
|
|
245
|
+
guard.halt('invalid-reservation');
|
|
246
|
+
return !guard.stop && guard.reserve(`${phase}:${arrival.ordinal}`, arrival.tokens, price(arrival.tokens, options.pricing)) && attempt > 0;
|
|
247
|
+
},
|
|
248
|
+
onAdmission: (arrival, attempt, admission) => {
|
|
249
|
+
const receipt = { phase, arrivalOrdinal: arrival.ordinal, principalId: arrival.principalId,
|
|
250
|
+
providerId: arrival.providerId, attempt, admittedAtMs: clock.now(), settledAtMs: clock.now(), tokens: arrival.tokens,
|
|
251
|
+
costUsd: price(arrival.tokens, options.pricing), admission: structuredClone(admission), status: 'error', reason: 'executor-unavailable',
|
|
252
|
+
usage: { inputTokens: null, outputTokens: null, costUsd: null } };
|
|
253
|
+
receipts.push(receipt);
|
|
254
|
+
inFlight.set(`${arrival.ordinal}:${attempt}`, receipt);
|
|
255
|
+
const ownActive = [...inFlight.values()].filter(r => r.principalId === arrival.principalId).length;
|
|
256
|
+
const principal = manifest.principals.find(p => p.id === arrival.principalId);
|
|
257
|
+
const provider = activeManifest.providers[arrival.providerId];
|
|
258
|
+
const phaseReceipts = receipts.filter(r => r.phase === phase);
|
|
259
|
+
const scoped = [
|
|
260
|
+
[principal.limits, phaseReceipts.filter(r => r.principalId === arrival.principalId), ownActive],
|
|
261
|
+
[activeManifest.workspace, phaseReceipts, inFlight.size],
|
|
262
|
+
[provider, phaseReceipts.filter(r => r.providerId === arrival.providerId), [...inFlight.values()].filter(r => r.providerId === arrival.providerId).length],
|
|
263
|
+
];
|
|
264
|
+
if (scoped.some(([limits, entries, active]) => active > limits.concurrency
|
|
265
|
+
|| bucketDebit(entries, limits.requestsPerMinute, 60000, () => 1) > limits.requestsPerMinute + 1e-9
|
|
266
|
+
|| bucketDebit(entries, limits.tokensPerSecond, 1000, r => r.tokens) > limits.tokensPerSecond + 1e-9
|
|
267
|
+
|| entries.filter(r => r.arrivalOrdinal === arrival.ordinal).reduce((sum, r) => sum + r.costUsd, 0) > limits.maxCostUsd
|
|
268
|
+
|| (limits.maxPrincipalShare !== undefined && ownActive > Math.max(1, Math.floor(limits.concurrency * limits.maxPrincipalShare)))))
|
|
269
|
+
guard.quotaViolation();
|
|
270
|
+
},
|
|
271
|
+
backend: async (arrival, signal, context, attempt) => {
|
|
272
|
+
if (signal.aborted)
|
|
273
|
+
return { status: 'cancelled', reason: 'cancelled', actualModel: null, uncertainty: null, requestId: null,
|
|
274
|
+
usage: { inputTokens: null, outputTokens: null, costUsd: null } };
|
|
275
|
+
if (adapter) {
|
|
276
|
+
const request = syntheticRequest(arrival);
|
|
277
|
+
return adapter.evaluate({ ...request, alias: 'synthetic', invocationId: `d05-${phase}-${arrival.ordinal}`,
|
|
278
|
+
signal, deadlineEpochMs: clock.now() + manifest.requestTimeoutMs,
|
|
279
|
+
target: { adapter: 'jev', adapterVersion: adapter.version, model: manifest.model, credentialRef: 'caller-env', requiredCapabilities: ['choice'],
|
|
280
|
+
acceptance: { mode: 'typed-value' }, timeoutMs: manifest.requestTimeoutMs, retry: { maxRetries: 0, initialDelayMs: 0, maxDelayMs: 0 } },
|
|
281
|
+
resolveCredential: async () => new TextEncoder().encode(env[options.credentialEnv ?? 'AIWG_JEV_LOAD_API_KEY']) });
|
|
282
|
+
}
|
|
283
|
+
if (options.fakeBackend)
|
|
284
|
+
return options.fakeBackend(arrival, signal);
|
|
285
|
+
const status = await context.dispatch(arrival.serviceMs, signal);
|
|
286
|
+
const failed = arrival.failsFirstAttempt && attempt === 1;
|
|
287
|
+
return { status: status === 'aborted' ? 'cancelled' : failed ? 'error' : 'success', reason: status === 'aborted' ? 'cancelled' : failed ? 'service-error' : 'none',
|
|
288
|
+
actualModel: manifest.model, uncertainty: null, requestId: null,
|
|
289
|
+
usage: { inputTokens: arrival.tokens - 256, outputTokens: 1, costUsd: null } };
|
|
290
|
+
},
|
|
291
|
+
onObservation: (arrival, attempt, admission, observation) => {
|
|
292
|
+
const receipt = inFlight.get(`${arrival.ordinal}:${attempt}`);
|
|
293
|
+
Object.assign(receipt, { settledAtMs: clock.now(), admission: structuredClone(admission), status: observation.status, reason: observation.reason,
|
|
294
|
+
usage: observation.usage, ...(observation.httpStatus === undefined ? {} : { httpStatus: observation.httpStatus }),
|
|
295
|
+
...(observation.actualModel ? { servedModel: observation.actualModel } : {}),
|
|
296
|
+
...(observation.requestId ? { requestId: observation.requestId } : {}) });
|
|
297
|
+
inFlight.delete(`${arrival.ordinal}:${attempt}`);
|
|
298
|
+
guard.observe(arrival.providerId, observation, receipt.tokens, receipt.costUsd);
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
if (guard.stop)
|
|
302
|
+
continue;
|
|
303
|
+
const run = await runDecisionLoadHarness({ manifest: activeManifest, clock, ...path, sample: observeScopes, windowSeconds, signal: abort.signal, fullWindow: true });
|
|
304
|
+
const dispatched = receipts.filter(r => r.phase === phase);
|
|
305
|
+
run.observations.attempts = dispatched.length;
|
|
306
|
+
run.observations.retryAmplificationRatio = run.observations.arrivals ? Number((dispatched.length / run.observations.arrivals).toFixed(6)) : 1;
|
|
307
|
+
stages.push({ phase, profileVersion: activeManifest.profileVersion, windowSeconds, ...run, scopes });
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
await assertSource(options.sourceRoot, options.sourceCommit);
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
guard.halt('source-drift');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
guard.halt('driver-error');
|
|
318
|
+
}
|
|
319
|
+
finally {
|
|
320
|
+
// Idempotent boundary restoration preserves the registry's rollback audit and all receipts.
|
|
321
|
+
try {
|
|
322
|
+
for (const principal of manifest.principals)
|
|
323
|
+
registry.register(decisionLoadPolicy(previousManifest, 'load-harness', principal.id), clock.now);
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
guard.halt('driver-error');
|
|
327
|
+
}
|
|
328
|
+
clearInterval(sampler);
|
|
329
|
+
clearTimeout(deadline);
|
|
330
|
+
try {
|
|
331
|
+
sample();
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
guard.halt('driver-error');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return decisionStagedLoadResultRecord({ manifestBytes: options.manifestBytes, mode: 'staged-provider', execution: options.live ? 'live' : 'offline-fake-provider',
|
|
338
|
+
sourceCommit: options.sourceCommit, dirty: false,
|
|
339
|
+
host: { hostname: hostname(), cpuClass: `${cpus().length} cores; ${cpus()[0]?.model ?? 'unknown'}`, memoryGiB: totalmem() / 2 ** 30,
|
|
340
|
+
networkPath: options.networkPath, node: process.version, platform: process.platform, arch: process.arch },
|
|
341
|
+
pricing: options.pricing, stages, receipts, samples, profileHistory: registry.profileHistory('load-harness'), breakerTransitions: transitions,
|
|
342
|
+
reservedUsage: guard.usage, stop: guard.stop, startedAtMs: start, elapsedMs: samples.at(-1)?.atMs ?? clock.now() - start });
|
|
343
|
+
}
|
|
344
|
+
/** Token-bucket debit (burst plus refill), matching the admission controller's rate semantics. */
|
|
345
|
+
function bucketDebit(receipts, capacity, periodMs, amount) {
|
|
346
|
+
let debit = 0;
|
|
347
|
+
let maximum = 0;
|
|
348
|
+
let at = receipts[0]?.admittedAtMs ?? 0;
|
|
349
|
+
for (const receipt of receipts) {
|
|
350
|
+
debit = Math.max(0, debit - Math.max(0, receipt.admittedAtMs - at) * capacity / periodMs) + amount(receipt);
|
|
351
|
+
maximum = Math.max(maximum, debit);
|
|
352
|
+
at = receipt.admittedAtMs;
|
|
353
|
+
}
|
|
354
|
+
return maximum;
|
|
355
|
+
}
|
|
356
|
+
function concurrency(receipts) {
|
|
357
|
+
let maximum = 0;
|
|
358
|
+
for (const r of receipts)
|
|
359
|
+
maximum = Math.max(maximum, receipts.filter(other => other.admittedAtMs <= r.admittedAtMs && other.settledAtMs > r.admittedAtMs).length, 1);
|
|
360
|
+
return maximum;
|
|
361
|
+
}
|
|
362
|
+
const maximum = (values) => Math.max(0, ...values);
|
|
363
|
+
/** Every measurement/stop/budget bound is recomputed from retained evidence, never the record's passed field. */
|
|
364
|
+
export function compareDecisionStagedLoad(record) {
|
|
365
|
+
const m = record.manifest;
|
|
366
|
+
const out = [];
|
|
367
|
+
const atMost = (bound, limit, observed) => {
|
|
368
|
+
out.push({ bound, limit, observed,
|
|
369
|
+
pass: finite(observed) && observed <= limit + 1e-9 });
|
|
370
|
+
};
|
|
371
|
+
for (const stage of record.stages) {
|
|
372
|
+
const resources = { ...stage.resources, heapUsedMiB: maximum(record.samples.map(s => s.heapUsedMiB)),
|
|
373
|
+
cpuSource: 'thread', cpuPercentOneCore: maximum(record.samples.map(s => s.cpuPercentOneCore)) };
|
|
374
|
+
out.push(...compareDecisionLoadBounds(m, { ...stage.observations,
|
|
375
|
+
maximumActiveCalls: Math.max(stage.observations.maximumActiveCalls, stage.scopes?.workspace?.active ?? 0, maximum(record.samples.map(s => s.active))),
|
|
376
|
+
maximumQueuedCalls: Math.max(stage.observations.maximumQueuedCalls, stage.scopes?.workspace?.queued ?? 0, maximum(record.samples.map(s => s.queued))),
|
|
377
|
+
}, resources).map(c => ({ ...c, bound: `${stage.phase}.${c.bound}` })));
|
|
378
|
+
atMost(`${stage.phase}.maximumResidentMemoryMiB`, m.bounds.maximumResidentMemoryMiB, maximum(record.samples.map(s => s.residentMemoryMiB)));
|
|
379
|
+
const receipts = record.receipts.filter(r => r.phase === stage.phase).sort((a, b) => a.admittedAtMs - b.admittedAtMs);
|
|
380
|
+
atMost(`${stage.phase}.quietPrincipalLaneWaitMs`, m.bounds.maximumEligibleLaneWaitMs, maximum(receipts.filter(r => m.principals.find(p => p.id === r.principalId)?.class === 'quiet').map(r => r.admission.queueDelayMs)));
|
|
381
|
+
const workspace = stage.phase === 'rollback' ? m.rollout.workspace : m.workspace;
|
|
382
|
+
const providers = stage.phase === 'rollback' ? m.rollout.providers : m.providers;
|
|
383
|
+
const scopes = [
|
|
384
|
+
['workspace', workspace, receipts],
|
|
385
|
+
...Object.entries(providers).map(([id, limits]) => [`provider.${id}`, limits, receipts.filter(r => r.providerId === id)]),
|
|
386
|
+
...m.principals.map(p => [`principal.${p.id}`, p.limits, receipts.filter(r => r.principalId === p.id)]),
|
|
387
|
+
];
|
|
388
|
+
for (const [scope, limits, entries] of scopes) {
|
|
389
|
+
const prefix = `${stage.phase}.${scope}`;
|
|
390
|
+
atMost(`${prefix}.concurrency`, limits.concurrency, Math.max(concurrency(entries), stage.scopes[scope]?.active ?? 0));
|
|
391
|
+
atMost(`${prefix}.maxQueueLength`, limits.maxQueueLength, stage.scopes[scope]?.queued ?? Number.POSITIVE_INFINITY);
|
|
392
|
+
atMost(`${prefix}.requestsPerMinute`, limits.requestsPerMinute, bucketDebit(entries, limits.requestsPerMinute, 60000, () => 1));
|
|
393
|
+
atMost(`${prefix}.tokensPerSecond`, limits.tokensPerSecond, bucketDebit(entries, limits.tokensPerSecond, 1000, r => r.tokens));
|
|
394
|
+
// Admission cost/retained-work limits are invocation-scoped, as in DecisionAdmissionController.
|
|
395
|
+
const invocations = new Map();
|
|
396
|
+
for (const r of entries)
|
|
397
|
+
invocations.set(r.arrivalOrdinal, (invocations.get(r.arrivalOrdinal) ?? 0) + r.costUsd);
|
|
398
|
+
atMost(`${prefix}.maxCostUsd`, limits.maxCostUsd, maximum([...invocations.values()]));
|
|
399
|
+
atMost(`${prefix}.maxRetainedWork`, limits.maxRetainedWork, entries.length ? 1 : 0);
|
|
400
|
+
atMost(`${prefix}.maxQueueWaitMs`, limits.maxQueueWaitMs, maximum(entries.map(r => r.admission.queueDelayMs)));
|
|
401
|
+
if (limits.maxPrincipalShare !== undefined)
|
|
402
|
+
for (const principal of m.principals) {
|
|
403
|
+
atMost(`${prefix}.${principal.id}.shareConcurrency`, Math.max(1, Math.floor(limits.concurrency * limits.maxPrincipalShare)), concurrency(entries.filter(r => r.principalId === principal.id)));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
atMost('budget.maxUsd.stopAtFraction', m.budget.maxUsd * m.budget.stopAtFraction, record.reservedUsage.usd);
|
|
408
|
+
atMost('budget.maxCalls', m.budget.maxCalls, record.reservedUsage.calls);
|
|
409
|
+
atMost('budget.maxTokens', m.budget.maxTokens, record.reservedUsage.tokens);
|
|
410
|
+
atMost('budget.maxWallClockSeconds', m.budget.maxWallClockSeconds, record.elapsedMs / 1000);
|
|
411
|
+
atMost('maximumSampleGapMs', m.bounds.maximumSampleGapMs, maximum(record.samples.slice(1).map((s, i) => s.atMs - record.samples[i].atMs)));
|
|
412
|
+
const endingAt = record.startedAtMs + record.elapsedMs;
|
|
413
|
+
const open = new Map();
|
|
414
|
+
let openMs = 0;
|
|
415
|
+
for (const t of record.breakerTransitions) {
|
|
416
|
+
const prior = open.get(t.providerId);
|
|
417
|
+
if (prior !== undefined)
|
|
418
|
+
openMs = Math.max(openMs, t.atEpochMs - prior);
|
|
419
|
+
if (t.to === 'open')
|
|
420
|
+
open.set(t.providerId, t.atEpochMs);
|
|
421
|
+
else
|
|
422
|
+
open.delete(t.providerId);
|
|
423
|
+
}
|
|
424
|
+
for (const at of open.values())
|
|
425
|
+
openMs = Math.max(openMs, endingAt - at);
|
|
426
|
+
let stormMs = 0;
|
|
427
|
+
const storms = new Map();
|
|
428
|
+
for (const receipt of [...record.receipts].sort((a, b) => a.settledAtMs - b.settledAtMs)) {
|
|
429
|
+
if (receipt.httpStatus === 429 && !storms.has(receipt.providerId))
|
|
430
|
+
storms.set(receipt.providerId, receipt.settledAtMs);
|
|
431
|
+
const prior = storms.get(receipt.providerId);
|
|
432
|
+
if (prior !== undefined)
|
|
433
|
+
stormMs = Math.max(stormMs, receipt.settledAtMs - prior);
|
|
434
|
+
if (receipt.status === 'success')
|
|
435
|
+
storms.delete(receipt.providerId);
|
|
436
|
+
}
|
|
437
|
+
for (const at of storms.values())
|
|
438
|
+
stormMs = Math.max(stormMs, endingAt - at);
|
|
439
|
+
atMost('stop.maximumBreakerOpenMs', m.stopConditions.maximumBreakerOpenMs, openMs);
|
|
440
|
+
atMost('stop.maximum429StormMs', m.stopConditions.maximum429StormMs, stormMs);
|
|
441
|
+
const ids = new Set(record.receipts.map(r => `${r.phase}:${r.arrivalOrdinal}`));
|
|
442
|
+
atMost('stop.maximumRetryAmplificationRatio', m.stopConditions.maximumRetryAmplificationRatio, ids.size ? record.receipts.length / ids.size : 0);
|
|
443
|
+
const violations = out.filter(c => /principal\..*\.concurrency|shareConcurrency/.test(c.bound) && !c.pass).length;
|
|
444
|
+
atMost('stop.maximumCrossPrincipalQuotaViolations', 0, violations);
|
|
445
|
+
return out;
|
|
446
|
+
}
|
|
447
|
+
function reportedUsage(receipts, pricing) {
|
|
448
|
+
const sum = (values) => values.reduce((total, value) => total === null || value === null ? null : total + value, 0);
|
|
449
|
+
const inputs = receipts.map(r => r.usage.inputTokens);
|
|
450
|
+
const outputs = receipts.map(r => r.usage.outputTokens);
|
|
451
|
+
const totals = receipts.map(r => r.usage.inputTokens === null || r.usage.outputTokens === null ? null : r.usage.inputTokens + r.usage.outputTokens);
|
|
452
|
+
const inputTokens = sum(inputs);
|
|
453
|
+
const outputTokens = sum(outputs);
|
|
454
|
+
// The request is pinned to the priced model; an explicitly different served model has no applicable rate.
|
|
455
|
+
const costs = receipts.map(r => r.usage.inputTokens === null || r.usage.outputTokens === null
|
|
456
|
+
|| (r.servedModel !== undefined && r.servedModel !== pricing.jev_model) ? null
|
|
457
|
+
: tokenCost(r.usage, { inputPerMillionUsd: pricing.jev_input_per_mtok, outputPerMillionUsd: pricing.jev_output_per_mtok }));
|
|
458
|
+
const knownSum = (values) => values.length > 0 && values.every(v => v === null)
|
|
459
|
+
? null : sum(values.filter(v => v !== null));
|
|
460
|
+
return { calls: receipts.length, inputTokens, outputTokens, tokens: inputTokens === null || outputTokens === null ? null : inputTokens + outputTokens,
|
|
461
|
+
usd: sum(costs), incompleteCalls: costs.filter(cost => cost === null).length,
|
|
462
|
+
knownSubtotal: { inputTokens: knownSum(inputs), outputTokens: knownSum(outputs), tokens: knownSum(totals), usd: knownSum(costs) } };
|
|
463
|
+
}
|
|
464
|
+
export function decisionStagedLoadResultRecord(input) {
|
|
465
|
+
const manifest = readDecisionLoadManifest(input.manifestBytes);
|
|
466
|
+
validateDecisionLoadManifestV3(manifest);
|
|
467
|
+
validateDecisionLoadPricing(input.pricing, manifest.model);
|
|
468
|
+
const payload = { ...input, schemaVersion: 'decision-load-result/v2', kind: 'load-manifest-qualified',
|
|
469
|
+
reportedUsage: reportedUsage(input.receipts, input.pricing),
|
|
470
|
+
manifest, manifestDigest: decisionLoadManifestDigest(input.manifestBytes) };
|
|
471
|
+
for (const key of ['digest', 'comparisons', 'passed', 'queueWaitDistribution', 'retries'])
|
|
472
|
+
delete payload[key];
|
|
473
|
+
const comparisons = compareDecisionStagedLoad(payload);
|
|
474
|
+
const complete = ['shadow', 'canary', 'rollback'].every(phase => input.stages.some(s => s.phase === phase))
|
|
475
|
+
&& input.stages.every(s => s.observations.virtualDurationMs >= s.windowSeconds * 1000 && s.observations.arrivals > 0)
|
|
476
|
+
&& input.samples.length > 1 && input.profileHistory.some(h => h.kind === 'rollback');
|
|
477
|
+
const waits = input.receipts.map(r => r.admission.queueDelayMs).sort((a, b) => a - b);
|
|
478
|
+
const percentile = (fraction) => waits[Math.max(0, Math.ceil(waits.length * fraction) - 1)] ?? 0;
|
|
479
|
+
const body = { ...payload, comparisons, retries: input.receipts.filter(receipt => receipt.attempt > 1).length, queueWaitDistribution: { count: waits.length, p50: percentile(.5), p95: percentile(.95), p99: percentile(.99), maximum: maximum(waits) },
|
|
480
|
+
passed: !input.stop && complete && comparisons.length > 0 && comparisons.every(c => c.pass) };
|
|
481
|
+
return { ...body, digest: decisionLoadManifestDigest(canonicalJson(body)) };
|
|
482
|
+
}
|
|
483
|
+
/** Self digest + exact manifest bytes + recomputed measurements + complete rollout + clean exact commit. */
|
|
484
|
+
export function verifyDecisionStagedLoadRecord(value, sourceCommit) {
|
|
485
|
+
try {
|
|
486
|
+
const r = value;
|
|
487
|
+
if (!r || !DECISION_STAGED_LOAD_RESULT_VERSIONS.includes(r.schemaVersion) || r.kind !== 'load-manifest-qualified' || r.mode !== 'staged-provider'
|
|
488
|
+
|| r.execution !== 'live' || r.dirty !== false || !exactCommit(r.sourceCommit) || (sourceCommit !== undefined && sourceCommit !== r.sourceCommit)
|
|
489
|
+
|| r.stop !== null || !r.passed || !finite(r.startedAtMs) || !r.host.networkPath || !r.host.cpuClass || !positive(r.host.memoryGiB))
|
|
490
|
+
return false;
|
|
491
|
+
const manifest = readDecisionLoadManifest(r.manifestBytes);
|
|
492
|
+
validateDecisionLoadManifestV3(manifest);
|
|
493
|
+
validateDecisionLoadPricing(r.pricing, manifest.model);
|
|
494
|
+
if (canonicalJson(manifest) !== canonicalJson(r.manifest) || r.manifestDigest !== decisionLoadManifestDigest(r.manifestBytes)
|
|
495
|
+
|| manifest.duration.soakSeconds !== 900 || r.stages.length !== 3 || !r.receipts.length || r.samples.length < 2)
|
|
496
|
+
return false;
|
|
497
|
+
const { digest, ...body } = r;
|
|
498
|
+
if (digest !== decisionLoadManifestDigest(canonicalJson(body)))
|
|
499
|
+
return false;
|
|
500
|
+
const expectedPhases = ['shadow', 'canary', 'rollback'];
|
|
501
|
+
for (const [index, stage] of r.stages.entries()) {
|
|
502
|
+
const window = index === 0 ? manifest.rollout.shadowSeconds : index === 2 ? manifest.rollout.rollbackSeconds
|
|
503
|
+
: manifest.duration.loadSeconds + manifest.duration.spikeSeconds + manifest.duration.soakSeconds;
|
|
504
|
+
if (stage.phase !== expectedPhases[index] || stage.windowSeconds !== window
|
|
505
|
+
|| stage.profileVersion !== (index === 2 ? manifest.rollout.previousProfileVersion : manifest.profileVersion)
|
|
506
|
+
|| stage.observations.virtualDurationMs < window * 1000 || stage.observations.arrivals <= 0)
|
|
507
|
+
return false;
|
|
508
|
+
const receipts = r.receipts.filter(receipt => receipt.phase === stage.phase);
|
|
509
|
+
const expectedArrivals = decisionLoadArrivals(manifest, window);
|
|
510
|
+
const obs = stage.observations;
|
|
511
|
+
if (obs.arrivals !== expectedArrivals.length || obs.attempts !== receipts.length
|
|
512
|
+
|| obs.completed + obs.failed + obs.shed + obs.cancelled !== obs.arrivals
|
|
513
|
+
|| obs.maximumEligibleLaneWaitMs !== maximum(receipts.map(receipt => receipt.admission.queueDelayMs)))
|
|
514
|
+
return false;
|
|
515
|
+
for (const cls of ['quiet', 'noisy']) {
|
|
516
|
+
const expected = expectedArrivals.filter(arrival => arrival.principalClass === cls).length;
|
|
517
|
+
const admitted = new Set(receipts.filter(receipt => manifest.principals.find(p => p.id === receipt.principalId)?.class === cls).map(receipt => receipt.arrivalOrdinal)).size;
|
|
518
|
+
const ratio = expected ? Number((admitted / expected).toFixed(6)) : 1;
|
|
519
|
+
if (obs[cls === 'quiet' ? 'quietPrincipalAdmissionRatio' : 'noisyPrincipalAdmissionRatio'] !== ratio)
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
if (receipts.some(receipt => {
|
|
523
|
+
const expected = expectedArrivals[receipt.arrivalOrdinal];
|
|
524
|
+
return !expected || expected.principalId !== receipt.principalId || expected.providerId !== receipt.providerId || expected.tokens !== receipt.tokens;
|
|
525
|
+
}))
|
|
526
|
+
return false;
|
|
527
|
+
const scopedKeys = ['workspace', ...Object.keys(manifest.providers).map(id => `provider.${id}`), ...manifest.principals.map(p => `principal.${p.id}`)];
|
|
528
|
+
if (!stage.scopes || !scopedKeys.every(key => stage.scopes[key] && finite(stage.scopes[key].active) && finite(stage.scopes[key].queued)))
|
|
529
|
+
return false;
|
|
530
|
+
if (!receipts.some(receipt => manifest.principals.find(p => p.id === receipt.principalId)?.class === 'quiet')
|
|
531
|
+
|| !receipts.some(receipt => manifest.principals.find(p => p.id === receipt.principalId)?.class === 'noisy'))
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
if (r.receipts.some(receipt => !manifest.principals.some(p => p.id === receipt.principalId) || !(receipt.providerId in manifest.providers)
|
|
535
|
+
|| !positive(receipt.tokens) || !finite(receipt.costUsd) || !finite(receipt.admittedAtMs) || receipt.settledAtMs < receipt.admittedAtMs
|
|
536
|
+
|| receipt.admission.decision !== 'admit' || receipt.costUsd !== price(receipt.tokens, r.pricing)))
|
|
537
|
+
return false;
|
|
538
|
+
const reservedUsage = r.schemaVersion === 'decision-load-result/v1' ? r.usage : r.reservedUsage;
|
|
539
|
+
if (!reservedUsage || ![reservedUsage.calls, reservedUsage.tokens, reservedUsage.usd].every(finite)
|
|
540
|
+
|| reservedUsage.calls !== r.receipts.length || reservedUsage.tokens !== r.receipts.reduce((sum, receipt) => sum + receipt.tokens, 0)
|
|
541
|
+
|| Math.abs(reservedUsage.usd - r.receipts.reduce((sum, receipt) => sum + receipt.costUsd, 0)) > 1e-9)
|
|
542
|
+
return false;
|
|
543
|
+
if (r.schemaVersion === 'decision-load-result/v2' && r.receipts.some(receipt => !receipt.usage
|
|
544
|
+
|| ![receipt.usage.inputTokens, receipt.usage.outputTokens].every(v => v === null || (Number.isSafeInteger(v) && v >= 0))
|
|
545
|
+
|| (receipt.usage.inputTokens ?? 0) + (receipt.usage.outputTokens ?? 0) > receipt.tokens
|
|
546
|
+
|| (receipt.usage.costUsd !== null && (!finite(receipt.usage.costUsd) || receipt.usage.costUsd > receipt.costUsd + 1e-12))
|
|
547
|
+
|| (receipt.servedModel !== undefined && (typeof receipt.servedModel !== 'string' || !receipt.servedModel))
|
|
548
|
+
|| (receipt.requestId !== undefined && (typeof receipt.requestId !== 'string' || !receipt.requestId))))
|
|
549
|
+
return false;
|
|
550
|
+
if (!r.samples.every((sample, index) => finite(sample.atMs) && finite(sample.residentMemoryMiB) && finite(sample.heapUsedMiB)
|
|
551
|
+
&& finite(sample.cpuPercentOneCore) && finite(sample.active) && finite(sample.queued) && (index === 0 ? sample.atMs === 0 : sample.atMs >= r.samples[index - 1].atMs
|
|
552
|
+
&& sample.atMs - r.samples[index - 1].atMs <= manifest.bounds.maximumSampleGapMs)))
|
|
553
|
+
return false;
|
|
554
|
+
if (r.samples.at(-1).atMs !== r.elapsedMs
|
|
555
|
+
|| r.elapsedMs < r.stages.reduce((sum, stage) => sum + stage.windowSeconds * 1000, 0))
|
|
556
|
+
return false;
|
|
557
|
+
const [initial, change, rollback] = r.profileHistory;
|
|
558
|
+
const profileDigest = (workspace, providers) => decisionLoadManifestDigest(canonicalJson({ workspace, providers }));
|
|
559
|
+
if (r.profileHistory.length !== 3 || initial?.kind !== 'initial' || initial.profileVersion !== manifest.rollout.previousProfileVersion
|
|
560
|
+
|| initial.digest !== profileDigest(manifest.rollout.workspace, manifest.rollout.providers)
|
|
561
|
+
|| change?.kind !== 'change' || change.profileVersion !== manifest.profileVersion || change.digest !== profileDigest(manifest.workspace, manifest.providers)
|
|
562
|
+
|| rollback?.kind !== 'rollback' || rollback.profileVersion !== initial.profileVersion || rollback.digest !== initial.digest)
|
|
563
|
+
return false;
|
|
564
|
+
const rebuilt = decisionStagedLoadResultRecord({ ...r, reservedUsage });
|
|
565
|
+
if (r.schemaVersion === 'decision-load-result/v1') {
|
|
566
|
+
const { reservedUsage: _, reportedUsage: __, digest: ___, ...legacy } = rebuilt;
|
|
567
|
+
const legacyBody = { ...legacy, schemaVersion: r.schemaVersion };
|
|
568
|
+
return rebuilt.passed && canonicalJson({ ...legacyBody, digest: decisionLoadManifestDigest(canonicalJson(legacyBody)) }) === canonicalJson(r);
|
|
569
|
+
}
|
|
570
|
+
return rebuilt.passed && canonicalJson(rebuilt) === canonicalJson(r);
|
|
571
|
+
}
|
|
572
|
+
catch {
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
//# sourceMappingURL=load-staged.js.map
|