open-multi-agent-kit 0.96.0 → 0.96.2
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/CHANGELOG.md +81 -0
- package/README.md +34 -6
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/commands/doctor-provider.d.ts.map +1 -1
- package/dist/commands/doctor-provider.js +15 -20
- package/dist/commands/doctor-provider.js.map +1 -1
- package/dist/commands/resource-doctor-cli.d.ts +22 -0
- package/dist/commands/resource-doctor-cli.d.ts.map +1 -0
- package/dist/commands/resource-doctor-cli.js +98 -0
- package/dist/commands/resource-doctor-cli.js.map +1 -0
- package/dist/commands/sdk-session-cli.d.ts +24 -0
- package/dist/commands/sdk-session-cli.d.ts.map +1 -0
- package/dist/commands/sdk-session-cli.js +225 -0
- package/dist/commands/sdk-session-cli.js.map +1 -0
- package/dist/core/advisory-judge-model.d.ts +18 -0
- package/dist/core/advisory-judge-model.d.ts.map +1 -0
- package/dist/core/advisory-judge-model.js +168 -0
- package/dist/core/advisory-judge-model.js.map +1 -0
- package/dist/core/advisory-judge-response.d.ts +8 -0
- package/dist/core/advisory-judge-response.d.ts.map +1 -0
- package/dist/core/advisory-judge-response.js +61 -0
- package/dist/core/advisory-judge-response.js.map +1 -0
- package/dist/core/advisory-judge-types.d.ts +53 -0
- package/dist/core/advisory-judge-types.d.ts.map +1 -0
- package/dist/core/advisory-judge-types.js +2 -0
- package/dist/core/advisory-judge-types.js.map +1 -0
- package/dist/core/advisory-judge.d.ts +8 -0
- package/dist/core/advisory-judge.d.ts.map +1 -0
- package/dist/core/advisory-judge.js +178 -0
- package/dist/core/advisory-judge.js.map +1 -0
- package/dist/core/agent-session-services.d.ts +1 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +41 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +396 -179
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +28 -4
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/compaction-gate.d.ts +28 -0
- package/dist/core/compaction-gate.d.ts.map +1 -0
- package/dist/core/compaction-gate.js +34 -0
- package/dist/core/compaction-gate.js.map +1 -0
- package/dist/core/completion-sound.d.ts +109 -0
- package/dist/core/completion-sound.d.ts.map +1 -0
- package/dist/core/completion-sound.js +165 -0
- package/dist/core/completion-sound.js.map +1 -0
- package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt-items.js +5 -3
- package/dist/core/context-budget-system-prompt-items.js.map +1 -1
- package/dist/core/domain-loadouts.d.ts.map +1 -1
- package/dist/core/domain-loadouts.js +2 -2
- package/dist/core/domain-loadouts.js.map +1 -1
- package/dist/core/durable-goal-checkpoint.d.ts +6 -0
- package/dist/core/durable-goal-checkpoint.d.ts.map +1 -0
- package/dist/core/durable-goal-checkpoint.js +195 -0
- package/dist/core/durable-goal-checkpoint.js.map +1 -0
- package/dist/core/durable-goal-parse.d.ts +3 -0
- package/dist/core/durable-goal-parse.d.ts.map +1 -0
- package/dist/core/durable-goal-parse.js +86 -0
- package/dist/core/durable-goal-parse.js.map +1 -0
- package/dist/core/durable-goal-reducer.d.ts +3 -0
- package/dist/core/durable-goal-reducer.d.ts.map +1 -0
- package/dist/core/durable-goal-reducer.js +175 -0
- package/dist/core/durable-goal-reducer.js.map +1 -0
- package/dist/core/durable-goal-store.d.ts +19 -0
- package/dist/core/durable-goal-store.d.ts.map +1 -0
- package/dist/core/durable-goal-store.js +126 -0
- package/dist/core/durable-goal-store.js.map +1 -0
- package/dist/core/durable-goal.d.ts +92 -0
- package/dist/core/durable-goal.d.ts.map +1 -0
- package/dist/core/durable-goal.js +62 -0
- package/dist/core/durable-goal.js.map +1 -0
- package/dist/core/extensions/builtin/goal-controller.d.ts +3 -0
- package/dist/core/extensions/builtin/goal-controller.d.ts.map +1 -0
- package/dist/core/extensions/builtin/goal-controller.js +125 -0
- package/dist/core/extensions/builtin/goal-controller.js.map +1 -0
- package/dist/core/extensions/builtin/identical-loop.d.ts +3 -0
- package/dist/core/extensions/builtin/identical-loop.d.ts.map +1 -0
- package/dist/core/extensions/builtin/identical-loop.js +31 -0
- package/dist/core/extensions/builtin/identical-loop.js.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset.d.ts +3 -0
- package/dist/core/extensions/builtin/prompt-preset.d.ts.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset.js +13 -0
- package/dist/core/extensions/builtin/prompt-preset.js.map +1 -0
- package/dist/core/extensions/builtin/tool-pair-repair.d.ts +3 -0
- package/dist/core/extensions/builtin/tool-pair-repair.d.ts.map +1 -0
- package/dist/core/extensions/builtin/tool-pair-repair.js +12 -0
- package/dist/core/extensions/builtin/tool-pair-repair.js.map +1 -0
- package/dist/core/extensions/types.d.ts +6 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/goal-continuation.d.ts +13 -0
- package/dist/core/goal-continuation.d.ts.map +1 -0
- package/dist/core/goal-continuation.js +10 -0
- package/dist/core/goal-continuation.js.map +1 -0
- package/dist/core/grok-harness.d.ts +1 -1
- package/dist/core/grok-harness.d.ts.map +1 -1
- package/dist/core/grok-harness.js +1 -1
- package/dist/core/grok-harness.js.map +1 -1
- package/dist/core/grok-playbook.d.ts +1 -1
- package/dist/core/grok-playbook.d.ts.map +1 -1
- package/dist/core/grok-playbook.js +1 -1
- package/dist/core/grok-playbook.js.map +1 -1
- package/dist/core/host-resource-probe.d.ts +63 -0
- package/dist/core/host-resource-probe.d.ts.map +1 -0
- package/dist/core/host-resource-probe.js +126 -0
- package/dist/core/host-resource-probe.js.map +1 -0
- package/dist/core/host-resource-snapshot.d.ts +54 -0
- package/dist/core/host-resource-snapshot.d.ts.map +1 -0
- package/dist/core/host-resource-snapshot.js +139 -0
- package/dist/core/host-resource-snapshot.js.map +1 -0
- package/dist/core/identical-loop.d.ts +15 -0
- package/dist/core/identical-loop.d.ts.map +1 -0
- package/dist/core/identical-loop.js +34 -0
- package/dist/core/identical-loop.js.map +1 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/model-prompt-policy.d.ts +11 -0
- package/dist/core/model-prompt-policy.d.ts.map +1 -0
- package/dist/core/model-prompt-policy.js +17 -0
- package/dist/core/model-prompt-policy.js.map +1 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +16 -4
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +2 -2
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/prompt-budget.d.ts +21 -0
- package/dist/core/prompt-budget.d.ts.map +1 -0
- package/dist/core/prompt-budget.js +51 -0
- package/dist/core/prompt-budget.js.map +1 -0
- package/dist/core/prompt-cache.d.ts +14 -0
- package/dist/core/prompt-cache.d.ts.map +1 -0
- package/dist/core/prompt-cache.js +16 -0
- package/dist/core/prompt-cache.js.map +1 -0
- package/dist/core/prompt-preset.d.ts +8 -0
- package/dist/core/prompt-preset.d.ts.map +1 -0
- package/dist/core/prompt-preset.js +56 -0
- package/dist/core/prompt-preset.js.map +1 -0
- package/dist/core/prompt-settlement.d.ts +81 -0
- package/dist/core/prompt-settlement.d.ts.map +1 -0
- package/dist/core/prompt-settlement.js +84 -0
- package/dist/core/prompt-settlement.js.map +1 -0
- package/dist/core/provider-resilience.d.ts +16 -0
- package/dist/core/provider-resilience.d.ts.map +1 -1
- package/dist/core/provider-resilience.js +24 -2
- package/dist/core/provider-resilience.js.map +1 -1
- package/dist/core/provider-retry.d.ts +36 -0
- package/dist/core/provider-retry.d.ts.map +1 -0
- package/dist/core/provider-retry.js +73 -0
- package/dist/core/provider-retry.js.map +1 -0
- package/dist/core/provider-usage.d.ts +16 -2
- package/dist/core/provider-usage.d.ts.map +1 -1
- package/dist/core/provider-usage.js +150 -8
- package/dist/core/provider-usage.js.map +1 -1
- package/dist/core/reasoning-router-resolver.d.ts.map +1 -1
- package/dist/core/reasoning-router-resolver.js +1 -1
- package/dist/core/reasoning-router-resolver.js.map +1 -1
- package/dist/core/resource-admission-config.d.ts +55 -0
- package/dist/core/resource-admission-config.d.ts.map +1 -0
- package/dist/core/resource-admission-config.js +97 -0
- package/dist/core/resource-admission-config.js.map +1 -0
- package/dist/core/resource-admission.d.ts +81 -0
- package/dist/core/resource-admission.d.ts.map +1 -0
- package/dist/core/resource-admission.js +128 -0
- package/dist/core/resource-admission.js.map +1 -0
- package/dist/core/resource-governor-format.d.ts +50 -0
- package/dist/core/resource-governor-format.d.ts.map +1 -0
- package/dist/core/resource-governor-format.js +113 -0
- package/dist/core/resource-governor-format.js.map +1 -0
- package/dist/core/resource-governor-settings.d.ts +56 -0
- package/dist/core/resource-governor-settings.d.ts.map +1 -0
- package/dist/core/resource-governor-settings.js +89 -0
- package/dist/core/resource-governor-settings.js.map +1 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +32 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/resource-observation-journal.d.ts +74 -0
- package/dist/core/resource-observation-journal.d.ts.map +1 -0
- package/dist/core/resource-observation-journal.js +198 -0
- package/dist/core/resource-observation-journal.js.map +1 -0
- package/dist/core/resource-safety-gate.d.ts +54 -0
- package/dist/core/resource-safety-gate.d.ts.map +1 -0
- package/dist/core/resource-safety-gate.js +51 -0
- package/dist/core/resource-safety-gate.js.map +1 -0
- package/dist/core/run-journal.d.ts.map +1 -1
- package/dist/core/run-journal.js +13 -1
- package/dist/core/run-journal.js.map +1 -1
- package/dist/core/run-resource-lease.d.ts +73 -0
- package/dist/core/run-resource-lease.d.ts.map +1 -0
- package/dist/core/run-resource-lease.js +78 -0
- package/dist/core/run-resource-lease.js.map +1 -0
- package/dist/core/sandbox/backend.d.ts +12 -0
- package/dist/core/sandbox/backend.d.ts.map +1 -1
- package/dist/core/sandbox/backend.js +52 -42
- package/dist/core/sandbox/backend.js.map +1 -1
- package/dist/core/sandbox/policy.d.ts +2 -0
- package/dist/core/sandbox/policy.d.ts.map +1 -1
- package/dist/core/sandbox/policy.js +2 -1
- package/dist/core/sandbox/policy.js.map +1 -1
- package/dist/core/sdk.d.ts +2 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-bash-service.d.ts +12 -0
- package/dist/core/session-bash-service.d.ts.map +1 -1
- package/dist/core/session-bash-service.js +14 -0
- package/dist/core/session-bash-service.js.map +1 -1
- package/dist/core/session-system-prompt.d.ts +30 -0
- package/dist/core/session-system-prompt.d.ts.map +1 -0
- package/dist/core/session-system-prompt.js +39 -0
- package/dist/core/session-system-prompt.js.map +1 -0
- package/dist/core/session-termination.d.ts +7 -2
- package/dist/core/session-termination.d.ts.map +1 -1
- package/dist/core/session-termination.js +29 -1
- package/dist/core/session-termination.js.map +1 -1
- package/dist/core/settings-manager.d.ts +12 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +36 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/strict-record.d.ts +2 -0
- package/dist/core/strict-record.d.ts.map +1 -0
- package/dist/core/strict-record.js +9 -0
- package/dist/core/strict-record.js.map +1 -0
- package/dist/core/subagent-lane-launcher.d.ts +64 -0
- package/dist/core/subagent-lane-launcher.d.ts.map +1 -0
- package/dist/core/subagent-lane-launcher.js +107 -0
- package/dist/core/subagent-lane-launcher.js.map +1 -0
- package/dist/core/system-cpu-sampler.d.ts +45 -0
- package/dist/core/system-cpu-sampler.d.ts.map +1 -0
- package/dist/core/system-cpu-sampler.js +95 -0
- package/dist/core/system-cpu-sampler.js.map +1 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +5 -8
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tool-pair-repair.d.ts +11 -0
- package/dist/core/tool-pair-repair.d.ts.map +1 -0
- package/dist/core/tool-pair-repair.js +38 -0
- package/dist/core/tool-pair-repair.js.map +1 -0
- package/dist/core/tools/artifact-spill.d.ts +13 -0
- package/dist/core/tools/artifact-spill.d.ts.map +1 -0
- package/dist/core/tools/artifact-spill.js +26 -0
- package/dist/core/tools/artifact-spill.js.map +1 -0
- package/dist/core/tools/bash.d.ts +7 -4
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +8 -7
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/read.d.ts +1 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +10 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +2 -0
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/core/workload-classifier.d.ts +32 -0
- package/dist/core/workload-classifier.d.ts.map +1 -0
- package/dist/core/workload-classifier.js +66 -0
- package/dist/core/workload-classifier.js.map +1 -0
- package/dist/core/workload-family-matcher.d.ts +3 -0
- package/dist/core/workload-family-matcher.d.ts.map +1 -0
- package/dist/core/workload-family-matcher.js +126 -0
- package/dist/core/workload-family-matcher.js.map +1 -0
- package/dist/core/workload-permit-pool.d.ts +78 -0
- package/dist/core/workload-permit-pool.d.ts.map +1 -0
- package/dist/core/workload-permit-pool.js +138 -0
- package/dist/core/workload-permit-pool.js.map +1 -0
- package/dist/core/workload-shard-executor.d.ts +68 -0
- package/dist/core/workload-shard-executor.d.ts.map +1 -0
- package/dist/core/workload-shard-executor.js +156 -0
- package/dist/core/workload-shard-executor.js.map +1 -0
- package/dist/core/workload-shard-plan.d.ts +81 -0
- package/dist/core/workload-shard-plan.d.ts.map +1 -0
- package/dist/core/workload-shard-plan.js +183 -0
- package/dist/core/workload-shard-plan.js.map +1 -0
- package/dist/core/workload-shard-runner.d.ts +11 -0
- package/dist/core/workload-shard-runner.d.ts.map +1 -0
- package/dist/core/workload-shard-runner.js +98 -0
- package/dist/core/workload-shard-runner.js.map +1 -0
- package/dist/core/workload-shard-store.d.ts +44 -0
- package/dist/core/workload-shard-store.d.ts.map +1 -0
- package/dist/core/workload-shard-store.js +189 -0
- package/dist/core/workload-shard-store.js.map +1 -0
- package/dist/core/workload-sharders.d.ts +47 -0
- package/dist/core/workload-sharders.d.ts.map +1 -0
- package/dist/core/workload-sharders.js +162 -0
- package/dist/core/workload-sharders.js.map +1 -0
- package/dist/core/workload-shell-scan.d.ts +12 -0
- package/dist/core/workload-shell-scan.d.ts.map +1 -0
- package/dist/core/workload-shell-scan.js +79 -0
- package/dist/core/workload-shell-scan.js.map +1 -0
- package/dist/index.d.ts +18 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +14 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +81 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +14 -3
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/docs/adaptorch-preview-spec.md +1 -1
- package/docs/adaptorch-preview.md +2 -2
- package/docs/book-to-skill.md +1 -1
- package/docs/containerization.md +9 -0
- package/docs/docs.json +11 -3
- package/docs/environment-variables.md +11 -4
- package/docs/extensions.md +14 -1
- package/docs/grok-harness.md +35 -12
- package/docs/index.md +7 -6
- package/docs/loadout-domains/grok-harness.md +2 -2
- package/docs/models.md +10 -9
- package/docs/provider-resilience.md +9 -3
- package/docs/providers.md +9 -4
- package/docs/run-protocol.md +32 -0
- package/docs/sdk.md +100 -3
- package/docs/security.md +22 -32
- package/docs/sessions.md +14 -0
- package/docs/settings.md +64 -2
- package/docs/skills.md +16 -0
- package/docs/usage.md +37 -15
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +6 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/agent-capability-router.test.ts +7 -0
- package/examples/extensions/subagent/domain-profiles.ts +0 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +18 -18
- package/package.json +7 -9
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { planShardResume, reduceShardRecords, validateWorkloadShardPlan, } from "./workload-shard-plan.js";
|
|
2
|
+
import { applyTransition, runOneShard, shardById } from "./workload-shard-runner.js";
|
|
3
|
+
import { WorkloadShardJournalError } from "./workload-shard-store.js";
|
|
4
|
+
export async function executeWorkloadShardPlan(input) {
|
|
5
|
+
const now = input.now ?? (() => new Date());
|
|
6
|
+
const planErrors = validateWorkloadShardPlan(input.plan);
|
|
7
|
+
if (planErrors.length > 0) {
|
|
8
|
+
return blockedEvidence(input, now, ["plan.invalid", ...planErrors.slice(0, 3)]);
|
|
9
|
+
}
|
|
10
|
+
let journal;
|
|
11
|
+
try {
|
|
12
|
+
journal = input.store.load();
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
// §21 fail closed: corrupt evidence is quarantined, never silently rebuilt.
|
|
16
|
+
if (error instanceof WorkloadShardJournalError) {
|
|
17
|
+
input.store.quarantine();
|
|
18
|
+
return blockedEvidence(input, now, [`journal.${error.code}`]);
|
|
19
|
+
}
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
if (journal.plan === null) {
|
|
23
|
+
input.store.appendPlan(input.plan);
|
|
24
|
+
}
|
|
25
|
+
else if (journal.plan.planId !== input.plan.planId || journal.plan.commandDigest !== input.plan.commandDigest) {
|
|
26
|
+
return blockedEvidence(input, now, ["plan.mismatch"]);
|
|
27
|
+
}
|
|
28
|
+
const projections = new Map(journal.projections ?? reduceShardRecords(input.plan, []));
|
|
29
|
+
const effectiveConcurrency = Math.max(1, Math.min(input.plan.maxConcurrency, input.decision.maxHeavyProcesses));
|
|
30
|
+
// §13.5 steps 2-4: re-arm recoverable shards through explicit pending records.
|
|
31
|
+
const resume = planShardResume(input.plan, projections);
|
|
32
|
+
const rearm = [
|
|
33
|
+
...resume.interrupted,
|
|
34
|
+
...(input.retryFailed ? resume.retryable : []),
|
|
35
|
+
...((input.resumeAborted ?? true) ? resume.resumable : []),
|
|
36
|
+
];
|
|
37
|
+
for (const shardId of rearm) {
|
|
38
|
+
// §13.5 step 2: a crash-orphaned `running` shard is first projected as
|
|
39
|
+
// interrupted (running -> pending is illegal in §13.4), then re-armed.
|
|
40
|
+
if (projections.get(shardId)?.state === "running") {
|
|
41
|
+
applyTransition(input, projections, shardId, "interrupted", { reasonCode: "recovery.orphaned", now });
|
|
42
|
+
}
|
|
43
|
+
applyTransition(input, projections, shardId, "pending", { reasonCode: "recovery.rearm", now });
|
|
44
|
+
}
|
|
45
|
+
// Dependency-wave scheduler: launch ready shards up to the recomputed width.
|
|
46
|
+
const remaining = new Set(input.plan.shards.map((shard) => shard.shardId).filter((id) => projections.get(id)?.state !== "passed"));
|
|
47
|
+
let active = 0;
|
|
48
|
+
let wake;
|
|
49
|
+
const waitTurn = () => new Promise((resolve) => {
|
|
50
|
+
wake = resolve;
|
|
51
|
+
});
|
|
52
|
+
const running = new Map();
|
|
53
|
+
while (remaining.size > 0 && !input.signal?.aborted) {
|
|
54
|
+
const ready = [...remaining].filter((id) => {
|
|
55
|
+
const projection = projections.get(id);
|
|
56
|
+
if (projection?.state !== "pending")
|
|
57
|
+
return false;
|
|
58
|
+
const spec = shardById(input.plan, id);
|
|
59
|
+
return spec.dependencyIds.every((dep) => projections.get(dep)?.state === "passed");
|
|
60
|
+
});
|
|
61
|
+
const launchable = ready.slice(0, Math.max(0, effectiveConcurrency - active));
|
|
62
|
+
if (launchable.length === 0) {
|
|
63
|
+
if (running.size === 0)
|
|
64
|
+
break; // deadlock = blocked dependents; aggregate reports them
|
|
65
|
+
await Promise.race([...running.values(), waitTurn()]);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
for (const shardId of launchable) {
|
|
69
|
+
remaining.delete(shardId);
|
|
70
|
+
active += 1;
|
|
71
|
+
const task = runOneShard(input, projections, shardId, now).finally(() => {
|
|
72
|
+
active -= 1;
|
|
73
|
+
running.delete(shardId);
|
|
74
|
+
wake?.();
|
|
75
|
+
});
|
|
76
|
+
running.set(shardId, task);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
await Promise.all(running.values());
|
|
80
|
+
return aggregate(input, projections, effectiveConcurrency, now, input.signal?.aborted ? ["run.aborted"] : []);
|
|
81
|
+
}
|
|
82
|
+
function aggregate(input, projections, effectiveConcurrency, now, extraReasons) {
|
|
83
|
+
const counts = {
|
|
84
|
+
pending: 0,
|
|
85
|
+
running: 0,
|
|
86
|
+
passed: 0,
|
|
87
|
+
failed: 0,
|
|
88
|
+
aborted: 0,
|
|
89
|
+
interrupted: 0,
|
|
90
|
+
};
|
|
91
|
+
const shards = input.plan.shards.map((shard) => {
|
|
92
|
+
const projection = projections.get(shard.shardId);
|
|
93
|
+
const state = projection?.state ?? "pending";
|
|
94
|
+
counts[state] += 1;
|
|
95
|
+
return {
|
|
96
|
+
shardId: shard.shardId,
|
|
97
|
+
state,
|
|
98
|
+
attempt: projection?.attempt ?? 0,
|
|
99
|
+
evidenceRefs: projection?.evidenceRefs ?? [],
|
|
100
|
+
...(projection?.reasonCode !== undefined ? { reasonCode: projection.reasonCode } : {}),
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
const verdict = aggregateVerdict(counts, input.plan.shards.length, extraReasons);
|
|
104
|
+
return {
|
|
105
|
+
kind: "workload_shard_result.v1",
|
|
106
|
+
planId: input.plan.planId,
|
|
107
|
+
promptRunId: input.plan.promptRunId,
|
|
108
|
+
commandDigest: input.plan.commandDigest,
|
|
109
|
+
strategy: input.plan.strategy,
|
|
110
|
+
verdict,
|
|
111
|
+
reasonCodes: extraReasons,
|
|
112
|
+
shardCount: input.plan.shards.length,
|
|
113
|
+
counts,
|
|
114
|
+
shards,
|
|
115
|
+
effectiveConcurrency,
|
|
116
|
+
completedAt: now().toISOString(),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function blockedEvidence(input, now, reasonCodes) {
|
|
120
|
+
const counts = {
|
|
121
|
+
pending: input.plan.shards.length,
|
|
122
|
+
running: 0,
|
|
123
|
+
passed: 0,
|
|
124
|
+
failed: 0,
|
|
125
|
+
aborted: 0,
|
|
126
|
+
interrupted: 0,
|
|
127
|
+
};
|
|
128
|
+
return {
|
|
129
|
+
kind: "workload_shard_result.v1",
|
|
130
|
+
planId: input.plan.planId,
|
|
131
|
+
promptRunId: input.plan.promptRunId,
|
|
132
|
+
commandDigest: input.plan.commandDigest,
|
|
133
|
+
strategy: input.plan.strategy,
|
|
134
|
+
verdict: "blocked",
|
|
135
|
+
reasonCodes,
|
|
136
|
+
shardCount: input.plan.shards.length,
|
|
137
|
+
counts,
|
|
138
|
+
shards: input.plan.shards.map((shard) => ({
|
|
139
|
+
shardId: shard.shardId,
|
|
140
|
+
state: "pending",
|
|
141
|
+
attempt: 0,
|
|
142
|
+
evidenceRefs: [],
|
|
143
|
+
})),
|
|
144
|
+
effectiveConcurrency: 0,
|
|
145
|
+
completedAt: now().toISOString(),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/** §13.6: "passed" requires EVERY shard to hold terminal passed evidence. */
|
|
149
|
+
function aggregateVerdict(counts, shardCount, extraReasons) {
|
|
150
|
+
if (counts.passed === shardCount)
|
|
151
|
+
return "passed";
|
|
152
|
+
if (counts.aborted > 0 || extraReasons.includes("run.aborted"))
|
|
153
|
+
return "aborted";
|
|
154
|
+
return "failed";
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=workload-shard-executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workload-shard-executor.js","sourceRoot":"","sources":["../../src/core/workload-shard-executor.ts"],"names":[],"mappings":"AAEA,OAAO,EACN,eAAe,EACf,kBAAkB,EAClB,yBAAyB,GAKzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACrF,OAAO,EAAE,yBAAyB,EAA2B,MAAM,2BAA2B,CAAC;AAqE/F,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC7C,KAAoC,EACM;IAC1C,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAC5C,MAAM,UAAU,GAAG,yBAAyB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,OAAO,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,cAAc,EAAE,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACjF,CAAC;IAED,IAAI,OAA+C,CAAC;IACpD,IAAI,CAAC;QACJ,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAC9B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,6EAA4E;QAC5E,IAAI,KAAK,YAAY,yBAAyB,EAAE,CAAC;YAChD,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;YACzB,OAAO,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,WAAW,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAC/D,CAAC;QACD,MAAM,KAAK,CAAC;IACb,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QAC3B,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;SAAM,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,aAAa,KAAK,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;QACjH,OAAO,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,GAAG,CAC1B,OAAO,CAAC,WAAW,IAAI,kBAAkB,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CACzD,CAAC;IACF,MAAM,oBAAoB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAEhH,gFAA+E;IAC/E,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG;QACb,GAAG,MAAM,CAAC,WAAW;QACrB,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9C,GAAG,CAAC,CAAC,KAAK,CAAC,aAAa,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1D,CAAC;IACF,KAAK,MAAM,OAAO,IAAI,KAAK,EAAE,CAAC;QAC7B,wEAAuE;QACvE,wEAAuE;QACvE,IAAI,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,KAAK,KAAK,SAAS,EAAE,CAAC;YACnD,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,UAAU,EAAE,mBAAmB,EAAE,GAAG,EAAE,CAAC,CAAC;QACvG,CAAC;QACD,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,CAAC;IAChG,CAAC;IAED,6EAA6E;IAC7E,MAAM,SAAS,GAAG,IAAI,GAAG,CACxB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,KAAK,KAAK,QAAQ,CAAC,CACvG,CAAC;IACF,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,IAA8B,CAAC;IACnC,MAAM,QAAQ,GAAG,GAAG,EAAE,CACrB,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QAC9B,IAAI,GAAG,OAAO,CAAC;IAAA,CACf,CAAC,CAAC;IACJ,MAAM,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAC;IAEjD,OAAO,SAAS,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;QACrD,MAAM,KAAK,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC;YAC3C,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACvC,IAAI,UAAU,EAAE,KAAK,KAAK,SAAS;gBAAE,OAAO,KAAK,CAAC;YAClD,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACvC,OAAO,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC;QAAA,CACnF,CAAC,CAAC;QACH,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAAC,CAAC,CAAC;QAC9E,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;gBAAE,MAAM,CAAC,wDAAwD;YACvF,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;YACtD,SAAS;QACV,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,UAAU,EAAE,CAAC;YAClC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC1B,MAAM,IAAI,CAAC,CAAC;YACZ,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;gBACxE,MAAM,IAAI,CAAC,CAAC;gBACZ,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBACxB,IAAI,EAAE,EAAE,CAAC;YAAA,CACT,CAAC,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC5B,CAAC;IACF,CAAC;IACD,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAEpC,OAAO,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,GAAG,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAAA,CAC9G;AAED,SAAS,SAAS,CACjB,KAAoC,EACpC,WAAyD,EACzD,oBAA4B,EAC5B,GAAe,EACf,YAA+B,EACE;IACjC,MAAM,MAAM,GAAuC;QAClD,OAAO,EAAE,CAAC;QACV,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,CAAC;QACT,MAAM,EAAE,CAAC;QACT,OAAO,EAAE,CAAC;QACV,WAAW,EAAE,CAAC;KACd,CAAC;IACF,MAAM,MAAM,GAAyB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;QACrE,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAClD,MAAM,KAAK,GAAG,UAAU,EAAE,KAAK,IAAI,SAAS,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO;YACN,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,KAAK;YACL,OAAO,EAAE,UAAU,EAAE,OAAO,IAAI,CAAC;YACjC,YAAY,EAAE,UAAU,EAAE,YAAY,IAAI,EAAE;YAC5C,GAAG,CAAC,UAAU,EAAE,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACtF,CAAC;IAAA,CACF,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,gBAAgB,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACjF,OAAO;QACN,IAAI,EAAE,0BAA0B;QAChC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM;QACzB,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,WAAW;QACnC,aAAa,EAAE,KAAK,CAAC,IAAI,CAAC,aAAa;QACvC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ;QAC7B,OAAO;QACP,WAAW,EAAE,YAAY;QACzB,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM;QACpC,MAAM;QACN,MAAM;QACN,oBAAoB;QACpB,WAAW,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE;KAChC,CAAC;AAAA,CACF;AAED,SAAS,eAAe,CACvB,KAAoC,EACpC,GAAe,EACf,WAA8B,EACG;IACjC,MAAM,MAAM,GAAuC;QAClD,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM;QACjC,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,CAAC;QACT,MAAM,EAAE,CAAC;QACT,OAAO,EAAE,CAAC;QACV,WAAW,EAAE,CAAC;KACd,CAAC;IACF,OAAO;QACN,IAAI,EAAE,0BAA0B;QAChC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM;QACzB,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,WAAW;QACnC,aAAa,EAAE,KAAK,CAAC,IAAI,CAAC,aAAa;QACvC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ;QAC7B,OAAO,EAAE,SAAS;QAClB,WAAW;QACX,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM;QACpC,MAAM;QACN,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACzC,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,KAAK,EAAE,SAAkB;YACzB,OAAO,EAAE,CAAC;YACV,YAAY,EAAE,EAAE;SAChB,CAAC,CAAC;QACH,oBAAoB,EAAE,CAAC;QACvB,WAAW,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE;KAChC,CAAC;AAAA,CACF;AAED,8EAA6E;AAC7E,SAAS,gBAAgB,CACxB,MAAoD,EACpD,UAAkB,EAClB,YAA+B,EACa;IAC5C,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU;QAAE,OAAO,QAAQ,CAAC;IAClD,IAAI,MAAM,CAAC,OAAO,GAAG,CAAC,IAAI,YAAY,CAAC,QAAQ,CAAC,aAAa,CAAC;QAAE,OAAO,SAAS,CAAC;IACjF,OAAO,QAAQ,CAAC;AAAA,CAChB","sourcesContent":["import type { ResourceAdmissionDecision } from \"./resource-admission.ts\";\nimport type { WorkloadPermitPool } from \"./workload-permit-pool.ts\";\nimport {\n\tplanShardResume,\n\treduceShardRecords,\n\tvalidateWorkloadShardPlan,\n\ttype WorkloadShardPlan,\n\ttype WorkloadShardProjection,\n\ttype WorkloadShardSpec,\n\ttype WorkloadShardState,\n} from \"./workload-shard-plan.ts\";\nimport { applyTransition, runOneShard, shardById } from \"./workload-shard-runner.ts\";\nimport { WorkloadShardJournalError, type WorkloadShardStore } from \"./workload-shard-store.ts\";\n\n/**\n * Workload shard executor (OMK v0.97.x roadmap §13, M5 completion slice).\n *\n * Drives a validated `WorkloadShardPlan` through the journalled §13.4 state\n * machine: passed shards are terminal and never re-run (§13.5 step 3),\n * crash-orphaned `running` shards are recovered through `pending` re-arm\n * records, concurrency is recomputed from the CURRENT admission decision\n * (§13.5 step 6) plus the shared permit pool, and completion produces an\n * aggregate evidence receipt (§13.6) — an observation input for\n * `evaluateTask()`, never a task verdict by itself.\n *\n * Fail-closed rules (§21): a corrupt journal is quarantined and the run is\n * `blocked`; a journal written for a different plan is `blocked`; an invalid\n * plan never starts. The executor never rewrites shard commands (§12.3) —\n * it only runs what the plan's descriptors already describe via the\n * injected runner.\n */\n\nexport interface ShardRunContext {\n\treadonly shard: WorkloadShardSpec;\n\treadonly attempt: number;\n\treadonly signal?: AbortSignal;\n}\n\nexport type ShardRunner = (\n\tcontext: ShardRunContext,\n) => Promise<{ readonly exitCode: number; readonly evidenceRefs?: readonly string[] }>;\n\nexport interface ExecuteWorkloadShardPlanInput {\n\treadonly plan: WorkloadShardPlan;\n\treadonly store: WorkloadShardStore;\n\treadonly runner: ShardRunner;\n\treadonly decision: ResourceAdmissionDecision;\n\treadonly permitPool?: WorkloadPermitPool;\n\treadonly signal?: AbortSignal;\n\t/** §13.5 step 4 retry policy: re-arm previously failed shards. Default false. */\n\treadonly retryFailed?: boolean;\n\t/** §13.4: aborted -> pending needs an explicit resume; this call is that act. Default true. */\n\treadonly resumeAborted?: boolean;\n\treadonly permitWaitTimeoutMs?: number;\n\treadonly now?: () => Date;\n}\n\nexport interface ShardResultSummary {\n\treadonly shardId: string;\n\treadonly state: WorkloadShardState;\n\treadonly attempt: number;\n\treadonly evidenceRefs: readonly string[];\n\treadonly reasonCode?: string;\n}\n\n/** §13.6 aggregate evidence receipt; §15.1 observation kind. */\nexport interface WorkloadShardAggregateEvidence {\n\treadonly kind: \"workload_shard_result.v1\";\n\treadonly planId: string;\n\treadonly promptRunId: string;\n\treadonly commandDigest: string;\n\treadonly strategy: string;\n\treadonly verdict: \"passed\" | \"failed\" | \"aborted\" | \"blocked\";\n\treadonly reasonCodes: readonly string[];\n\treadonly shardCount: number;\n\treadonly counts: Readonly<Record<WorkloadShardState, number>>;\n\treadonly shards: readonly ShardResultSummary[];\n\treadonly effectiveConcurrency: number;\n\treadonly completedAt: string;\n}\n\nexport async function executeWorkloadShardPlan(\n\tinput: ExecuteWorkloadShardPlanInput,\n): Promise<WorkloadShardAggregateEvidence> {\n\tconst now = input.now ?? (() => new Date());\n\tconst planErrors = validateWorkloadShardPlan(input.plan);\n\tif (planErrors.length > 0) {\n\t\treturn blockedEvidence(input, now, [\"plan.invalid\", ...planErrors.slice(0, 3)]);\n\t}\n\n\tlet journal: ReturnType<WorkloadShardStore[\"load\"]>;\n\ttry {\n\t\tjournal = input.store.load();\n\t} catch (error) {\n\t\t// §21 fail closed: corrupt evidence is quarantined, never silently rebuilt.\n\t\tif (error instanceof WorkloadShardJournalError) {\n\t\t\tinput.store.quarantine();\n\t\t\treturn blockedEvidence(input, now, [`journal.${error.code}`]);\n\t\t}\n\t\tthrow error;\n\t}\n\tif (journal.plan === null) {\n\t\tinput.store.appendPlan(input.plan);\n\t} else if (journal.plan.planId !== input.plan.planId || journal.plan.commandDigest !== input.plan.commandDigest) {\n\t\treturn blockedEvidence(input, now, [\"plan.mismatch\"]);\n\t}\n\n\tconst projections = new Map<string, WorkloadShardProjection>(\n\t\tjournal.projections ?? reduceShardRecords(input.plan, []),\n\t);\n\tconst effectiveConcurrency = Math.max(1, Math.min(input.plan.maxConcurrency, input.decision.maxHeavyProcesses));\n\n\t// §13.5 steps 2-4: re-arm recoverable shards through explicit pending records.\n\tconst resume = planShardResume(input.plan, projections);\n\tconst rearm = [\n\t\t...resume.interrupted,\n\t\t...(input.retryFailed ? resume.retryable : []),\n\t\t...((input.resumeAborted ?? true) ? resume.resumable : []),\n\t];\n\tfor (const shardId of rearm) {\n\t\t// §13.5 step 2: a crash-orphaned `running` shard is first projected as\n\t\t// interrupted (running -> pending is illegal in §13.4), then re-armed.\n\t\tif (projections.get(shardId)?.state === \"running\") {\n\t\t\tapplyTransition(input, projections, shardId, \"interrupted\", { reasonCode: \"recovery.orphaned\", now });\n\t\t}\n\t\tapplyTransition(input, projections, shardId, \"pending\", { reasonCode: \"recovery.rearm\", now });\n\t}\n\n\t// Dependency-wave scheduler: launch ready shards up to the recomputed width.\n\tconst remaining = new Set(\n\t\tinput.plan.shards.map((shard) => shard.shardId).filter((id) => projections.get(id)?.state !== \"passed\"),\n\t);\n\tlet active = 0;\n\tlet wake: (() => void) | undefined;\n\tconst waitTurn = () =>\n\t\tnew Promise<void>((resolve) => {\n\t\t\twake = resolve;\n\t\t});\n\tconst running = new Map<string, Promise<void>>();\n\n\twhile (remaining.size > 0 && !input.signal?.aborted) {\n\t\tconst ready = [...remaining].filter((id) => {\n\t\t\tconst projection = projections.get(id);\n\t\t\tif (projection?.state !== \"pending\") return false;\n\t\t\tconst spec = shardById(input.plan, id);\n\t\t\treturn spec.dependencyIds.every((dep) => projections.get(dep)?.state === \"passed\");\n\t\t});\n\t\tconst launchable = ready.slice(0, Math.max(0, effectiveConcurrency - active));\n\t\tif (launchable.length === 0) {\n\t\t\tif (running.size === 0) break; // deadlock = blocked dependents; aggregate reports them\n\t\t\tawait Promise.race([...running.values(), waitTurn()]);\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const shardId of launchable) {\n\t\t\tremaining.delete(shardId);\n\t\t\tactive += 1;\n\t\t\tconst task = runOneShard(input, projections, shardId, now).finally(() => {\n\t\t\t\tactive -= 1;\n\t\t\t\trunning.delete(shardId);\n\t\t\t\twake?.();\n\t\t\t});\n\t\t\trunning.set(shardId, task);\n\t\t}\n\t}\n\tawait Promise.all(running.values());\n\n\treturn aggregate(input, projections, effectiveConcurrency, now, input.signal?.aborted ? [\"run.aborted\"] : []);\n}\n\nfunction aggregate(\n\tinput: ExecuteWorkloadShardPlanInput,\n\tprojections: ReadonlyMap<string, WorkloadShardProjection>,\n\teffectiveConcurrency: number,\n\tnow: () => Date,\n\textraReasons: readonly string[],\n): WorkloadShardAggregateEvidence {\n\tconst counts: Record<WorkloadShardState, number> = {\n\t\tpending: 0,\n\t\trunning: 0,\n\t\tpassed: 0,\n\t\tfailed: 0,\n\t\taborted: 0,\n\t\tinterrupted: 0,\n\t};\n\tconst shards: ShardResultSummary[] = input.plan.shards.map((shard) => {\n\t\tconst projection = projections.get(shard.shardId);\n\t\tconst state = projection?.state ?? \"pending\";\n\t\tcounts[state] += 1;\n\t\treturn {\n\t\t\tshardId: shard.shardId,\n\t\t\tstate,\n\t\t\tattempt: projection?.attempt ?? 0,\n\t\t\tevidenceRefs: projection?.evidenceRefs ?? [],\n\t\t\t...(projection?.reasonCode !== undefined ? { reasonCode: projection.reasonCode } : {}),\n\t\t};\n\t});\n\tconst verdict = aggregateVerdict(counts, input.plan.shards.length, extraReasons);\n\treturn {\n\t\tkind: \"workload_shard_result.v1\",\n\t\tplanId: input.plan.planId,\n\t\tpromptRunId: input.plan.promptRunId,\n\t\tcommandDigest: input.plan.commandDigest,\n\t\tstrategy: input.plan.strategy,\n\t\tverdict,\n\t\treasonCodes: extraReasons,\n\t\tshardCount: input.plan.shards.length,\n\t\tcounts,\n\t\tshards,\n\t\teffectiveConcurrency,\n\t\tcompletedAt: now().toISOString(),\n\t};\n}\n\nfunction blockedEvidence(\n\tinput: ExecuteWorkloadShardPlanInput,\n\tnow: () => Date,\n\treasonCodes: readonly string[],\n): WorkloadShardAggregateEvidence {\n\tconst counts: Record<WorkloadShardState, number> = {\n\t\tpending: input.plan.shards.length,\n\t\trunning: 0,\n\t\tpassed: 0,\n\t\tfailed: 0,\n\t\taborted: 0,\n\t\tinterrupted: 0,\n\t};\n\treturn {\n\t\tkind: \"workload_shard_result.v1\",\n\t\tplanId: input.plan.planId,\n\t\tpromptRunId: input.plan.promptRunId,\n\t\tcommandDigest: input.plan.commandDigest,\n\t\tstrategy: input.plan.strategy,\n\t\tverdict: \"blocked\",\n\t\treasonCodes,\n\t\tshardCount: input.plan.shards.length,\n\t\tcounts,\n\t\tshards: input.plan.shards.map((shard) => ({\n\t\t\tshardId: shard.shardId,\n\t\t\tstate: \"pending\" as const,\n\t\t\tattempt: 0,\n\t\t\tevidenceRefs: [],\n\t\t})),\n\t\teffectiveConcurrency: 0,\n\t\tcompletedAt: now().toISOString(),\n\t};\n}\n\n/** §13.6: \"passed\" requires EVERY shard to hold terminal passed evidence. */\nfunction aggregateVerdict(\n\tcounts: Readonly<Record<WorkloadShardState, number>>,\n\tshardCount: number,\n\textraReasons: readonly string[],\n): WorkloadShardAggregateEvidence[\"verdict\"] {\n\tif (counts.passed === shardCount) return \"passed\";\n\tif (counts.aborted > 0 || extraReasons.includes(\"run.aborted\")) return \"aborted\";\n\treturn \"failed\";\n}\n"]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workload shard plan and state contracts (OMK v0.97.x roadmap §13, M5/PR8).
|
|
3
|
+
*
|
|
4
|
+
* Pure layer: plan validation, the §13.4 state machine, deterministic
|
|
5
|
+
* journal replay (reduce), and the §13.5 resume projection. No I/O here —
|
|
6
|
+
* the journal store persists lines and the M5 executor consumes the
|
|
7
|
+
* projections. §13.6: shard completion is an evidence source, never a task
|
|
8
|
+
* verdict; nothing in this module declares a task passed.
|
|
9
|
+
*/
|
|
10
|
+
export declare const WORKLOAD_SHARD_PLAN_VERSION: 1;
|
|
11
|
+
export interface WorkloadShardPlan {
|
|
12
|
+
readonly schemaVersion: typeof WORKLOAD_SHARD_PLAN_VERSION;
|
|
13
|
+
readonly planId: string;
|
|
14
|
+
readonly promptRunId: string;
|
|
15
|
+
readonly commandDigest: string;
|
|
16
|
+
readonly strategy: string;
|
|
17
|
+
readonly createdAt: string;
|
|
18
|
+
readonly maxConcurrency: number;
|
|
19
|
+
readonly shards: readonly WorkloadShardSpec[];
|
|
20
|
+
}
|
|
21
|
+
export interface WorkloadShardSpec {
|
|
22
|
+
readonly shardId: string;
|
|
23
|
+
readonly dependencyIds: readonly string[];
|
|
24
|
+
readonly commandDescriptor: {
|
|
25
|
+
readonly executable: string;
|
|
26
|
+
readonly argvDigest: string;
|
|
27
|
+
readonly redactedArgv: readonly string[];
|
|
28
|
+
};
|
|
29
|
+
readonly expectedEvidence: readonly string[];
|
|
30
|
+
}
|
|
31
|
+
export type WorkloadShardState = "pending" | "running" | "passed" | "failed" | "aborted" | "interrupted";
|
|
32
|
+
export interface WorkloadShardRecord {
|
|
33
|
+
readonly schemaVersion: 1;
|
|
34
|
+
readonly planId: string;
|
|
35
|
+
readonly shardId: string;
|
|
36
|
+
readonly attempt: number;
|
|
37
|
+
readonly state: WorkloadShardState;
|
|
38
|
+
readonly startedAt?: string;
|
|
39
|
+
readonly finishedAt?: string;
|
|
40
|
+
readonly evidenceRefs: readonly string[];
|
|
41
|
+
readonly reasonCode?: string;
|
|
42
|
+
}
|
|
43
|
+
export declare function canTransitionShardState(from: WorkloadShardState, to: WorkloadShardState): boolean;
|
|
44
|
+
/** §22.1: journals carry only the digest and a redacted descriptor, never raw argv. */
|
|
45
|
+
export declare function buildShardCommandDescriptor(argv: readonly string[]): WorkloadShardSpec["commandDescriptor"];
|
|
46
|
+
/** Structural plan validation: ids unique, dependencies resolvable and acyclic. */
|
|
47
|
+
export declare function validateWorkloadShardPlan(plan: WorkloadShardPlan): readonly string[];
|
|
48
|
+
export interface WorkloadShardProjection {
|
|
49
|
+
readonly shardId: string;
|
|
50
|
+
readonly state: WorkloadShardState;
|
|
51
|
+
readonly attempt: number;
|
|
52
|
+
readonly evidenceRefs: readonly string[];
|
|
53
|
+
readonly reasonCode?: string;
|
|
54
|
+
}
|
|
55
|
+
export declare class WorkloadShardTransitionError extends Error {
|
|
56
|
+
readonly shardId: string;
|
|
57
|
+
constructor(shardId: string, message: string);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Deterministic replay of transition records into per-shard projections
|
|
61
|
+
* (§23.2 property 10). Fail closed (§21): unknown shards, illegal §13.4
|
|
62
|
+
* transitions, or non-monotonic attempts throw instead of guessing.
|
|
63
|
+
*/
|
|
64
|
+
export declare function reduceShardRecords(plan: WorkloadShardPlan, records: readonly WorkloadShardRecord[]): ReadonlyMap<string, WorkloadShardProjection>;
|
|
65
|
+
export interface WorkloadShardResumePlan {
|
|
66
|
+
/** §13.5 step 3 / §23.2 property 9: passed shards are never re-run. */
|
|
67
|
+
readonly skip: readonly string[];
|
|
68
|
+
/** Pending shards whose dependencies all passed (§13.5 step 5). */
|
|
69
|
+
readonly ready: readonly string[];
|
|
70
|
+
/** Pending shards still blocked on dependencies. */
|
|
71
|
+
readonly blocked: readonly string[];
|
|
72
|
+
/** Failed shards awaiting the caller's retry policy (§13.5 step 4). */
|
|
73
|
+
readonly retryable: readonly string[];
|
|
74
|
+
/** Aborted shards that need an explicit resume decision (§13.4). */
|
|
75
|
+
readonly resumable: readonly string[];
|
|
76
|
+
/** Shards projected interrupted: `running` with no terminal record (§13.5 step 2). */
|
|
77
|
+
readonly interrupted: readonly string[];
|
|
78
|
+
}
|
|
79
|
+
/** §13.5 resume projection. Pure; ordering follows plan order for determinism. */
|
|
80
|
+
export declare function planShardResume(plan: WorkloadShardPlan, projections: ReadonlyMap<string, WorkloadShardProjection>): WorkloadShardResumePlan;
|
|
81
|
+
//# sourceMappingURL=workload-shard-plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workload-shard-plan.d.ts","sourceRoot":"","sources":["../../src/core/workload-shard-plan.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\n\n/**\n * Workload shard plan and state contracts (OMK v0.97.x roadmap §13, M5/PR8).\n *\n * Pure layer: plan validation, the §13.4 state machine, deterministic\n * journal replay (reduce), and the §13.5 resume projection. No I/O here —\n * the journal store persists lines and the M5 executor consumes the\n * projections. §13.6: shard completion is an evidence source, never a task\n * verdict; nothing in this module declares a task passed.\n */\n\nexport const WORKLOAD_SHARD_PLAN_VERSION = 1 as const;\n\nexport interface WorkloadShardPlan {\n\treadonly schemaVersion: typeof WORKLOAD_SHARD_PLAN_VERSION;\n\treadonly planId: string;\n\treadonly promptRunId: string;\n\treadonly commandDigest: string;\n\treadonly strategy: string;\n\treadonly createdAt: string;\n\treadonly maxConcurrency: number;\n\treadonly shards: readonly WorkloadShardSpec[];\n}\n\nexport interface WorkloadShardSpec {\n\treadonly shardId: string;\n\treadonly dependencyIds: readonly string[];\n\treadonly commandDescriptor: {\n\t\treadonly executable: string;\n\t\treadonly argvDigest: string;\n\t\treadonly redactedArgv: readonly string[];\n\t};\n\treadonly expectedEvidence: readonly string[];\n}\n\nexport type WorkloadShardState = \"pending\" | \"running\" | \"passed\" | \"failed\" | \"aborted\" | \"interrupted\";\n\nexport interface WorkloadShardRecord {\n\treadonly schemaVersion: 1;\n\treadonly planId: string;\n\treadonly shardId: string;\n\treadonly attempt: number;\n\treadonly state: WorkloadShardState;\n\treadonly startedAt?: string;\n\treadonly finishedAt?: string;\n\treadonly evidenceRefs: readonly string[];\n\treadonly reasonCode?: string;\n}\n\n/** §13.4 legal transitions. `pending` re-arms come from failed/aborted/interrupted only. */\nconst LEGAL_TRANSITIONS: Readonly<Record<WorkloadShardState, readonly WorkloadShardState[]>> = {\n\tpending: [\"running\"],\n\trunning: [\"passed\", \"failed\", \"aborted\", \"interrupted\"],\n\tfailed: [\"pending\"],\n\taborted: [\"pending\"],\n\tinterrupted: [\"pending\"],\n\tpassed: [],\n};\n\nexport function canTransitionShardState(from: WorkloadShardState, to: WorkloadShardState): boolean {\n\treturn LEGAL_TRANSITIONS[from].includes(to);\n}\n\n/** §22.1: journals carry only the digest and a redacted descriptor, never raw argv. */\nexport function buildShardCommandDescriptor(argv: readonly string[]): WorkloadShardSpec[\"commandDescriptor\"] {\n\tconst executable = basename(argv[0] ?? \"\");\n\tconst redactedArgv = argv.map((token, index) => {\n\t\tif (index === 0) {\n\t\t\treturn executable;\n\t\t}\n\t\tif (/^-{1,2}[A-Za-z0-9][A-Za-z0-9=/:.,_-]*$/.test(token)) {\n\t\t\treturn token; // flags (with inline values) stay readable\n\t\t}\n\t\tif (/^[A-Za-z0-9_.:-]{1,32}$/.test(token) && !token.includes(\"..\")) {\n\t\t\treturn token; // short bare words (subcommands, shard specs)\n\t\t}\n\t\treturn \"<redacted>\"; // paths, globs, and anything possibly sensitive\n\t});\n\treturn {\n\t\texecutable,\n\t\targvDigest: createHash(\"sha256\").update(JSON.stringify(argv), \"utf8\").digest(\"hex\"),\n\t\tredactedArgv,\n\t};\n}\n\n/** Structural plan validation: ids unique, dependencies resolvable and acyclic. */\nexport function validateWorkloadShardPlan(plan: WorkloadShardPlan): readonly string[] {\n\tconst errors: string[] = [];\n\tif (plan.schemaVersion !== WORKLOAD_SHARD_PLAN_VERSION) {\n\t\terrors.push(`plan.schemaVersion: expected ${WORKLOAD_SHARD_PLAN_VERSION}`);\n\t}\n\tif (!Number.isSafeInteger(plan.maxConcurrency) || plan.maxConcurrency < 1) {\n\t\terrors.push(\"plan.maxConcurrency: must be a positive integer\");\n\t}\n\tif (plan.shards.length === 0) {\n\t\terrors.push(\"plan.shards: must not be empty\");\n\t}\n\tconst ids = new Set<string>();\n\tfor (const shard of plan.shards) {\n\t\tif (ids.has(shard.shardId)) {\n\t\t\terrors.push(`plan.shards: duplicate shardId ${shard.shardId}`);\n\t\t}\n\t\tids.add(shard.shardId);\n\t}\n\tfor (const shard of plan.shards) {\n\t\tfor (const dependency of shard.dependencyIds) {\n\t\t\tif (!ids.has(dependency)) {\n\t\t\t\terrors.push(`plan.shards: ${shard.shardId} depends on unknown ${dependency}`);\n\t\t\t}\n\t\t}\n\t}\n\tif (errors.length === 0 && hasDependencyCycle(plan.shards)) {\n\t\terrors.push(\"plan.shards: dependency cycle detected\");\n\t}\n\treturn errors;\n}\n\nfunction hasDependencyCycle(shards: readonly WorkloadShardSpec[]): boolean {\n\tconst visiting = new Set<string>();\n\tconst done = new Set<string>();\n\tconst byId = new Map(shards.map((shard) => [shard.shardId, shard]));\n\tconst visit = (id: string): boolean => {\n\t\tif (done.has(id)) return false;\n\t\tif (visiting.has(id)) return true;\n\t\tvisiting.add(id);\n\t\tfor (const dependency of byId.get(id)?.dependencyIds ?? []) {\n\t\t\tif (visit(dependency)) return true;\n\t\t}\n\t\tvisiting.delete(id);\n\t\tdone.add(id);\n\t\treturn false;\n\t};\n\treturn shards.some((shard) => visit(shard.shardId));\n}\n\nexport interface WorkloadShardProjection {\n\treadonly shardId: string;\n\treadonly state: WorkloadShardState;\n\treadonly attempt: number;\n\treadonly evidenceRefs: readonly string[];\n\treadonly reasonCode?: string;\n}\n\nexport class WorkloadShardTransitionError extends Error {\n\treadonly shardId: string;\n\tconstructor(shardId: string, message: string) {\n\t\tsuper(`shard ${shardId}: ${message}`);\n\t\tthis.name = \"WorkloadShardTransitionError\";\n\t\tthis.shardId = shardId;\n\t}\n}\n\n/**\n * Deterministic replay of transition records into per-shard projections\n * (§23.2 property 10). Fail closed (§21): unknown shards, illegal §13.4\n * transitions, or non-monotonic attempts throw instead of guessing.\n */\nexport function reduceShardRecords(\n\tplan: WorkloadShardPlan,\n\trecords: readonly WorkloadShardRecord[],\n): ReadonlyMap<string, WorkloadShardProjection> {\n\tconst projections = new Map<string, WorkloadShardProjection>();\n\tfor (const shard of plan.shards) {\n\t\tprojections.set(shard.shardId, { shardId: shard.shardId, state: \"pending\", attempt: 0, evidenceRefs: [] });\n\t}\n\tfor (const record of records) {\n\t\tconst current = projections.get(record.shardId);\n\t\tif (current === undefined) {\n\t\t\tthrow new WorkloadShardTransitionError(record.shardId, \"transition for shard not in plan\");\n\t\t}\n\t\tif (record.planId !== plan.planId) {\n\t\t\tthrow new WorkloadShardTransitionError(record.shardId, `record planId ${record.planId} != ${plan.planId}`);\n\t\t}\n\t\tif (!canTransitionShardState(current.state, record.state)) {\n\t\t\tthrow new WorkloadShardTransitionError(\n\t\t\t\trecord.shardId,\n\t\t\t\t`illegal transition ${current.state} -> ${record.state}`,\n\t\t\t);\n\t\t}\n\t\tconst expectedAttempt = record.state === \"running\" ? current.attempt + 1 : current.attempt;\n\t\tif (record.attempt !== expectedAttempt) {\n\t\t\tthrow new WorkloadShardTransitionError(\n\t\t\t\trecord.shardId,\n\t\t\t\t`attempt ${record.attempt} != expected ${expectedAttempt} for ${record.state}`,\n\t\t\t);\n\t\t}\n\t\tprojections.set(record.shardId, {\n\t\t\tshardId: record.shardId,\n\t\t\tstate: record.state,\n\t\t\tattempt: expectedAttempt,\n\t\t\tevidenceRefs: record.evidenceRefs.length > 0 ? record.evidenceRefs : current.evidenceRefs,\n\t\t\treasonCode: record.reasonCode ?? (record.state === \"pending\" ? undefined : current.reasonCode),\n\t\t});\n\t}\n\treturn projections;\n}\n\nexport interface WorkloadShardResumePlan {\n\t/** §13.5 step 3 / §23.2 property 9: passed shards are never re-run. */\n\treadonly skip: readonly string[];\n\t/** Pending shards whose dependencies all passed (§13.5 step 5). */\n\treadonly ready: readonly string[];\n\t/** Pending shards still blocked on dependencies. */\n\treadonly blocked: readonly string[];\n\t/** Failed shards awaiting the caller's retry policy (§13.5 step 4). */\n\treadonly retryable: readonly string[];\n\t/** Aborted shards that need an explicit resume decision (§13.4). */\n\treadonly resumable: readonly string[];\n\t/** Shards projected interrupted: `running` with no terminal record (§13.5 step 2). */\n\treadonly interrupted: readonly string[];\n}\n\n/** §13.5 resume projection. Pure; ordering follows plan order for determinism. */\nexport function planShardResume(\n\tplan: WorkloadShardPlan,\n\tprojections: ReadonlyMap<string, WorkloadShardProjection>,\n): WorkloadShardResumePlan {\n\tconst skip: string[] = [];\n\tconst ready: string[] = [];\n\tconst blocked: string[] = [];\n\tconst retryable: string[] = [];\n\tconst resumable: string[] = [];\n\tconst interrupted: string[] = [];\n\tconst stateOf = (id: string): WorkloadShardState => projections.get(id)?.state ?? \"pending\";\n\tfor (const shard of plan.shards) {\n\t\tswitch (stateOf(shard.shardId)) {\n\t\t\tcase \"passed\":\n\t\t\t\tskip.push(shard.shardId);\n\t\t\t\tbreak;\n\t\t\tcase \"failed\":\n\t\t\t\tretryable.push(shard.shardId);\n\t\t\t\tbreak;\n\t\t\tcase \"aborted\":\n\t\t\t\tresumable.push(shard.shardId);\n\t\t\t\tbreak;\n\t\t\t// A crash can leave `running` with no terminal record; recovery\n\t\t\t// projects it as interrupted work needing a fresh attempt.\n\t\t\tcase \"running\":\n\t\t\tcase \"interrupted\":\n\t\t\t\tinterrupted.push(shard.shardId);\n\t\t\t\tbreak;\n\t\t\tcase \"pending\":\n\t\t\t\tif (shard.dependencyIds.every((dependency) => stateOf(dependency) === \"passed\")) {\n\t\t\t\t\tready.push(shard.shardId);\n\t\t\t\t} else {\n\t\t\t\t\tblocked.push(shard.shardId);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn { skip, ready, blocked, retryable, resumable, interrupted };\n}\n\nfunction basename(token: string): string {\n\tconst slash = token.lastIndexOf(\"/\");\n\treturn slash === -1 ? token : token.slice(slash + 1);\n}\n"]}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Workload shard plan and state contracts (OMK v0.97.x roadmap §13, M5/PR8).
|
|
4
|
+
*
|
|
5
|
+
* Pure layer: plan validation, the §13.4 state machine, deterministic
|
|
6
|
+
* journal replay (reduce), and the §13.5 resume projection. No I/O here —
|
|
7
|
+
* the journal store persists lines and the M5 executor consumes the
|
|
8
|
+
* projections. §13.6: shard completion is an evidence source, never a task
|
|
9
|
+
* verdict; nothing in this module declares a task passed.
|
|
10
|
+
*/
|
|
11
|
+
export const WORKLOAD_SHARD_PLAN_VERSION = 1;
|
|
12
|
+
/** §13.4 legal transitions. `pending` re-arms come from failed/aborted/interrupted only. */
|
|
13
|
+
const LEGAL_TRANSITIONS = {
|
|
14
|
+
pending: ["running"],
|
|
15
|
+
running: ["passed", "failed", "aborted", "interrupted"],
|
|
16
|
+
failed: ["pending"],
|
|
17
|
+
aborted: ["pending"],
|
|
18
|
+
interrupted: ["pending"],
|
|
19
|
+
passed: [],
|
|
20
|
+
};
|
|
21
|
+
export function canTransitionShardState(from, to) {
|
|
22
|
+
return LEGAL_TRANSITIONS[from].includes(to);
|
|
23
|
+
}
|
|
24
|
+
/** §22.1: journals carry only the digest and a redacted descriptor, never raw argv. */
|
|
25
|
+
export function buildShardCommandDescriptor(argv) {
|
|
26
|
+
const executable = basename(argv[0] ?? "");
|
|
27
|
+
const redactedArgv = argv.map((token, index) => {
|
|
28
|
+
if (index === 0) {
|
|
29
|
+
return executable;
|
|
30
|
+
}
|
|
31
|
+
if (/^-{1,2}[A-Za-z0-9][A-Za-z0-9=/:.,_-]*$/.test(token)) {
|
|
32
|
+
return token; // flags (with inline values) stay readable
|
|
33
|
+
}
|
|
34
|
+
if (/^[A-Za-z0-9_.:-]{1,32}$/.test(token) && !token.includes("..")) {
|
|
35
|
+
return token; // short bare words (subcommands, shard specs)
|
|
36
|
+
}
|
|
37
|
+
return "<redacted>"; // paths, globs, and anything possibly sensitive
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
executable,
|
|
41
|
+
argvDigest: createHash("sha256").update(JSON.stringify(argv), "utf8").digest("hex"),
|
|
42
|
+
redactedArgv,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Structural plan validation: ids unique, dependencies resolvable and acyclic. */
|
|
46
|
+
export function validateWorkloadShardPlan(plan) {
|
|
47
|
+
const errors = [];
|
|
48
|
+
if (plan.schemaVersion !== WORKLOAD_SHARD_PLAN_VERSION) {
|
|
49
|
+
errors.push(`plan.schemaVersion: expected ${WORKLOAD_SHARD_PLAN_VERSION}`);
|
|
50
|
+
}
|
|
51
|
+
if (!Number.isSafeInteger(plan.maxConcurrency) || plan.maxConcurrency < 1) {
|
|
52
|
+
errors.push("plan.maxConcurrency: must be a positive integer");
|
|
53
|
+
}
|
|
54
|
+
if (plan.shards.length === 0) {
|
|
55
|
+
errors.push("plan.shards: must not be empty");
|
|
56
|
+
}
|
|
57
|
+
const ids = new Set();
|
|
58
|
+
for (const shard of plan.shards) {
|
|
59
|
+
if (ids.has(shard.shardId)) {
|
|
60
|
+
errors.push(`plan.shards: duplicate shardId ${shard.shardId}`);
|
|
61
|
+
}
|
|
62
|
+
ids.add(shard.shardId);
|
|
63
|
+
}
|
|
64
|
+
for (const shard of plan.shards) {
|
|
65
|
+
for (const dependency of shard.dependencyIds) {
|
|
66
|
+
if (!ids.has(dependency)) {
|
|
67
|
+
errors.push(`plan.shards: ${shard.shardId} depends on unknown ${dependency}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (errors.length === 0 && hasDependencyCycle(plan.shards)) {
|
|
72
|
+
errors.push("plan.shards: dependency cycle detected");
|
|
73
|
+
}
|
|
74
|
+
return errors;
|
|
75
|
+
}
|
|
76
|
+
function hasDependencyCycle(shards) {
|
|
77
|
+
const visiting = new Set();
|
|
78
|
+
const done = new Set();
|
|
79
|
+
const byId = new Map(shards.map((shard) => [shard.shardId, shard]));
|
|
80
|
+
const visit = (id) => {
|
|
81
|
+
if (done.has(id))
|
|
82
|
+
return false;
|
|
83
|
+
if (visiting.has(id))
|
|
84
|
+
return true;
|
|
85
|
+
visiting.add(id);
|
|
86
|
+
for (const dependency of byId.get(id)?.dependencyIds ?? []) {
|
|
87
|
+
if (visit(dependency))
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
visiting.delete(id);
|
|
91
|
+
done.add(id);
|
|
92
|
+
return false;
|
|
93
|
+
};
|
|
94
|
+
return shards.some((shard) => visit(shard.shardId));
|
|
95
|
+
}
|
|
96
|
+
export class WorkloadShardTransitionError extends Error {
|
|
97
|
+
shardId;
|
|
98
|
+
constructor(shardId, message) {
|
|
99
|
+
super(`shard ${shardId}: ${message}`);
|
|
100
|
+
this.name = "WorkloadShardTransitionError";
|
|
101
|
+
this.shardId = shardId;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Deterministic replay of transition records into per-shard projections
|
|
106
|
+
* (§23.2 property 10). Fail closed (§21): unknown shards, illegal §13.4
|
|
107
|
+
* transitions, or non-monotonic attempts throw instead of guessing.
|
|
108
|
+
*/
|
|
109
|
+
export function reduceShardRecords(plan, records) {
|
|
110
|
+
const projections = new Map();
|
|
111
|
+
for (const shard of plan.shards) {
|
|
112
|
+
projections.set(shard.shardId, { shardId: shard.shardId, state: "pending", attempt: 0, evidenceRefs: [] });
|
|
113
|
+
}
|
|
114
|
+
for (const record of records) {
|
|
115
|
+
const current = projections.get(record.shardId);
|
|
116
|
+
if (current === undefined) {
|
|
117
|
+
throw new WorkloadShardTransitionError(record.shardId, "transition for shard not in plan");
|
|
118
|
+
}
|
|
119
|
+
if (record.planId !== plan.planId) {
|
|
120
|
+
throw new WorkloadShardTransitionError(record.shardId, `record planId ${record.planId} != ${plan.planId}`);
|
|
121
|
+
}
|
|
122
|
+
if (!canTransitionShardState(current.state, record.state)) {
|
|
123
|
+
throw new WorkloadShardTransitionError(record.shardId, `illegal transition ${current.state} -> ${record.state}`);
|
|
124
|
+
}
|
|
125
|
+
const expectedAttempt = record.state === "running" ? current.attempt + 1 : current.attempt;
|
|
126
|
+
if (record.attempt !== expectedAttempt) {
|
|
127
|
+
throw new WorkloadShardTransitionError(record.shardId, `attempt ${record.attempt} != expected ${expectedAttempt} for ${record.state}`);
|
|
128
|
+
}
|
|
129
|
+
projections.set(record.shardId, {
|
|
130
|
+
shardId: record.shardId,
|
|
131
|
+
state: record.state,
|
|
132
|
+
attempt: expectedAttempt,
|
|
133
|
+
evidenceRefs: record.evidenceRefs.length > 0 ? record.evidenceRefs : current.evidenceRefs,
|
|
134
|
+
reasonCode: record.reasonCode ?? (record.state === "pending" ? undefined : current.reasonCode),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return projections;
|
|
138
|
+
}
|
|
139
|
+
/** §13.5 resume projection. Pure; ordering follows plan order for determinism. */
|
|
140
|
+
export function planShardResume(plan, projections) {
|
|
141
|
+
const skip = [];
|
|
142
|
+
const ready = [];
|
|
143
|
+
const blocked = [];
|
|
144
|
+
const retryable = [];
|
|
145
|
+
const resumable = [];
|
|
146
|
+
const interrupted = [];
|
|
147
|
+
const stateOf = (id) => projections.get(id)?.state ?? "pending";
|
|
148
|
+
for (const shard of plan.shards) {
|
|
149
|
+
switch (stateOf(shard.shardId)) {
|
|
150
|
+
case "passed":
|
|
151
|
+
skip.push(shard.shardId);
|
|
152
|
+
break;
|
|
153
|
+
case "failed":
|
|
154
|
+
retryable.push(shard.shardId);
|
|
155
|
+
break;
|
|
156
|
+
case "aborted":
|
|
157
|
+
resumable.push(shard.shardId);
|
|
158
|
+
break;
|
|
159
|
+
// A crash can leave `running` with no terminal record; recovery
|
|
160
|
+
// projects it as interrupted work needing a fresh attempt.
|
|
161
|
+
case "running":
|
|
162
|
+
case "interrupted":
|
|
163
|
+
interrupted.push(shard.shardId);
|
|
164
|
+
break;
|
|
165
|
+
case "pending":
|
|
166
|
+
if (shard.dependencyIds.every((dependency) => stateOf(dependency) === "passed")) {
|
|
167
|
+
ready.push(shard.shardId);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
blocked.push(shard.shardId);
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
default:
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return { skip, ready, blocked, retryable, resumable, interrupted };
|
|
178
|
+
}
|
|
179
|
+
function basename(token) {
|
|
180
|
+
const slash = token.lastIndexOf("/");
|
|
181
|
+
return slash === -1 ? token : token.slice(slash + 1);
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=workload-shard-plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workload-shard-plan.js","sourceRoot":"","sources":["../../src/core/workload-shard-plan.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\n\n/**\n * Workload shard plan and state contracts (OMK v0.97.x roadmap §13, M5/PR8).\n *\n * Pure layer: plan validation, the §13.4 state machine, deterministic\n * journal replay (reduce), and the §13.5 resume projection. No I/O here —\n * the journal store persists lines and the M5 executor consumes the\n * projections. §13.6: shard completion is an evidence source, never a task\n * verdict; nothing in this module declares a task passed.\n */\n\nexport const WORKLOAD_SHARD_PLAN_VERSION = 1 as const;\n\nexport interface WorkloadShardPlan {\n\treadonly schemaVersion: typeof WORKLOAD_SHARD_PLAN_VERSION;\n\treadonly planId: string;\n\treadonly promptRunId: string;\n\treadonly commandDigest: string;\n\treadonly strategy: string;\n\treadonly createdAt: string;\n\treadonly maxConcurrency: number;\n\treadonly shards: readonly WorkloadShardSpec[];\n}\n\nexport interface WorkloadShardSpec {\n\treadonly shardId: string;\n\treadonly dependencyIds: readonly string[];\n\treadonly commandDescriptor: {\n\t\treadonly executable: string;\n\t\treadonly argvDigest: string;\n\t\treadonly redactedArgv: readonly string[];\n\t};\n\treadonly expectedEvidence: readonly string[];\n}\n\nexport type WorkloadShardState = \"pending\" | \"running\" | \"passed\" | \"failed\" | \"aborted\" | \"interrupted\";\n\nexport interface WorkloadShardRecord {\n\treadonly schemaVersion: 1;\n\treadonly planId: string;\n\treadonly shardId: string;\n\treadonly attempt: number;\n\treadonly state: WorkloadShardState;\n\treadonly startedAt?: string;\n\treadonly finishedAt?: string;\n\treadonly evidenceRefs: readonly string[];\n\treadonly reasonCode?: string;\n}\n\n/** §13.4 legal transitions. `pending` re-arms come from failed/aborted/interrupted only. */\nconst LEGAL_TRANSITIONS: Readonly<Record<WorkloadShardState, readonly WorkloadShardState[]>> = {\n\tpending: [\"running\"],\n\trunning: [\"passed\", \"failed\", \"aborted\", \"interrupted\"],\n\tfailed: [\"pending\"],\n\taborted: [\"pending\"],\n\tinterrupted: [\"pending\"],\n\tpassed: [],\n};\n\nexport function canTransitionShardState(from: WorkloadShardState, to: WorkloadShardState): boolean {\n\treturn LEGAL_TRANSITIONS[from].includes(to);\n}\n\n/** §22.1: journals carry only the digest and a redacted descriptor, never raw argv. */\nexport function buildShardCommandDescriptor(argv: readonly string[]): WorkloadShardSpec[\"commandDescriptor\"] {\n\tconst executable = basename(argv[0] ?? \"\");\n\tconst redactedArgv = argv.map((token, index) => {\n\t\tif (index === 0) {\n\t\t\treturn executable;\n\t\t}\n\t\tif (/^-{1,2}[A-Za-z0-9][A-Za-z0-9=/:.,_-]*$/.test(token)) {\n\t\t\treturn token; // flags (with inline values) stay readable\n\t\t}\n\t\tif (/^[A-Za-z0-9_.:-]{1,32}$/.test(token) && !token.includes(\"..\")) {\n\t\t\treturn token; // short bare words (subcommands, shard specs)\n\t\t}\n\t\treturn \"<redacted>\"; // paths, globs, and anything possibly sensitive\n\t});\n\treturn {\n\t\texecutable,\n\t\targvDigest: createHash(\"sha256\").update(JSON.stringify(argv), \"utf8\").digest(\"hex\"),\n\t\tredactedArgv,\n\t};\n}\n\n/** Structural plan validation: ids unique, dependencies resolvable and acyclic. */\nexport function validateWorkloadShardPlan(plan: WorkloadShardPlan): readonly string[] {\n\tconst errors: string[] = [];\n\tif (plan.schemaVersion !== WORKLOAD_SHARD_PLAN_VERSION) {\n\t\terrors.push(`plan.schemaVersion: expected ${WORKLOAD_SHARD_PLAN_VERSION}`);\n\t}\n\tif (!Number.isSafeInteger(plan.maxConcurrency) || plan.maxConcurrency < 1) {\n\t\terrors.push(\"plan.maxConcurrency: must be a positive integer\");\n\t}\n\tif (plan.shards.length === 0) {\n\t\terrors.push(\"plan.shards: must not be empty\");\n\t}\n\tconst ids = new Set<string>();\n\tfor (const shard of plan.shards) {\n\t\tif (ids.has(shard.shardId)) {\n\t\t\terrors.push(`plan.shards: duplicate shardId ${shard.shardId}`);\n\t\t}\n\t\tids.add(shard.shardId);\n\t}\n\tfor (const shard of plan.shards) {\n\t\tfor (const dependency of shard.dependencyIds) {\n\t\t\tif (!ids.has(dependency)) {\n\t\t\t\terrors.push(`plan.shards: ${shard.shardId} depends on unknown ${dependency}`);\n\t\t\t}\n\t\t}\n\t}\n\tif (errors.length === 0 && hasDependencyCycle(plan.shards)) {\n\t\terrors.push(\"plan.shards: dependency cycle detected\");\n\t}\n\treturn errors;\n}\n\nfunction hasDependencyCycle(shards: readonly WorkloadShardSpec[]): boolean {\n\tconst visiting = new Set<string>();\n\tconst done = new Set<string>();\n\tconst byId = new Map(shards.map((shard) => [shard.shardId, shard]));\n\tconst visit = (id: string): boolean => {\n\t\tif (done.has(id)) return false;\n\t\tif (visiting.has(id)) return true;\n\t\tvisiting.add(id);\n\t\tfor (const dependency of byId.get(id)?.dependencyIds ?? []) {\n\t\t\tif (visit(dependency)) return true;\n\t\t}\n\t\tvisiting.delete(id);\n\t\tdone.add(id);\n\t\treturn false;\n\t};\n\treturn shards.some((shard) => visit(shard.shardId));\n}\n\nexport interface WorkloadShardProjection {\n\treadonly shardId: string;\n\treadonly state: WorkloadShardState;\n\treadonly attempt: number;\n\treadonly evidenceRefs: readonly string[];\n\treadonly reasonCode?: string;\n}\n\nexport class WorkloadShardTransitionError extends Error {\n\treadonly shardId: string;\n\tconstructor(shardId: string, message: string) {\n\t\tsuper(`shard ${shardId}: ${message}`);\n\t\tthis.name = \"WorkloadShardTransitionError\";\n\t\tthis.shardId = shardId;\n\t}\n}\n\n/**\n * Deterministic replay of transition records into per-shard projections\n * (§23.2 property 10). Fail closed (§21): unknown shards, illegal §13.4\n * transitions, or non-monotonic attempts throw instead of guessing.\n */\nexport function reduceShardRecords(\n\tplan: WorkloadShardPlan,\n\trecords: readonly WorkloadShardRecord[],\n): ReadonlyMap<string, WorkloadShardProjection> {\n\tconst projections = new Map<string, WorkloadShardProjection>();\n\tfor (const shard of plan.shards) {\n\t\tprojections.set(shard.shardId, { shardId: shard.shardId, state: \"pending\", attempt: 0, evidenceRefs: [] });\n\t}\n\tfor (const record of records) {\n\t\tconst current = projections.get(record.shardId);\n\t\tif (current === undefined) {\n\t\t\tthrow new WorkloadShardTransitionError(record.shardId, \"transition for shard not in plan\");\n\t\t}\n\t\tif (record.planId !== plan.planId) {\n\t\t\tthrow new WorkloadShardTransitionError(record.shardId, `record planId ${record.planId} != ${plan.planId}`);\n\t\t}\n\t\tif (!canTransitionShardState(current.state, record.state)) {\n\t\t\tthrow new WorkloadShardTransitionError(\n\t\t\t\trecord.shardId,\n\t\t\t\t`illegal transition ${current.state} -> ${record.state}`,\n\t\t\t);\n\t\t}\n\t\tconst expectedAttempt = record.state === \"running\" ? current.attempt + 1 : current.attempt;\n\t\tif (record.attempt !== expectedAttempt) {\n\t\t\tthrow new WorkloadShardTransitionError(\n\t\t\t\trecord.shardId,\n\t\t\t\t`attempt ${record.attempt} != expected ${expectedAttempt} for ${record.state}`,\n\t\t\t);\n\t\t}\n\t\tprojections.set(record.shardId, {\n\t\t\tshardId: record.shardId,\n\t\t\tstate: record.state,\n\t\t\tattempt: expectedAttempt,\n\t\t\tevidenceRefs: record.evidenceRefs.length > 0 ? record.evidenceRefs : current.evidenceRefs,\n\t\t\treasonCode: record.reasonCode ?? (record.state === \"pending\" ? undefined : current.reasonCode),\n\t\t});\n\t}\n\treturn projections;\n}\n\nexport interface WorkloadShardResumePlan {\n\t/** §13.5 step 3 / §23.2 property 9: passed shards are never re-run. */\n\treadonly skip: readonly string[];\n\t/** Pending shards whose dependencies all passed (§13.5 step 5). */\n\treadonly ready: readonly string[];\n\t/** Pending shards still blocked on dependencies. */\n\treadonly blocked: readonly string[];\n\t/** Failed shards awaiting the caller's retry policy (§13.5 step 4). */\n\treadonly retryable: readonly string[];\n\t/** Aborted shards that need an explicit resume decision (§13.4). */\n\treadonly resumable: readonly string[];\n\t/** Shards projected interrupted: `running` with no terminal record (§13.5 step 2). */\n\treadonly interrupted: readonly string[];\n}\n\n/** §13.5 resume projection. Pure; ordering follows plan order for determinism. */\nexport function planShardResume(\n\tplan: WorkloadShardPlan,\n\tprojections: ReadonlyMap<string, WorkloadShardProjection>,\n): WorkloadShardResumePlan {\n\tconst skip: string[] = [];\n\tconst ready: string[] = [];\n\tconst blocked: string[] = [];\n\tconst retryable: string[] = [];\n\tconst resumable: string[] = [];\n\tconst interrupted: string[] = [];\n\tconst stateOf = (id: string): WorkloadShardState => projections.get(id)?.state ?? \"pending\";\n\tfor (const shard of plan.shards) {\n\t\tswitch (stateOf(shard.shardId)) {\n\t\t\tcase \"passed\":\n\t\t\t\tskip.push(shard.shardId);\n\t\t\t\tbreak;\n\t\t\tcase \"failed\":\n\t\t\t\tretryable.push(shard.shardId);\n\t\t\t\tbreak;\n\t\t\tcase \"aborted\":\n\t\t\t\tresumable.push(shard.shardId);\n\t\t\t\tbreak;\n\t\t\t// A crash can leave `running` with no terminal record; recovery\n\t\t\t// projects it as interrupted work needing a fresh attempt.\n\t\t\tcase \"running\":\n\t\t\tcase \"interrupted\":\n\t\t\t\tinterrupted.push(shard.shardId);\n\t\t\t\tbreak;\n\t\t\tcase \"pending\":\n\t\t\t\tif (shard.dependencyIds.every((dependency) => stateOf(dependency) === \"passed\")) {\n\t\t\t\t\tready.push(shard.shardId);\n\t\t\t\t} else {\n\t\t\t\t\tblocked.push(shard.shardId);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn { skip, ready, blocked, retryable, resumable, interrupted };\n}\n\nfunction basename(token: string): string {\n\tconst slash = token.lastIndexOf(\"/\");\n\treturn slash === -1 ? token : token.slice(slash + 1);\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ExecuteWorkloadShardPlanInput } from "./workload-shard-executor.ts";
|
|
2
|
+
import type { WorkloadShardPlan, WorkloadShardProjection, WorkloadShardSpec, WorkloadShardState } from "./workload-shard-plan.ts";
|
|
3
|
+
export declare function runOneShard(input: ExecuteWorkloadShardPlanInput, projections: Map<string, WorkloadShardProjection>, shardId: string, now: () => Date): Promise<void>;
|
|
4
|
+
export declare function applyTransition(input: ExecuteWorkloadShardPlanInput, projections: Map<string, WorkloadShardProjection>, shardId: string, state: WorkloadShardState, options: {
|
|
5
|
+
readonly now: () => Date;
|
|
6
|
+
readonly startedAt?: string;
|
|
7
|
+
readonly evidenceRefs?: readonly string[];
|
|
8
|
+
readonly reasonCode?: string;
|
|
9
|
+
}): void;
|
|
10
|
+
export declare function shardById(plan: WorkloadShardPlan, shardId: string): WorkloadShardSpec;
|
|
11
|
+
//# sourceMappingURL=workload-shard-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workload-shard-runner.d.ts","sourceRoot":"","sources":["../../src/core/workload-shard-runner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,8BAA8B,CAAC;AAClF,OAAO,KAAK,EACX,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,EACjB,kBAAkB,EAClB,MAAM,0BAA0B,CAAC;AAElC,wBAAsB,WAAW,CAChC,KAAK,EAAE,6BAA6B,EACpC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAAC,EACjD,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,IAAI,GACb,OAAO,CAAC,IAAI,CAAC,CAkDf;AAED,wBAAgB,eAAe,CAC9B,KAAK,EAAE,6BAA6B,EACpC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAAC,EACjD,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE;IACR,QAAQ,CAAC,GAAG,EAAE,MAAM,IAAI,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B,GACC,IAAI,CAuBN;AAYD,wBAAgB,SAAS,CAAC,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAMrF","sourcesContent":["import { WorkloadPermitError } from \"./workload-permit-pool.ts\";\nimport type { ExecuteWorkloadShardPlanInput } from \"./workload-shard-executor.ts\";\nimport type {\n\tWorkloadShardPlan,\n\tWorkloadShardProjection,\n\tWorkloadShardSpec,\n\tWorkloadShardState,\n} from \"./workload-shard-plan.ts\";\n\nexport async function runOneShard(\n\tinput: ExecuteWorkloadShardPlanInput,\n\tprojections: Map<string, WorkloadShardProjection>,\n\tshardId: string,\n\tnow: () => Date,\n): Promise<void> {\n\tconst shard = shardById(input.plan, shardId);\n\tlet releasePermit: (() => void) | undefined;\n\tif (input.permitPool) {\n\t\ttry {\n\t\t\tconst permit = await input.permitPool.acquire({\n\t\t\t\trequestId: `shard-${input.plan.planId}-${shardId}`,\n\t\t\t\tpromptRunId: input.plan.promptRunId,\n\t\t\t\tworkloadClass: \"heavy\",\n\t\t\t\tweight: 1,\n\t\t\t\tsignal: input.signal,\n\t\t\t\ttimeoutMs: input.permitWaitTimeoutMs ?? 120_000,\n\t\t\t});\n\t\t\treleasePermit = () => permit.release();\n\t\t} catch (error) {\n\t\t\tconst aborted = error instanceof WorkloadPermitError && error.code === \"aborted\";\n\t\t\t// Never ran: stays pending; an aborted wait is not a shard failure.\n\t\t\tif (!aborted) {\n\t\t\t\tapplyTransition(input, projections, shardId, \"running\", { now });\n\t\t\t\tapplyTransition(input, projections, shardId, \"failed\", { reasonCode: \"permit.unavailable\", now });\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t}\n\tconst startedAt = now().toISOString();\n\tapplyTransition(input, projections, shardId, \"running\", { now, startedAt });\n\ttry {\n\t\tconst result = await input.runner({\n\t\t\tshard,\n\t\t\tattempt: projections.get(shardId)?.attempt ?? 1,\n\t\t\tsignal: input.signal,\n\t\t});\n\t\tconst state = terminalStateFor(input.signal, result.exitCode);\n\t\tapplyTransition(input, projections, shardId, state, {\n\t\t\tnow,\n\t\t\tstartedAt,\n\t\t\tevidenceRefs: [`exit-code:${result.exitCode}`, ...(result.evidenceRefs ?? [])],\n\t\t\treasonCode: terminalReasonFor(state, result.exitCode),\n\t\t});\n\t} catch (error) {\n\t\tconst state: WorkloadShardState = input.signal?.aborted ? \"aborted\" : \"failed\";\n\t\tapplyTransition(input, projections, shardId, state, {\n\t\t\tnow,\n\t\t\tstartedAt,\n\t\t\treasonCode: state === \"aborted\" ? \"run.aborted\" : \"runner.error\",\n\t\t\tevidenceRefs: [`error:${String(error).slice(0, 120)}`],\n\t\t});\n\t} finally {\n\t\treleasePermit?.();\n\t}\n}\n\nexport function applyTransition(\n\tinput: ExecuteWorkloadShardPlanInput,\n\tprojections: Map<string, WorkloadShardProjection>,\n\tshardId: string,\n\tstate: WorkloadShardState,\n\toptions: {\n\t\treadonly now: () => Date;\n\t\treadonly startedAt?: string;\n\t\treadonly evidenceRefs?: readonly string[];\n\t\treadonly reasonCode?: string;\n\t},\n): void {\n\tconst current = projections.get(shardId);\n\tif (current === undefined) return;\n\tconst attempt = state === \"running\" ? current.attempt + 1 : current.attempt;\n\tconst terminal = state !== \"pending\" && state !== \"running\";\n\tinput.store.appendTransition({\n\t\tschemaVersion: 1,\n\t\tplanId: input.plan.planId,\n\t\tshardId,\n\t\tattempt,\n\t\tstate,\n\t\t...(options.startedAt !== undefined && state === \"running\" ? { startedAt: options.startedAt } : {}),\n\t\t...(terminal ? { finishedAt: options.now().toISOString() } : {}),\n\t\tevidenceRefs: options.evidenceRefs ?? [],\n\t\t...(options.reasonCode !== undefined ? { reasonCode: options.reasonCode } : {}),\n\t});\n\tprojections.set(shardId, {\n\t\tshardId,\n\t\tstate,\n\t\tattempt,\n\t\tevidenceRefs: options.evidenceRefs?.length ? options.evidenceRefs : current.evidenceRefs,\n\t\treasonCode: options.reasonCode ?? (state === \"pending\" ? undefined : current.reasonCode),\n\t});\n}\n\nfunction terminalStateFor(signal: AbortSignal | undefined, exitCode: number): WorkloadShardState {\n\tif (signal?.aborted) return \"aborted\";\n\treturn exitCode === 0 ? \"passed\" : \"failed\";\n}\n\nfunction terminalReasonFor(state: WorkloadShardState, exitCode: number): string | undefined {\n\tif (state === \"failed\") return `exit.${exitCode}`;\n\treturn state === \"aborted\" ? \"run.aborted\" : undefined;\n}\n\nexport function shardById(plan: WorkloadShardPlan, shardId: string): WorkloadShardSpec {\n\tconst shard = plan.shards.find((candidate) => candidate.shardId === shardId);\n\tif (shard === undefined) {\n\t\tthrow new Error(`shard ${shardId} not in plan ${plan.planId}`);\n\t}\n\treturn shard;\n}\n"]}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { WorkloadPermitError } from "./workload-permit-pool.js";
|
|
2
|
+
export async function runOneShard(input, projections, shardId, now) {
|
|
3
|
+
const shard = shardById(input.plan, shardId);
|
|
4
|
+
let releasePermit;
|
|
5
|
+
if (input.permitPool) {
|
|
6
|
+
try {
|
|
7
|
+
const permit = await input.permitPool.acquire({
|
|
8
|
+
requestId: `shard-${input.plan.planId}-${shardId}`,
|
|
9
|
+
promptRunId: input.plan.promptRunId,
|
|
10
|
+
workloadClass: "heavy",
|
|
11
|
+
weight: 1,
|
|
12
|
+
signal: input.signal,
|
|
13
|
+
timeoutMs: input.permitWaitTimeoutMs ?? 120_000,
|
|
14
|
+
});
|
|
15
|
+
releasePermit = () => permit.release();
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const aborted = error instanceof WorkloadPermitError && error.code === "aborted";
|
|
19
|
+
// Never ran: stays pending; an aborted wait is not a shard failure.
|
|
20
|
+
if (!aborted) {
|
|
21
|
+
applyTransition(input, projections, shardId, "running", { now });
|
|
22
|
+
applyTransition(input, projections, shardId, "failed", { reasonCode: "permit.unavailable", now });
|
|
23
|
+
}
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const startedAt = now().toISOString();
|
|
28
|
+
applyTransition(input, projections, shardId, "running", { now, startedAt });
|
|
29
|
+
try {
|
|
30
|
+
const result = await input.runner({
|
|
31
|
+
shard,
|
|
32
|
+
attempt: projections.get(shardId)?.attempt ?? 1,
|
|
33
|
+
signal: input.signal,
|
|
34
|
+
});
|
|
35
|
+
const state = terminalStateFor(input.signal, result.exitCode);
|
|
36
|
+
applyTransition(input, projections, shardId, state, {
|
|
37
|
+
now,
|
|
38
|
+
startedAt,
|
|
39
|
+
evidenceRefs: [`exit-code:${result.exitCode}`, ...(result.evidenceRefs ?? [])],
|
|
40
|
+
reasonCode: terminalReasonFor(state, result.exitCode),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
const state = input.signal?.aborted ? "aborted" : "failed";
|
|
45
|
+
applyTransition(input, projections, shardId, state, {
|
|
46
|
+
now,
|
|
47
|
+
startedAt,
|
|
48
|
+
reasonCode: state === "aborted" ? "run.aborted" : "runner.error",
|
|
49
|
+
evidenceRefs: [`error:${String(error).slice(0, 120)}`],
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
releasePermit?.();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function applyTransition(input, projections, shardId, state, options) {
|
|
57
|
+
const current = projections.get(shardId);
|
|
58
|
+
if (current === undefined)
|
|
59
|
+
return;
|
|
60
|
+
const attempt = state === "running" ? current.attempt + 1 : current.attempt;
|
|
61
|
+
const terminal = state !== "pending" && state !== "running";
|
|
62
|
+
input.store.appendTransition({
|
|
63
|
+
schemaVersion: 1,
|
|
64
|
+
planId: input.plan.planId,
|
|
65
|
+
shardId,
|
|
66
|
+
attempt,
|
|
67
|
+
state,
|
|
68
|
+
...(options.startedAt !== undefined && state === "running" ? { startedAt: options.startedAt } : {}),
|
|
69
|
+
...(terminal ? { finishedAt: options.now().toISOString() } : {}),
|
|
70
|
+
evidenceRefs: options.evidenceRefs ?? [],
|
|
71
|
+
...(options.reasonCode !== undefined ? { reasonCode: options.reasonCode } : {}),
|
|
72
|
+
});
|
|
73
|
+
projections.set(shardId, {
|
|
74
|
+
shardId,
|
|
75
|
+
state,
|
|
76
|
+
attempt,
|
|
77
|
+
evidenceRefs: options.evidenceRefs?.length ? options.evidenceRefs : current.evidenceRefs,
|
|
78
|
+
reasonCode: options.reasonCode ?? (state === "pending" ? undefined : current.reasonCode),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function terminalStateFor(signal, exitCode) {
|
|
82
|
+
if (signal?.aborted)
|
|
83
|
+
return "aborted";
|
|
84
|
+
return exitCode === 0 ? "passed" : "failed";
|
|
85
|
+
}
|
|
86
|
+
function terminalReasonFor(state, exitCode) {
|
|
87
|
+
if (state === "failed")
|
|
88
|
+
return `exit.${exitCode}`;
|
|
89
|
+
return state === "aborted" ? "run.aborted" : undefined;
|
|
90
|
+
}
|
|
91
|
+
export function shardById(plan, shardId) {
|
|
92
|
+
const shard = plan.shards.find((candidate) => candidate.shardId === shardId);
|
|
93
|
+
if (shard === undefined) {
|
|
94
|
+
throw new Error(`shard ${shardId} not in plan ${plan.planId}`);
|
|
95
|
+
}
|
|
96
|
+
return shard;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=workload-shard-runner.js.map
|