@kontextmind/kxm 0.7.40 → 0.7.44
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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +45 -7
- package/docs/README.md +5 -3
- package/docs/agent-skills.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/configuration.md +28 -5
- package/docs/{vnext → contracts}/README.md +7 -7
- package/docs/{vnext → contracts}/architecture.md +2 -2
- package/docs/{vnext → contracts}/migration.md +25 -25
- package/docs/{vnext → contracts}/routing.md +2 -2
- package/docs/{vnext → contracts}/synchronization.md +1 -1
- package/docs/{vnext → contracts}/validation.md +7 -7
- package/docs/getting-started.md +1 -1
- package/docs/kb/qa-authentik-authentication.md +47 -0
- package/docs/kb/qa-extension-install-and-hub-bootstrap.md +68 -0
- package/docs/kb/qa-hub-on-a-public-host.md +31 -0
- package/docs/kb/qa-sqlite-vs-duckdb.md +18 -0
- package/docs/kb/qa-what-the-hub-stores.md +47 -0
- package/docs/packages.md +91 -0
- package/docs/templates/architecture.md +1 -1
- package/docs/test-matrix.md +6 -6
- package/docs/tui-components.md +131 -0
- package/examples/README.md +2 -2
- package/examples/{vnext → project}/.kxm/agents/critic-3.yaml +1 -1
- package/examples/{vnext → project}/README.md +4 -4
- package/package.json +18 -8
- package/packages/core/tui/CHANGELOG.md +19 -0
- package/packages/core/tui/LICENSE +21 -0
- package/packages/core/tui/README.md +45 -0
- package/packages/core/tui/dist/index.js +1439 -0
- package/packages/core/tui/src/adapters/pi.ts +73 -0
- package/packages/core/tui/src/adapters/terminal.ts +106 -0
- package/packages/core/tui/src/exports/index.ts +30 -0
- package/packages/core/tui/src/services/registry.ts +274 -0
- package/packages/core/tui/src/tui/keys.ts +130 -0
- package/packages/core/tui/src/tui/layout.ts +117 -0
- package/packages/core/tui/src/tui/panel.ts +435 -0
- package/packages/core/tui/src/tui/panelComponent.ts +208 -0
- package/packages/core/tui/src/tui/render.ts +309 -0
- package/packages/core/tui/src/tui/theme.ts +145 -0
- package/packages/core/tui/src/types/surface.ts +407 -0
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +681 -553
- package/plugins/kxm/dist/extension.js +18 -18
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/{vnext-runtime-supervisor.js → runtime-supervisor.js} +553 -553
- package/plugins/kxm/dist/runtime.js +608 -608
- package/plugins/kxm/dist/server.js +13 -13
- package/plugins/kxm/package.json +2 -2
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +2 -2
- package/plugins/kxm/skills/kxm-runs/SKILL.md +3 -3
- package/plugins/kxm/skills/kxm-session/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-workflow/SKILL.md +1 -1
- package/plugins/kxm/src/autocomplete.ts +4 -4
- package/plugins/kxm/src/{vnext-bindings.ts → bindings.ts} +48 -48
- package/plugins/kxm/src/cli/hub.ts +3 -3
- package/plugins/kxm/src/cli/{vnext.ts → project.ts} +106 -106
- package/plugins/kxm/src/cli/roles.ts +9 -9
- package/plugins/kxm/src/cli/system.ts +2 -2
- package/plugins/kxm/src/cli/tasks.ts +8 -8
- package/plugins/kxm/src/cli/workflows.ts +8 -8
- package/plugins/kxm/src/cli.ts +47 -47
- package/plugins/kxm/src/config.ts +69 -14
- package/plugins/kxm/src/database.ts +4 -4
- package/plugins/kxm/src/{vnext-engine-artifacts.ts → engine-artifacts.ts} +7 -7
- package/plugins/kxm/src/{vnext-engine-command.ts → engine-command.ts} +40 -40
- package/plugins/kxm/src/{vnext-engine-compile.ts → engine-compile.ts} +64 -64
- package/plugins/kxm/src/{vnext-engine-evidence.ts → engine-evidence.ts} +26 -26
- package/plugins/kxm/src/{vnext-engine-fold.ts → engine-fold.ts} +99 -99
- package/plugins/kxm/src/{vnext-engine-gate-records.ts → engine-gate-records.ts} +105 -105
- package/plugins/kxm/src/{vnext-engine-plan.ts → engine-plan.ts} +69 -69
- package/plugins/kxm/src/{vnext-engine.ts → engine.ts} +502 -502
- package/plugins/kxm/src/gate-hash.ts +10 -0
- package/plugins/kxm/src/{vnext-harness.ts → harness.ts} +1 -1
- package/plugins/kxm/src/hub.ts +1 -1
- package/plugins/kxm/src/init-guide-setup.ts +3 -3
- package/plugins/kxm/src/{vnext-init.ts → init.ts} +93 -93
- package/plugins/kxm/src/kxm-install-kind.ts +1 -1
- package/plugins/kxm/src/kxm-update-config.ts +2 -2
- package/plugins/kxm/src/local-snapshot.ts +23 -23
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/{vnext-migrate.ts → migrate.ts} +123 -123
- package/plugins/kxm/src/model-inventory.ts +1 -1
- package/plugins/kxm/src/{vnext-oneshot-evidence.ts → oneshot-evidence.ts} +2 -2
- package/plugins/kxm/src/{vnext-oneshot-process.ts → oneshot-process.ts} +4 -4
- package/plugins/kxm/src/{vnext-oneshot-producer.ts → oneshot-producer.ts} +24 -24
- package/plugins/kxm/src/{vnext-permission.ts → permission.ts} +86 -86
- package/plugins/kxm/src/{vnext-pi-producer.ts → pi-producer.ts} +13 -13
- package/plugins/kxm/src/{vnext-config.ts → project-config.ts} +177 -177
- package/plugins/kxm/src/{vnext-repair.ts → repair.ts} +159 -159
- package/plugins/kxm/src/restricted-yaml.d.mts +3 -3
- package/plugins/kxm/src/restricted-yaml.mjs +4 -4
- package/plugins/kxm/src/{vnext-runtime-owner.ts → runtime-owner.ts} +35 -35
- package/plugins/kxm/src/{vnext-runtime.ts → runtime-service.ts} +136 -136
- package/plugins/kxm/src/{vnext-runtime-store.ts → runtime-store.ts} +145 -145
- package/plugins/kxm/src/{vnext-runtime-supervisor.ts → runtime-supervisor.ts} +80 -80
- package/plugins/kxm/src/runtime.ts +6 -6
- package/plugins/kxm/src/session-work.ts +3 -3
- package/plugins/kxm/src/studio-layout.ts +6 -6
- package/plugins/kxm/src/{vnext-template.ts → template.ts} +33 -33
- package/plugins/kxm/src/tui.ts +10 -24
- package/schemas/{vnext/README.md → README.md} +3 -3
- package/schemas/{vnext/agent.schema.json → agent.schema.json} +1 -1
- package/schemas/{vnext/assignment-result.schema.json → assignment-result.schema.json} +1 -1
- package/schemas/{vnext/backup-manifest.schema.json → backup-manifest.schema.json} +1 -1
- package/schemas/{vnext/candidate.schema.json → candidate.schema.json} +1 -1
- package/schemas/{vnext/common.schema.json → common.schema.json} +2 -2
- package/schemas/{vnext/context-candidate.schema.json → context-candidate.schema.json} +1 -1
- package/schemas/{vnext/context-packet.schema.json → context-packet.schema.json} +1 -1
- package/schemas/{vnext/delivery-manifest.schema.json → delivery-manifest.schema.json} +1 -1
- package/schemas/{vnext/drive-receipt.schema.json → drive-receipt.schema.json} +2 -2
- package/schemas/{vnext/environment.schema.json → environment.schema.json} +1 -1
- package/schemas/{vnext/gate-registry.schema.json → gate-registry.schema.json} +1 -1
- package/schemas/{vnext/handoff-manifest.schema.json → handoff-manifest.schema.json} +1 -1
- package/schemas/{vnext/init-operation.schema.json → init-operation.schema.json} +1 -1
- package/schemas/{vnext/local-repository-bindings.schema.json → local-repository-bindings.schema.json} +1 -1
- package/schemas/{vnext/memory-record.schema.json → memory-record.schema.json} +1 -1
- package/schemas/{vnext/migration-decision.schema.json → migration-decision.schema.json} +1 -1
- package/schemas/{vnext/migration-plan.schema.json → migration-plan.schema.json} +1 -1
- package/schemas/{vnext/migration-receipt.schema.json → migration-receipt.schema.json} +1 -1
- package/schemas/{vnext/model.schema.json → model.schema.json} +1 -1
- package/schemas/{vnext/modes.schema.json → modes.schema.json} +1 -1
- package/schemas/{vnext/permission-diff.schema.json → permission-diff.schema.json} +1 -1
- package/schemas/policy-draft/README.md +2 -2
- package/schemas/{vnext/prices.schema.json → prices.schema.json} +1 -1
- package/schemas/{vnext/project.schema.json → project.schema.json} +1 -1
- package/schemas/{vnext/repository.schema.json → repository.schema.json} +1 -1
- package/schemas/{vnext/role.schema.json → role.schema.json} +1 -1
- package/schemas/{vnext/run-event.schema.json → run-event.schema.json} +1 -1
- package/schemas/{vnext/session-brief.schema.json → session-brief.schema.json} +2 -2
- package/schemas/{vnext/sync-event.schema.json → sync-event.schema.json} +1 -1
- package/schemas/{vnext/template-provenance.schema.json → template-provenance.schema.json} +1 -1
- package/schemas/{vnext/workflow.schema.json → workflow.schema.json} +1 -1
- package/scripts/build-package.mjs +59 -0
- package/scripts/build-runtime.mjs +3 -2
- package/scripts/check-generated.mjs +2 -1
- package/scripts/check-versions.mjs +12 -1
- package/scripts/docker-install-smoke.mjs +268 -0
- package/scripts/emit-codex-artifacts.mjs +1 -1
- package/scripts/kxm-bump-version.mjs +53 -3
- package/scripts/kxm-hub.mjs +36 -0
- package/scripts/kxm-runtime-supervisor.mjs +3 -3
- package/scripts/package-surfaces.mjs +39 -0
- package/plugins/kxm/src/vnext-gate-hash.ts +0 -10
- /package/docs/{vnext → contracts}/effects-and-recovery.md +0 -0
- /package/docs/{vnext → contracts}/lifecycles.md +0 -0
- /package/docs/{vnext → contracts}/terminology.md +0 -0
- /package/examples/{vnext → project}/.kxm/agents/coordinator.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/critic-1.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/critic-2.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/implementer.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/planner.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/reproducer.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/reviewer.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/gates.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/models/critic-claude.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/models/critic-gemini.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/models/critic-grok.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/models/implementation.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/models/primary.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/prices.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/project/env.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/project.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/repo/repo.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/workflows/default.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/workflows/fix.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/workflows/improve.yaml +0 -0
- /package/examples/{vnext → project}/records/assignment-result-recorded.json +0 -0
- /package/examples/{vnext → project}/records/assignment-result.json +0 -0
- /package/examples/{vnext → project}/records/context-candidate.json +0 -0
- /package/examples/{vnext → project}/records/delivery-manifest.json +0 -0
- /package/examples/{vnext → project}/records/effect-uncertainty-resolved-sync.json +0 -0
- /package/examples/{vnext → project}/records/effect-uncertainty-resolved.json +0 -0
- /package/examples/{vnext → project}/records/run-created.json +0 -0
- /package/examples/{vnext → project}/records/sync-event.json +0 -0
- /package/examples/{vnext → project}/repositories/api/.kxm/repo/env.yaml +0 -0
- /package/examples/{vnext → project}/repositories/api/.kxm/repo/repo.yaml +0 -0
- /package/examples/{vnext → project}/repositories/web/.kxm/repo/repo.yaml +0 -0
|
@@ -11,85 +11,85 @@ import {
|
|
|
11
11
|
type FormalContextPacketV2,
|
|
12
12
|
type HandoffManifestV1,
|
|
13
13
|
} from "./context-packet.ts";
|
|
14
|
-
import {
|
|
14
|
+
import { compileKxmWorkflow, type KxmCompiledPlan, type KxmCompiledStep } from "./engine-compile.ts";
|
|
15
15
|
import {
|
|
16
16
|
effectiveRunDurationBudget,
|
|
17
17
|
isTerminalRunStatus,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
type
|
|
22
|
-
type
|
|
23
|
-
} from "./
|
|
18
|
+
kxmFoldPanelAttempt,
|
|
19
|
+
kxmFoldPanelAttemptIds,
|
|
20
|
+
kxmJoinAll,
|
|
21
|
+
type KxmRunDurationBudget,
|
|
22
|
+
type KxmRunState,
|
|
23
|
+
} from "./engine-fold.ts";
|
|
24
24
|
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
KXM_RUN_PLAN_SCHEMA,
|
|
26
|
+
freezeKxmCompiledPlan,
|
|
27
|
+
hashKxmRunPlanEnvelope,
|
|
28
|
+
loadKxmRunPlanEnvelope,
|
|
29
29
|
parseGateDefinition,
|
|
30
|
-
|
|
31
|
-
type
|
|
32
|
-
type
|
|
33
|
-
} from "./
|
|
34
|
-
import { gateRegistryHash } from "./
|
|
30
|
+
rehydrateKxmCompiledPlanFromStore,
|
|
31
|
+
type KxmPinnedGates,
|
|
32
|
+
type KxmRunPlanEnvelope,
|
|
33
|
+
} from "./engine-plan.ts";
|
|
34
|
+
import { gateRegistryHash } from "./gate-hash.ts";
|
|
35
35
|
import {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
type
|
|
55
|
-
} from "./
|
|
36
|
+
admitKxmRun,
|
|
37
|
+
armKxmGateHold,
|
|
38
|
+
attachKxmDriveSession,
|
|
39
|
+
bindKxmSchedulerPolicy,
|
|
40
|
+
clearKxmDriveSession,
|
|
41
|
+
dropKxmGateStopHook,
|
|
42
|
+
enqueueKxmScheduledRun,
|
|
43
|
+
finishKxmOwnedGate,
|
|
44
|
+
markKxmGateHoldUnsettled,
|
|
45
|
+
registerKxmAttemptController,
|
|
46
|
+
releaseKxmRun,
|
|
47
|
+
resolveKxmGateHold,
|
|
48
|
+
unregisterKxmAttemptController,
|
|
49
|
+
kxmAdmittedToken,
|
|
50
|
+
kxmAttemptController,
|
|
51
|
+
kxmAttemptControllers,
|
|
52
|
+
kxmGateHold,
|
|
53
|
+
kxmSchedulerPolicy,
|
|
54
|
+
type KxmDriveSession,
|
|
55
|
+
} from "./runtime-owner.ts";
|
|
56
56
|
import {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
type
|
|
68
|
-
type
|
|
69
|
-
} from "./
|
|
57
|
+
cancelKxmRun,
|
|
58
|
+
foldStoredKxmRun,
|
|
59
|
+
isKxmRuntimeContextClosed,
|
|
60
|
+
persistKxmRunState,
|
|
61
|
+
registerKxmRuntimeCloseHook,
|
|
62
|
+
kxmEventBase,
|
|
63
|
+
kxmIncrementMonotonicNs,
|
|
64
|
+
kxmMonotonicNs,
|
|
65
|
+
kxmPolicyRevisions,
|
|
66
|
+
kxmProjectAdmissionLimits,
|
|
67
|
+
type KxmMemoryRevisionOptions,
|
|
68
|
+
type KxmRuntimeContext,
|
|
69
|
+
} from "./runtime-service.ts";
|
|
70
70
|
import {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
hashKxmDriveLog,
|
|
72
|
+
newKxmAssignmentId,
|
|
73
|
+
newKxmAttemptId,
|
|
74
|
+
newKxmEventId,
|
|
75
75
|
projectRuntimeKey,
|
|
76
76
|
runtimeError,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
type
|
|
80
|
-
type
|
|
81
|
-
type
|
|
82
|
-
type
|
|
83
|
-
type
|
|
84
|
-
type
|
|
85
|
-
type
|
|
86
|
-
type
|
|
87
|
-
type
|
|
88
|
-
type
|
|
89
|
-
} from "./
|
|
90
|
-
import {
|
|
91
|
-
import { evaluateArtifactsGate } from "./
|
|
92
|
-
import { createCommandObserver } from "./
|
|
77
|
+
verifyKxmDriveReceipt,
|
|
78
|
+
KXM_DRIVE_RECEIPT_SCHEMA,
|
|
79
|
+
type KxmAttemptCapabilityRow,
|
|
80
|
+
type KxmDriveReceipt,
|
|
81
|
+
type KxmDriveReceiptBudget,
|
|
82
|
+
type KxmDriveReceiptHandoff,
|
|
83
|
+
type KxmGateAttemptRow,
|
|
84
|
+
type KxmGateEvidenceRow,
|
|
85
|
+
type KxmGateObservationRow,
|
|
86
|
+
type KxmRunEvent,
|
|
87
|
+
type KxmRunRecord,
|
|
88
|
+
type KxmRunStatus,
|
|
89
|
+
} from "./runtime-store.ts";
|
|
90
|
+
import { KxmConfigError, kxmCanonicalJson, type JsonValue, type KxmProjectBundle } from "./project-config.ts";
|
|
91
|
+
import { evaluateArtifactsGate } from "./engine-artifacts.ts";
|
|
92
|
+
import { createCommandObserver } from "./engine-command.ts";
|
|
93
93
|
import {
|
|
94
94
|
recordGateCancelObservedInTransaction,
|
|
95
95
|
recordGateIntentInTransaction,
|
|
@@ -97,8 +97,8 @@ import {
|
|
|
97
97
|
recordGateSettlementInTransaction,
|
|
98
98
|
recordGateSpawnedInTransaction,
|
|
99
99
|
recordGateUncertainInTransaction,
|
|
100
|
-
type
|
|
101
|
-
} from "./
|
|
100
|
+
type KxmGateObservationInput,
|
|
101
|
+
} from "./engine-gate-records.ts";
|
|
102
102
|
import {
|
|
103
103
|
ROUTING_RECORD_V2_SCHEMA,
|
|
104
104
|
type RoutingRecordV2,
|
|
@@ -124,7 +124,7 @@ export {
|
|
|
124
124
|
const trustedProducers = new WeakSet<object>();
|
|
125
125
|
const CAPABILITY_PREFIX = "kxm-attempt-capability\0";
|
|
126
126
|
|
|
127
|
-
export interface
|
|
127
|
+
export interface KxmProducerRequest {
|
|
128
128
|
readonly runId: string;
|
|
129
129
|
readonly stepId: string;
|
|
130
130
|
readonly stepAttempt: number;
|
|
@@ -146,7 +146,7 @@ export interface VnextProducerRequest {
|
|
|
146
146
|
readonly handoffManifest?: HandoffManifestV1 | undefined;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
export interface
|
|
149
|
+
export interface KxmProducerResult {
|
|
150
150
|
readonly outcome: string;
|
|
151
151
|
/** Host cannot attest termination; retain the issued attempt, never replay or settle it. */
|
|
152
152
|
readonly effectUncertain?: true;
|
|
@@ -169,13 +169,13 @@ export interface VnextProducerResult {
|
|
|
169
169
|
readonly providerMetadata?: Record<string, string | number | boolean>;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
export interface
|
|
172
|
+
export interface KxmProducer {
|
|
173
173
|
readonly id: "driver-simulated" | "pi" | "oneshot";
|
|
174
|
-
produce(request:
|
|
174
|
+
produce(request: KxmProducerRequest): Promise<KxmProducerResult>;
|
|
175
175
|
close?(): Promise<void>;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
export interface
|
|
178
|
+
export interface KxmRunHandoff {
|
|
179
179
|
readonly reason: "limit_unsupported" | "step_unsupported" | "attempt_unreconciled" | "cancel_pending_foreign" | "gate_unsupported" | "gate_outcome_undeclared" | "gate_recovery_pending" | "attempt_unsettled" | "gate_uncertain_blocked";
|
|
180
180
|
readonly field?: string;
|
|
181
181
|
readonly stepId?: string;
|
|
@@ -183,22 +183,22 @@ export interface VnextRunHandoff {
|
|
|
183
183
|
readonly detail: string;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
export interface
|
|
187
|
-
readonly state:
|
|
188
|
-
readonly handoff?:
|
|
186
|
+
export interface KxmRunDriveResult {
|
|
187
|
+
readonly state: KxmRunState;
|
|
188
|
+
readonly handoff?: KxmRunHandoff;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
export interface
|
|
191
|
+
export interface KxmAttemptCapabilityRecord extends KxmAttemptCapabilityRow {
|
|
192
192
|
readonly runPlanHash: string;
|
|
193
193
|
readonly toolPolicyRevision: string;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
export function
|
|
197
|
-
script: (request:
|
|
198
|
-
):
|
|
196
|
+
export function createKxmSimulatedProducer(
|
|
197
|
+
script: (request: KxmProducerRequest) => Promise<KxmProducerResult> | KxmProducerResult,
|
|
198
|
+
): KxmProducer {
|
|
199
199
|
const producer = Object.freeze({
|
|
200
200
|
id: "driver-simulated" as const,
|
|
201
|
-
async produce(request:
|
|
201
|
+
async produce(request: KxmProducerRequest): Promise<KxmProducerResult> {
|
|
202
202
|
const start = Date.now();
|
|
203
203
|
try {
|
|
204
204
|
const raw = await script(request);
|
|
@@ -221,11 +221,11 @@ export function createVnextSimulatedProducer(
|
|
|
221
221
|
return producer;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
export function registerTrustedProducer(producer:
|
|
224
|
+
export function registerTrustedProducer(producer: KxmProducer): void {
|
|
225
225
|
trustedProducers.add(producer);
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
function requireTrustedProducer(producer:
|
|
228
|
+
function requireTrustedProducer(producer: KxmProducer): void {
|
|
229
229
|
if (!trustedProducers.has(producer)) {
|
|
230
230
|
throw runtimeError("engine_producer_untrusted", "producer", "producer is not a trusted factory instance");
|
|
231
231
|
}
|
|
@@ -235,21 +235,21 @@ function hashCapabilitySecret(secret: string): string {
|
|
|
235
235
|
return `sha256:${createHash("sha256").update(`${CAPABILITY_PREFIX}${secret}`, "utf8").digest("hex")}`;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
export function
|
|
238
|
+
export function mintKxmCapabilitySecret(): { secret: string; hash: string } {
|
|
239
239
|
const secret = `kxmcap_${randomBytes(32).toString("base64url")}`;
|
|
240
240
|
return { secret, hash: hashCapabilitySecret(secret) };
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
function mintCapabilitySecret(): { secret: string; hash: string } {
|
|
244
|
-
return
|
|
244
|
+
return mintKxmCapabilitySecret();
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
export function
|
|
248
|
-
context:
|
|
249
|
-
bundle:
|
|
247
|
+
export function pinKxmCompiledPlan(
|
|
248
|
+
context: KxmRuntimeContext,
|
|
249
|
+
bundle: KxmProjectBundle,
|
|
250
250
|
runId: string,
|
|
251
|
-
options?:
|
|
252
|
-
): { plan:
|
|
251
|
+
options?: KxmMemoryRevisionOptions,
|
|
252
|
+
): { plan: KxmCompiledPlan; runPlanHash: string; idempotent: boolean; event?: KxmRunEvent } {
|
|
253
253
|
return context.eventStore.transaction(() => {
|
|
254
254
|
const run = requireRun(context, runId);
|
|
255
255
|
if (run.projectId !== context.projectId || run.homeRuntimeId !== context.homeRuntimeId) {
|
|
@@ -260,7 +260,7 @@ export function pinVnextCompiledPlan(
|
|
|
260
260
|
}
|
|
261
261
|
const workflow = bundle.workflows.get(run.workflowId);
|
|
262
262
|
if (!workflow) throw runtimeError("run_workflow_unknown", run.workflowId, `workflow ${run.workflowId} does not exist in this project`);
|
|
263
|
-
const revisions =
|
|
263
|
+
const revisions = kxmPolicyRevisions(bundle, options);
|
|
264
264
|
if (
|
|
265
265
|
revisions.configRevision !== run.configRevision
|
|
266
266
|
|| revisions.executorPolicyRevision !== run.executorPolicyRevision
|
|
@@ -269,13 +269,13 @@ export function pinVnextCompiledPlan(
|
|
|
269
269
|
) {
|
|
270
270
|
throw runtimeError("run_revision_drift", runId, "bundle revisions do not match the accepted run");
|
|
271
271
|
}
|
|
272
|
-
const compiled =
|
|
272
|
+
const compiled = freezeKxmCompiledPlan(compileKxmWorkflow({
|
|
273
273
|
id: run.workflowId,
|
|
274
274
|
value: workflow.value,
|
|
275
275
|
...(workflow.logicalPath !== undefined ? { logicalPath: workflow.logicalPath } : {}),
|
|
276
276
|
}));
|
|
277
|
-
const envelope:
|
|
278
|
-
schema:
|
|
277
|
+
const envelope: KxmRunPlanEnvelope = {
|
|
278
|
+
schema: KXM_RUN_PLAN_SCHEMA,
|
|
279
279
|
runId: run.runId,
|
|
280
280
|
projectId: run.projectId,
|
|
281
281
|
homeRuntimeId: run.homeRuntimeId,
|
|
@@ -286,11 +286,11 @@ export function pinVnextCompiledPlan(
|
|
|
286
286
|
toolPolicy: run.toolPolicyRevision,
|
|
287
287
|
memory: run.memoryRevision,
|
|
288
288
|
},
|
|
289
|
-
projectLimits:
|
|
289
|
+
projectLimits: kxmProjectAdmissionLimits(bundle),
|
|
290
290
|
plan: compiled,
|
|
291
291
|
gates: pinnedGatesForCompiledPlan(compiled, bundle, context.projectRoot),
|
|
292
292
|
};
|
|
293
|
-
const runPlanHash =
|
|
293
|
+
const runPlanHash = hashKxmRunPlanEnvelope(envelope);
|
|
294
294
|
const existing = context.eventStore.runPlan(runId);
|
|
295
295
|
if (existing) {
|
|
296
296
|
if (existing.runPlanHash !== runPlanHash) {
|
|
@@ -303,9 +303,9 @@ export function pinVnextCompiledPlan(
|
|
|
303
303
|
}
|
|
304
304
|
const now = new Date().toISOString();
|
|
305
305
|
const sequence = context.eventStore.nextSequence(runId);
|
|
306
|
-
const event:
|
|
307
|
-
...
|
|
308
|
-
eventId:
|
|
306
|
+
const event: KxmRunEvent = {
|
|
307
|
+
...kxmEventBase(context, run, now, kxmMonotonicNs()),
|
|
308
|
+
eventId: newKxmEventId(),
|
|
309
309
|
eventType: "run.status_changed",
|
|
310
310
|
sequence,
|
|
311
311
|
payload: { status: "preparing", runPlanHash },
|
|
@@ -313,28 +313,28 @@ export function pinVnextCompiledPlan(
|
|
|
313
313
|
context.eventStore.insertRunPlan({
|
|
314
314
|
runId,
|
|
315
315
|
runPlanHash,
|
|
316
|
-
envelope:
|
|
316
|
+
envelope: kxmCanonicalJson(envelope as unknown as JsonValue),
|
|
317
317
|
pinnedSequence: sequence,
|
|
318
318
|
});
|
|
319
319
|
context.eventStore.appendEvent(event);
|
|
320
|
-
const state =
|
|
321
|
-
|
|
320
|
+
const state = foldStoredKxmRun(context, run);
|
|
321
|
+
persistKxmRunState(context, runId, state, sequence);
|
|
322
322
|
context.eventStore.updateRunStatus(runId, "preparing", now);
|
|
323
323
|
return { plan: compiled, runPlanHash, idempotent: false, event };
|
|
324
324
|
});
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
export function
|
|
327
|
+
export function rehydrateKxmCompiledPlan(context: KxmRuntimeContext, runId: string): KxmCompiledPlan {
|
|
328
328
|
const run = requireRun(context, runId);
|
|
329
|
-
return
|
|
329
|
+
return rehydrateKxmCompiledPlanFromStore(context.eventStore, run);
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
-
export function
|
|
332
|
+
export function startKxmRun(context: KxmRuntimeContext, runId: string, options: { allowLimits?: boolean } = {}): KxmRunDriveResult {
|
|
333
333
|
return context.eventStore.transaction(() => {
|
|
334
334
|
const run = requireRun(context, runId);
|
|
335
|
-
const envelope =
|
|
335
|
+
const envelope = loadKxmRunPlanEnvelope(context.eventStore, run);
|
|
336
336
|
const unsupported = options.allowLimits ? undefined : unsupportedLimit(envelope);
|
|
337
|
-
const state =
|
|
337
|
+
const state = foldStoredKxmRun(context, run);
|
|
338
338
|
if (unsupported) {
|
|
339
339
|
return { state, handoff: unsupported };
|
|
340
340
|
}
|
|
@@ -343,32 +343,32 @@ export function startVnextRun(context: VnextRuntimeContext, runId: string, optio
|
|
|
343
343
|
}
|
|
344
344
|
const now = new Date().toISOString();
|
|
345
345
|
const sequence = context.eventStore.nextSequence(runId);
|
|
346
|
-
const event:
|
|
347
|
-
...
|
|
348
|
-
eventId:
|
|
346
|
+
const event: KxmRunEvent = {
|
|
347
|
+
...kxmEventBase(context, run, now, kxmMonotonicNs()),
|
|
348
|
+
eventId: newKxmEventId(),
|
|
349
349
|
eventType: "run.status_changed",
|
|
350
350
|
sequence,
|
|
351
351
|
payload: { status: "running" },
|
|
352
352
|
};
|
|
353
353
|
context.eventStore.appendEvent(event);
|
|
354
|
-
const next =
|
|
355
|
-
|
|
354
|
+
const next = foldStoredKxmRun(context, run);
|
|
355
|
+
persistKxmRunState(context, runId, next, sequence);
|
|
356
356
|
context.eventStore.updateRunStatus(runId, "running", now);
|
|
357
357
|
return { state: next };
|
|
358
358
|
});
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
-
export async function
|
|
361
|
+
export async function stepKxmRun(context: KxmRuntimeContext, runId: string, producer: KxmProducer): Promise<KxmRunDriveResult> {
|
|
362
362
|
requireTrustedProducer(producer);
|
|
363
363
|
return withAdmission(context, runId, (token) => stepLocked(context, runId, producer, token));
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
-
export async function
|
|
367
|
-
context:
|
|
366
|
+
export async function driveKxmRun(
|
|
367
|
+
context: KxmRuntimeContext,
|
|
368
368
|
runId: string,
|
|
369
|
-
producer:
|
|
369
|
+
producer: KxmProducer,
|
|
370
370
|
options: { allowLimits?: boolean; liveMode?: boolean } = {},
|
|
371
|
-
): Promise<
|
|
371
|
+
): Promise<KxmRunDriveResult> {
|
|
372
372
|
requireTrustedProducer(producer);
|
|
373
373
|
const run = requireRun(context, runId);
|
|
374
374
|
if (run.status === "created") {
|
|
@@ -378,18 +378,18 @@ export async function driveVnextRun(
|
|
|
378
378
|
}
|
|
379
379
|
|
|
380
380
|
async function driveAdmitted(
|
|
381
|
-
context:
|
|
381
|
+
context: KxmRuntimeContext,
|
|
382
382
|
runId: string,
|
|
383
|
-
producer:
|
|
383
|
+
producer: KxmProducer,
|
|
384
384
|
token: string,
|
|
385
385
|
options: { allowLimits?: boolean; liveMode?: boolean } = {},
|
|
386
|
-
): Promise<
|
|
386
|
+
): Promise<KxmRunDriveResult> {
|
|
387
387
|
const run = requireRun(context, runId);
|
|
388
388
|
if (run.status === "preparing") {
|
|
389
|
-
const started =
|
|
389
|
+
const started = startKxmRun(context, runId, options);
|
|
390
390
|
if (started.handoff || isTerminalRunStatus(started.state.status)) return started;
|
|
391
391
|
}
|
|
392
|
-
let latest:
|
|
392
|
+
let latest: KxmRunDriveResult = { state: foldStoredKxmRun(context, requireRun(context, runId)) };
|
|
393
393
|
while (latest.state.status === "running") {
|
|
394
394
|
const overBudget = cancelRunDurationIfExceeded(context, runId);
|
|
395
395
|
if (overBudget) return overBudget;
|
|
@@ -406,19 +406,19 @@ async function driveAdmitted(
|
|
|
406
406
|
return latest;
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
-
async function withAdmission<T>(context:
|
|
409
|
+
async function withAdmission<T>(context: KxmRuntimeContext, runId: string, fn: (token: string) => Promise<T>): Promise<T> {
|
|
410
410
|
const token = admitPinnedRun(context, runId);
|
|
411
411
|
try {
|
|
412
412
|
return await fn(token);
|
|
413
413
|
} finally {
|
|
414
|
-
|
|
414
|
+
releaseKxmRun(context.eventStore.path, runId, token);
|
|
415
415
|
}
|
|
416
416
|
}
|
|
417
417
|
|
|
418
|
-
function admitPinnedRun(context:
|
|
418
|
+
function admitPinnedRun(context: KxmRuntimeContext, runId: string): string {
|
|
419
419
|
const run = requireRun(context, runId);
|
|
420
|
-
const envelope =
|
|
421
|
-
return
|
|
420
|
+
const envelope = loadKxmRunPlanEnvelope(context.eventStore, run);
|
|
421
|
+
return admitKxmRun(
|
|
422
422
|
context.eventStore.path,
|
|
423
423
|
runId,
|
|
424
424
|
envelope.revisions.config,
|
|
@@ -426,47 +426,47 @@ function admitPinnedRun(context: VnextRuntimeContext, runId: string): string {
|
|
|
426
426
|
);
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
-
export interface
|
|
429
|
+
export interface OpenKxmDriveSessionOptions {
|
|
430
430
|
mode: "simulated" | "live";
|
|
431
431
|
allowLimits?: boolean;
|
|
432
432
|
liveMode?: boolean;
|
|
433
|
-
createProducer: () =>
|
|
433
|
+
createProducer: () => KxmProducer;
|
|
434
434
|
}
|
|
435
435
|
|
|
436
|
-
export interface
|
|
436
|
+
export interface KxmDriveSessionHandle {
|
|
437
437
|
driveId: string;
|
|
438
|
-
settled: Promise<
|
|
438
|
+
settled: Promise<KxmRunDriveResult>;
|
|
439
439
|
}
|
|
440
440
|
|
|
441
441
|
function newDriveId(runId: string, token: string, runtimeId: string, monotonicNs: string): string {
|
|
442
442
|
return `drv_${createHash("sha256").update(`${runId}\0${token}\0${runtimeId}\0${monotonicNs}`, "utf8").digest("hex").slice(0, 24)}`;
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
-
export interface
|
|
445
|
+
export interface KxmDrivePollProjection {
|
|
446
446
|
driveId: string;
|
|
447
447
|
mode: "simulated" | "live";
|
|
448
448
|
openedAt: string;
|
|
449
|
-
receipt:
|
|
449
|
+
receipt: KxmDriveReceipt | null;
|
|
450
450
|
verified: boolean;
|
|
451
451
|
divergence?: string;
|
|
452
452
|
}
|
|
453
453
|
|
|
454
|
-
export interface
|
|
454
|
+
export interface KxmDriveReceiptCloseInfo {
|
|
455
455
|
kind: "terminal" | "handoff" | "unsettled";
|
|
456
456
|
reason?: string;
|
|
457
|
-
handoff?:
|
|
457
|
+
handoff?: KxmRunHandoff;
|
|
458
458
|
error?: { class: string; component: string; retryable: boolean };
|
|
459
459
|
producerId: string;
|
|
460
460
|
producerClosed: boolean;
|
|
461
461
|
}
|
|
462
462
|
|
|
463
463
|
function driveErrorSummary(error: unknown): { class: string; component: string; retryable: boolean } {
|
|
464
|
-
const raw = error instanceof
|
|
464
|
+
const raw = error instanceof KxmConfigError ? error.issues[0]?.code : undefined;
|
|
465
465
|
const className = raw && /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/.test(raw) ? raw : "drive-failure";
|
|
466
|
-
return { class: className, component: "
|
|
466
|
+
return { class: className, component: "engine", retryable: false };
|
|
467
467
|
}
|
|
468
468
|
|
|
469
|
-
function receiptHandoff(handoff:
|
|
469
|
+
function receiptHandoff(handoff: KxmRunHandoff): KxmDriveReceiptHandoff {
|
|
470
470
|
return {
|
|
471
471
|
reason: handoff.reason,
|
|
472
472
|
detail: handoff.detail,
|
|
@@ -476,7 +476,7 @@ function receiptHandoff(handoff: VnextRunHandoff): VnextDriveReceiptHandoff {
|
|
|
476
476
|
}
|
|
477
477
|
|
|
478
478
|
function appendDriveOpened(
|
|
479
|
-
context:
|
|
479
|
+
context: KxmRuntimeContext,
|
|
480
480
|
runId: string,
|
|
481
481
|
driveId: string,
|
|
482
482
|
mode: "simulated" | "live",
|
|
@@ -485,30 +485,30 @@ function appendDriveOpened(
|
|
|
485
485
|
const run = requireRun(context, runId);
|
|
486
486
|
const now = new Date().toISOString();
|
|
487
487
|
const sequence = context.eventStore.nextSequence(runId);
|
|
488
|
-
const event:
|
|
489
|
-
...
|
|
490
|
-
eventId:
|
|
488
|
+
const event: KxmRunEvent = {
|
|
489
|
+
...kxmEventBase(context, run, now, kxmMonotonicNs()),
|
|
490
|
+
eventId: newKxmEventId(),
|
|
491
491
|
eventType: "run.drive_opened",
|
|
492
492
|
sequence,
|
|
493
493
|
payload: { driveId, mode },
|
|
494
494
|
};
|
|
495
495
|
context.eventStore.appendEvent(event);
|
|
496
|
-
const next =
|
|
497
|
-
|
|
496
|
+
const next = foldStoredKxmRun(context, run);
|
|
497
|
+
persistKxmRunState(context, runId, next, sequence);
|
|
498
498
|
return { sequence, occurredAt: now };
|
|
499
499
|
});
|
|
500
500
|
}
|
|
501
501
|
|
|
502
502
|
export function recordDriveReceipt(
|
|
503
|
-
context:
|
|
504
|
-
session:
|
|
505
|
-
closeInfo:
|
|
503
|
+
context: KxmRuntimeContext,
|
|
504
|
+
session: KxmDriveSession,
|
|
505
|
+
closeInfo: KxmDriveReceiptCloseInfo,
|
|
506
506
|
): void {
|
|
507
|
-
if (
|
|
507
|
+
if (isKxmRuntimeContextClosed(context)) return;
|
|
508
508
|
try {
|
|
509
509
|
const run = context.eventStore.run(session.runId);
|
|
510
510
|
if (!run) return;
|
|
511
|
-
const state =
|
|
511
|
+
const state = foldStoredKxmRun(context, run);
|
|
512
512
|
const events = context.eventStore.events(session.runId, 0, 1_000_000);
|
|
513
513
|
if (events.length === 0) return;
|
|
514
514
|
const opened = state.drive;
|
|
@@ -519,8 +519,8 @@ export function recordDriveReceipt(
|
|
|
519
519
|
const openedSequence = opened?.openedSequence ?? openedEvent?.sequence;
|
|
520
520
|
if (openedSequence === undefined) return;
|
|
521
521
|
const closedAt = new Date().toISOString();
|
|
522
|
-
const receipt:
|
|
523
|
-
schema:
|
|
522
|
+
const receipt: KxmDriveReceipt = {
|
|
523
|
+
schema: KXM_DRIVE_RECEIPT_SCHEMA,
|
|
524
524
|
driveId: session.driveId,
|
|
525
525
|
runId: session.runId,
|
|
526
526
|
projectId: run.projectId,
|
|
@@ -530,7 +530,7 @@ export function recordDriveReceipt(
|
|
|
530
530
|
openedSequence,
|
|
531
531
|
closedAt,
|
|
532
532
|
lastSequence: last.sequence,
|
|
533
|
-
logHash:
|
|
533
|
+
logHash: hashKxmDriveLog(events.filter((event) => event.sequence >= 1 && event.sequence <= last.sequence)),
|
|
534
534
|
settlement: {
|
|
535
535
|
kind: closeInfo.kind,
|
|
536
536
|
status: state.status,
|
|
@@ -547,22 +547,22 @@ export function recordDriveReceipt(
|
|
|
547
547
|
}
|
|
548
548
|
}
|
|
549
549
|
|
|
550
|
-
export function
|
|
551
|
-
context:
|
|
550
|
+
export function kxmDrivePollProjection(
|
|
551
|
+
context: KxmRuntimeContext,
|
|
552
552
|
runId: string,
|
|
553
|
-
state:
|
|
554
|
-
):
|
|
553
|
+
state: KxmRunState,
|
|
554
|
+
): KxmDrivePollProjection | undefined {
|
|
555
555
|
if (!state.drive) return undefined;
|
|
556
556
|
const events = context.eventStore.events(runId, 0, 1_000_000);
|
|
557
557
|
const openedEvent = events.find((event) => event.sequence === state.drive!.openedSequence);
|
|
558
|
-
let receipt:
|
|
558
|
+
let receipt: KxmDriveReceipt | null = null;
|
|
559
559
|
let unreadable = false;
|
|
560
560
|
try {
|
|
561
561
|
receipt = context.eventStore.driveReceipt(state.drive.driveId) ?? null;
|
|
562
562
|
} catch {
|
|
563
563
|
unreadable = true;
|
|
564
564
|
}
|
|
565
|
-
const projection:
|
|
565
|
+
const projection: KxmDrivePollProjection = {
|
|
566
566
|
driveId: state.drive.driveId,
|
|
567
567
|
mode: state.drive.mode,
|
|
568
568
|
openedAt: openedEvent?.occurredAt ?? "",
|
|
@@ -577,7 +577,7 @@ export function vnextDrivePollProjection(
|
|
|
577
577
|
projection.divergence = "no receipt";
|
|
578
578
|
return projection;
|
|
579
579
|
}
|
|
580
|
-
const checked =
|
|
580
|
+
const checked = verifyKxmDriveReceipt(receipt, events, state.status, {
|
|
581
581
|
runId,
|
|
582
582
|
driveId: state.drive.driveId,
|
|
583
583
|
});
|
|
@@ -586,50 +586,50 @@ export function vnextDrivePollProjection(
|
|
|
586
586
|
return projection;
|
|
587
587
|
}
|
|
588
588
|
|
|
589
|
-
function inflightAttemptId(state:
|
|
589
|
+
function inflightAttemptId(state: KxmRunState): string | undefined {
|
|
590
590
|
const leftover = unreconciledPanelAttemptId(state);
|
|
591
591
|
if (leftover) return leftover;
|
|
592
592
|
if (state.currentStep?.attemptId) {
|
|
593
|
-
const located =
|
|
593
|
+
const located = kxmFoldPanelAttempt(state.currentStep, state.currentStep.attemptId);
|
|
594
594
|
const status = located?.attempt.status;
|
|
595
595
|
if (status === "starting" || status === "executing" || status === "settling") return state.currentStep.attemptId;
|
|
596
596
|
}
|
|
597
597
|
return undefined;
|
|
598
598
|
}
|
|
599
599
|
|
|
600
|
-
function recordExecutingUnrecordedFailure(context:
|
|
600
|
+
function recordExecutingUnrecordedFailure(context: KxmRuntimeContext, runId: string, attemptId: string, stepId: string): void {
|
|
601
601
|
context.eventStore.transaction(() => {
|
|
602
602
|
const run = requireRun(context, runId);
|
|
603
603
|
const now = new Date().toISOString();
|
|
604
604
|
const sequence = context.eventStore.nextSequence(runId);
|
|
605
|
-
const event:
|
|
606
|
-
...
|
|
607
|
-
eventId:
|
|
605
|
+
const event: KxmRunEvent = {
|
|
606
|
+
...kxmEventBase(context, run, now, kxmMonotonicNs()),
|
|
607
|
+
eventId: newKxmEventId(),
|
|
608
608
|
eventType: "run.status_changed",
|
|
609
609
|
sequence,
|
|
610
610
|
payload: { status: "failed", reason: "executing_unrecorded", stepId },
|
|
611
611
|
};
|
|
612
612
|
context.eventStore.appendEvent(event);
|
|
613
613
|
context.eventStore.settleCapability(attemptId, "revoked");
|
|
614
|
-
const next =
|
|
615
|
-
|
|
614
|
+
const next = foldStoredKxmRun(context, run);
|
|
615
|
+
persistKxmRunState(context, runId, next, sequence);
|
|
616
616
|
context.eventStore.updateRunStatus(runId, "failed", now);
|
|
617
617
|
});
|
|
618
618
|
}
|
|
619
619
|
|
|
620
|
-
function recordBareDriveFailure(context:
|
|
621
|
-
if (
|
|
620
|
+
function recordBareDriveFailure(context: KxmRuntimeContext, runId: string): void {
|
|
621
|
+
if (isKxmRuntimeContextClosed(context)) return;
|
|
622
622
|
try {
|
|
623
623
|
const run = context.eventStore.run(runId);
|
|
624
624
|
if (!run) return;
|
|
625
|
-
const state =
|
|
625
|
+
const state = foldStoredKxmRun(context, run);
|
|
626
626
|
if (isTerminalRunStatus(state.status) || state.status === "cancelling") return;
|
|
627
627
|
const inflight = inflightAttemptId(state);
|
|
628
628
|
if (inflight) {
|
|
629
629
|
const currentStep = state.currentStep;
|
|
630
|
-
const located =
|
|
630
|
+
const located = kxmFoldPanelAttempt(currentStep, inflight);
|
|
631
631
|
if (currentStep && currentStep.panel.order.length === 1 && located?.attempt.status === "starting") {
|
|
632
|
-
const plan =
|
|
632
|
+
const plan = rehydrateKxmCompiledPlanFromStore(context.eventStore, run);
|
|
633
633
|
const step = plan.steps[currentStep.stepId];
|
|
634
634
|
if (step?.kind === "gate") return;
|
|
635
635
|
if (step && (step.kind === "agent" || step.kind === "moa") && step.assignments.maximum > 1) return;
|
|
@@ -637,13 +637,13 @@ function recordBareDriveFailure(context: VnextRuntimeContext, runId: string): vo
|
|
|
637
637
|
}
|
|
638
638
|
return;
|
|
639
639
|
}
|
|
640
|
-
|
|
640
|
+
cancelKxmRun(context, runId, { reason: "drive_error" });
|
|
641
641
|
} catch {
|
|
642
642
|
// The original drive error governs; this is a best-effort bare-running brake.
|
|
643
643
|
}
|
|
644
644
|
}
|
|
645
645
|
|
|
646
|
-
async function closeDriveProducer(producer:
|
|
646
|
+
async function closeDriveProducer(producer: KxmProducer): Promise<void> {
|
|
647
647
|
if (typeof producer.close !== "function") return;
|
|
648
648
|
try {
|
|
649
649
|
await producer.close();
|
|
@@ -652,53 +652,53 @@ async function closeDriveProducer(producer: VnextProducer): Promise<void> {
|
|
|
652
652
|
}
|
|
653
653
|
}
|
|
654
654
|
|
|
655
|
-
export class
|
|
656
|
-
private readonly context:
|
|
657
|
-
private readonly bundle:
|
|
655
|
+
export class KxmRunScheduler {
|
|
656
|
+
private readonly context: KxmRuntimeContext;
|
|
657
|
+
private readonly bundle: KxmProjectBundle;
|
|
658
658
|
private readonly configRevision: string;
|
|
659
659
|
|
|
660
|
-
private constructor(context:
|
|
660
|
+
private constructor(context: KxmRuntimeContext, bundle: KxmProjectBundle) {
|
|
661
661
|
this.context = context;
|
|
662
662
|
this.bundle = bundle;
|
|
663
663
|
this.configRevision = bundle.configRevision;
|
|
664
664
|
}
|
|
665
665
|
|
|
666
|
-
static for(context:
|
|
666
|
+
static for(context: KxmRuntimeContext, bundle: KxmProjectBundle): KxmRunScheduler {
|
|
667
667
|
if (String(bundle.project.value.id) !== context.projectId) {
|
|
668
668
|
throw runtimeError("run_owner_mismatch", context.projectId, "scheduler bundle project does not match the runtime context");
|
|
669
669
|
}
|
|
670
|
-
const limits =
|
|
671
|
-
|
|
672
|
-
return new
|
|
670
|
+
const limits = kxmProjectAdmissionLimits(bundle);
|
|
671
|
+
bindKxmSchedulerPolicy(context.eventStore.path, limits.maxConcurrentRuns, bundle.configRevision);
|
|
672
|
+
return new KxmRunScheduler(context, bundle);
|
|
673
673
|
}
|
|
674
674
|
|
|
675
675
|
enqueue(
|
|
676
676
|
runId: string,
|
|
677
|
-
producer:
|
|
677
|
+
producer: KxmProducer,
|
|
678
678
|
options: { allowLimits?: boolean; liveMode?: boolean } = {},
|
|
679
|
-
): Promise<
|
|
679
|
+
): Promise<KxmRunDriveResult> {
|
|
680
680
|
requireTrustedProducer(producer);
|
|
681
|
-
const policy =
|
|
681
|
+
const policy = kxmSchedulerPolicy(this.context.eventStore.path);
|
|
682
682
|
if (!policy || policy.configRevision !== this.configRevision) {
|
|
683
683
|
return Promise.reject(runtimeError("scheduler_policy_conflict", runId, "scheduler handle does not match the active policy"));
|
|
684
684
|
}
|
|
685
685
|
try {
|
|
686
686
|
const run = requireRun(this.context, runId);
|
|
687
|
-
const envelope =
|
|
688
|
-
return
|
|
687
|
+
const envelope = loadKxmRunPlanEnvelope(this.context.eventStore, run);
|
|
688
|
+
return enqueueKxmScheduledRun(
|
|
689
689
|
this.context.eventStore.path,
|
|
690
690
|
runId,
|
|
691
691
|
envelope.revisions.config,
|
|
692
692
|
envelope.projectLimits.maxConcurrentRuns,
|
|
693
693
|
(token) => driveAdmitted(this.context, runId, producer, token, options),
|
|
694
|
-
) as Promise<
|
|
694
|
+
) as Promise<KxmRunDriveResult>;
|
|
695
695
|
} catch (error) {
|
|
696
696
|
return Promise.reject(error);
|
|
697
697
|
}
|
|
698
698
|
}
|
|
699
699
|
|
|
700
|
-
openDriveSession(runId: string, options:
|
|
701
|
-
const policy =
|
|
700
|
+
openDriveSession(runId: string, options: OpenKxmDriveSessionOptions): Promise<KxmDriveSessionHandle> {
|
|
701
|
+
const policy = kxmSchedulerPolicy(this.context.eventStore.path);
|
|
702
702
|
if (!policy || policy.configRevision !== this.configRevision) {
|
|
703
703
|
return Promise.reject(runtimeError("scheduler_policy_conflict", runId, "scheduler handle does not match the active policy"));
|
|
704
704
|
}
|
|
@@ -711,48 +711,48 @@ export class VnextRunScheduler {
|
|
|
711
711
|
return Promise.reject(error);
|
|
712
712
|
}
|
|
713
713
|
|
|
714
|
-
const limits =
|
|
714
|
+
const limits = kxmProjectAdmissionLimits(this.bundle);
|
|
715
715
|
const driveOptions: { allowLimits?: boolean; liveMode?: boolean } = {
|
|
716
716
|
liveMode: options.liveMode ?? options.mode === "live",
|
|
717
717
|
};
|
|
718
718
|
if (options.allowLimits !== undefined) driveOptions.allowLimits = options.allowLimits;
|
|
719
719
|
let opened = false;
|
|
720
720
|
const pending = {
|
|
721
|
-
resolve: (_value:
|
|
721
|
+
resolve: (_value: KxmRunDriveResult) => undefined as void,
|
|
722
722
|
reject: (_error: unknown) => undefined as void,
|
|
723
723
|
};
|
|
724
|
-
const settled = new Promise<
|
|
724
|
+
const settled = new Promise<KxmRunDriveResult>((resolve, reject) => {
|
|
725
725
|
pending.resolve = resolve;
|
|
726
726
|
pending.reject = reject;
|
|
727
727
|
});
|
|
728
728
|
void settled.then(undefined, () => undefined);
|
|
729
729
|
|
|
730
|
-
return new Promise<
|
|
731
|
-
const queued =
|
|
730
|
+
return new Promise<KxmDriveSessionHandle>((resolveOpen, rejectOpen) => {
|
|
731
|
+
const queued = enqueueKxmScheduledRun(
|
|
732
732
|
this.context.eventStore.path,
|
|
733
733
|
runId,
|
|
734
734
|
this.configRevision,
|
|
735
735
|
limits.maxConcurrentRuns,
|
|
736
736
|
async (token) => {
|
|
737
|
-
let producer:
|
|
737
|
+
let producer: KxmProducer | undefined;
|
|
738
738
|
try {
|
|
739
739
|
producer = options.createProducer();
|
|
740
740
|
requireTrustedProducer(producer);
|
|
741
741
|
const current = requireRun(this.context, runId);
|
|
742
742
|
if (current.status === "created") {
|
|
743
|
-
|
|
743
|
+
pinKxmCompiledPlan(this.context, this.bundle, runId);
|
|
744
744
|
}
|
|
745
|
-
const started =
|
|
745
|
+
const started = startKxmRun(this.context, runId, driveOptions);
|
|
746
746
|
if (started.handoff) {
|
|
747
747
|
const error = runtimeError("run_handoff_required", runId, started.handoff.detail);
|
|
748
748
|
Object.assign(error, { handoff: started.handoff });
|
|
749
749
|
throw error;
|
|
750
750
|
}
|
|
751
|
-
const driveId = newDriveId(runId, token, this.context.homeRuntimeId,
|
|
751
|
+
const driveId = newDriveId(runId, token, this.context.homeRuntimeId, kxmMonotonicNs());
|
|
752
752
|
const openedMeta = appendDriveOpened(this.context, runId, driveId, options.mode);
|
|
753
|
-
const startedState =
|
|
753
|
+
const startedState = foldStoredKxmRun(this.context, requireRun(this.context, runId));
|
|
754
754
|
const deadlineAt = driveDeadlineAt(this.context, runId, startedState);
|
|
755
|
-
const session:
|
|
755
|
+
const session: KxmDriveSession = {
|
|
756
756
|
driveId,
|
|
757
757
|
runId,
|
|
758
758
|
token,
|
|
@@ -764,7 +764,7 @@ export class VnextRunScheduler {
|
|
|
764
764
|
settled,
|
|
765
765
|
...(deadlineAt !== undefined ? { deadlineAt } : {}),
|
|
766
766
|
};
|
|
767
|
-
|
|
767
|
+
attachKxmDriveSession(this.context.eventStore.path, runId, token, session);
|
|
768
768
|
opened = true;
|
|
769
769
|
resolveOpen({ driveId, settled });
|
|
770
770
|
try {
|
|
@@ -783,12 +783,12 @@ export class VnextRunScheduler {
|
|
|
783
783
|
recordBareDriveFailure(this.context, runId);
|
|
784
784
|
let foldedStatus: string | undefined;
|
|
785
785
|
try {
|
|
786
|
-
foldedStatus =
|
|
786
|
+
foldedStatus = foldStoredKxmRun(this.context, requireRun(this.context, runId)).status;
|
|
787
787
|
} catch {
|
|
788
788
|
foldedStatus = undefined;
|
|
789
789
|
}
|
|
790
790
|
recordDriveReceipt(this.context, session, {
|
|
791
|
-
kind: foldedStatus !== undefined && isTerminalRunStatus(foldedStatus as
|
|
791
|
+
kind: foldedStatus !== undefined && isTerminalRunStatus(foldedStatus as KxmRunStatus) ? "terminal" : "unsettled",
|
|
792
792
|
error: driveErrorSummary(error),
|
|
793
793
|
producerId: producer.id,
|
|
794
794
|
producerClosed: false,
|
|
@@ -798,10 +798,10 @@ export class VnextRunScheduler {
|
|
|
798
798
|
}
|
|
799
799
|
} finally {
|
|
800
800
|
if (producer) await closeDriveProducer(producer);
|
|
801
|
-
|
|
801
|
+
clearKxmDriveSession(this.context.eventStore.path, runId, token);
|
|
802
802
|
}
|
|
803
803
|
},
|
|
804
|
-
) as Promise<
|
|
804
|
+
) as Promise<KxmRunDriveResult>;
|
|
805
805
|
void queued.then(undefined, (error) => {
|
|
806
806
|
if (!opened) {
|
|
807
807
|
rejectOpen(error);
|
|
@@ -811,19 +811,19 @@ export class VnextRunScheduler {
|
|
|
811
811
|
}
|
|
812
812
|
}
|
|
813
813
|
|
|
814
|
-
export function
|
|
815
|
-
context:
|
|
814
|
+
export function verifyKxmAttemptCapability(
|
|
815
|
+
context: KxmRuntimeContext,
|
|
816
816
|
presented: string,
|
|
817
817
|
expected: { runId: string; attemptId: string },
|
|
818
|
-
):
|
|
818
|
+
): KxmAttemptCapabilityRecord {
|
|
819
819
|
const presentedHash = hashCapabilitySecret(presented);
|
|
820
820
|
const row = context.eventStore.capabilityByHash(presentedHash);
|
|
821
821
|
const run = context.eventStore.run(expected.runId);
|
|
822
|
-
const folded = run ?
|
|
822
|
+
const folded = run ? foldStoredKxmRun(context, run) : undefined;
|
|
823
823
|
const hashBuf = Buffer.from(presentedHash);
|
|
824
824
|
const storedBuf = Buffer.from(row?.capabilityHash ?? presentedHash);
|
|
825
825
|
const hashMatch = hashBuf.length === storedBuf.length && timingSafeEqual(hashBuf, storedBuf);
|
|
826
|
-
const located =
|
|
826
|
+
const located = kxmFoldPanelAttempt(folded?.currentStep, expected.attemptId);
|
|
827
827
|
const ok = Boolean(
|
|
828
828
|
hashMatch
|
|
829
829
|
&& row
|
|
@@ -851,10 +851,10 @@ export function verifyVnextAttemptCapability(
|
|
|
851
851
|
}
|
|
852
852
|
|
|
853
853
|
function invokeProducer(
|
|
854
|
-
producer:
|
|
855
|
-
request:
|
|
856
|
-
): Promise<{ result?:
|
|
857
|
-
let pending: Promise<
|
|
854
|
+
producer: KxmProducer,
|
|
855
|
+
request: KxmProducerRequest,
|
|
856
|
+
): Promise<{ result?: KxmProducerResult; error: boolean; errorCode?: string }> {
|
|
857
|
+
let pending: Promise<KxmProducerResult>;
|
|
858
858
|
try {
|
|
859
859
|
pending = Promise.resolve(producer.produce(request));
|
|
860
860
|
} catch {
|
|
@@ -866,31 +866,31 @@ function invokeProducer(
|
|
|
866
866
|
);
|
|
867
867
|
}
|
|
868
868
|
|
|
869
|
-
export interface
|
|
870
|
-
readonly run:
|
|
871
|
-
readonly envelope:
|
|
869
|
+
export interface KxmPreparedGateDispatch {
|
|
870
|
+
readonly run: KxmRunRecord;
|
|
871
|
+
readonly envelope: KxmRunPlanEnvelope;
|
|
872
872
|
readonly stepId: string;
|
|
873
873
|
readonly stepAttempt: number;
|
|
874
874
|
readonly assignmentId: string;
|
|
875
875
|
readonly attemptId: string;
|
|
876
876
|
readonly effectId: string;
|
|
877
877
|
readonly controller: AbortController;
|
|
878
|
-
readonly state:
|
|
878
|
+
readonly state: KxmRunState;
|
|
879
879
|
}
|
|
880
880
|
|
|
881
|
-
export interface
|
|
882
|
-
afterEvaluate?: ((prepared:
|
|
883
|
-
afterObservationWrite?: ((prepared:
|
|
884
|
-
afterUncertaintyWrite?: ((prepared:
|
|
885
|
-
afterSettlementCommit?: ((prepared:
|
|
886
|
-
afterSpawnRecord?: ((prepared:
|
|
887
|
-
beforeCommandSpawn?: ((prepared:
|
|
888
|
-
beforeCommandSettle?: ((prepared:
|
|
881
|
+
export interface KxmGateDispatchSeams {
|
|
882
|
+
afterEvaluate?: ((prepared: KxmPreparedGateDispatch) => void) | undefined;
|
|
883
|
+
afterObservationWrite?: ((prepared: KxmPreparedGateDispatch) => void) | undefined;
|
|
884
|
+
afterUncertaintyWrite?: ((prepared: KxmPreparedGateDispatch) => void) | undefined;
|
|
885
|
+
afterSettlementCommit?: ((prepared: KxmPreparedGateDispatch) => void) | undefined;
|
|
886
|
+
afterSpawnRecord?: ((prepared: KxmPreparedGateDispatch) => void) | undefined;
|
|
887
|
+
beforeCommandSpawn?: ((prepared: KxmPreparedGateDispatch) => void) | undefined;
|
|
888
|
+
beforeCommandSettle?: ((prepared: KxmPreparedGateDispatch) => void) | undefined;
|
|
889
889
|
}
|
|
890
890
|
|
|
891
|
-
export const
|
|
891
|
+
export const kxmGateDispatchSeams: KxmGateDispatchSeams = {};
|
|
892
892
|
|
|
893
|
-
async function stepLocked(context:
|
|
893
|
+
async function stepLocked(context: KxmRuntimeContext, runId: string, producer: KxmProducer, token: string): Promise<KxmRunDriveResult> {
|
|
894
894
|
const prepared = context.eventStore.transaction(() => prepareDispatch(context, runId, producer.id));
|
|
895
895
|
if (prepared.kind === "return") {
|
|
896
896
|
return prepared.handoff ? { state: prepared.state, handoff: prepared.handoff } : { state: prepared.state };
|
|
@@ -908,15 +908,15 @@ async function stepLocked(context: VnextRuntimeContext, runId: string, producer:
|
|
|
908
908
|
if (!definition || definition.kind !== "artifacts-exist") {
|
|
909
909
|
throw runtimeError("run_events_illegal", prepared.run.runId, "prepared gate is not artifacts-exist");
|
|
910
910
|
}
|
|
911
|
-
|
|
911
|
+
registerKxmAttemptController(context.eventStore.path, runId, { attemptId: prepared.attemptId, controller: prepared.controller });
|
|
912
912
|
const clearBudget = armRunDurationBudgetTimer(context, runId);
|
|
913
913
|
try {
|
|
914
914
|
const observation = evaluateArtifactsGate(context, definition);
|
|
915
|
-
|
|
915
|
+
kxmGateDispatchSeams.afterEvaluate?.(prepared);
|
|
916
916
|
return context.eventStore.transaction(() => settlePreparedGate(context, prepared, observation));
|
|
917
917
|
} finally {
|
|
918
918
|
clearBudget();
|
|
919
|
-
|
|
919
|
+
unregisterKxmAttemptController(context.eventStore.path, runId, prepared.attemptId);
|
|
920
920
|
}
|
|
921
921
|
}
|
|
922
922
|
|
|
@@ -924,11 +924,11 @@ async function stepLocked(context: VnextRuntimeContext, runId: string, producer:
|
|
|
924
924
|
}
|
|
925
925
|
|
|
926
926
|
async function runPreparedCommandGate(
|
|
927
|
-
context:
|
|
928
|
-
prepared:
|
|
927
|
+
context: KxmRuntimeContext,
|
|
928
|
+
prepared: KxmPreparedGateDispatch,
|
|
929
929
|
definition: { readonly kind: "command"; readonly argv: readonly string[]; readonly timeoutMs: number; readonly cwd?: "control" },
|
|
930
930
|
token: string,
|
|
931
|
-
): Promise<
|
|
931
|
+
): Promise<KxmRunDriveResult> {
|
|
932
932
|
assertCommandSeatbelt(definition.argv.join(" "));
|
|
933
933
|
const storePath = context.eventStore.path;
|
|
934
934
|
const observer = createCommandObserver({
|
|
@@ -936,17 +936,17 @@ async function runPreparedCommandGate(
|
|
|
936
936
|
cwd: context.projectRoot,
|
|
937
937
|
signal: prepared.controller.signal,
|
|
938
938
|
onSpawned: () => {
|
|
939
|
-
if (
|
|
939
|
+
if (isKxmRuntimeContextClosed(context)) return;
|
|
940
940
|
try {
|
|
941
941
|
context.eventStore.transaction(() => {
|
|
942
942
|
recordGateSpawnedInTransaction(context, prepared.run, prepared.envelope, prepared.stepId);
|
|
943
|
-
|
|
943
|
+
kxmGateDispatchSeams.afterSpawnRecord?.(prepared);
|
|
944
944
|
});
|
|
945
945
|
} catch (error) {
|
|
946
946
|
// Mark the hold unsettled synchronously BEFORE the observer stop path
|
|
947
947
|
// can take any fallible action (group signalling) on this attempt.
|
|
948
948
|
try {
|
|
949
|
-
|
|
949
|
+
markKxmGateHoldUnsettled(storePath, prepared.run.runId, token, prepared.attemptId);
|
|
950
950
|
} catch {
|
|
951
951
|
// The hold is already unsettled or released; the original failure governs.
|
|
952
952
|
}
|
|
@@ -954,25 +954,25 @@ async function runPreparedCommandGate(
|
|
|
954
954
|
}
|
|
955
955
|
},
|
|
956
956
|
});
|
|
957
|
-
|
|
958
|
-
|
|
957
|
+
armKxmGateHold(storePath, prepared.run.runId, token, prepared.attemptId, () => observer.requestStop("cancel"));
|
|
958
|
+
registerKxmAttemptController(storePath, prepared.run.runId, { attemptId: prepared.attemptId, controller: prepared.controller });
|
|
959
959
|
const clearBudget = armRunDurationBudgetTimer(context, prepared.run.runId);
|
|
960
|
-
const unhookClose =
|
|
960
|
+
const unhookClose = registerKxmRuntimeCloseHook(context, () => {
|
|
961
961
|
try {
|
|
962
|
-
|
|
962
|
+
markKxmGateHoldUnsettled(storePath, prepared.run.runId, token, prepared.attemptId);
|
|
963
963
|
} catch {
|
|
964
964
|
// Hold may already be unsettled.
|
|
965
965
|
}
|
|
966
|
-
|
|
966
|
+
dropKxmGateStopHook(storePath, prepared.run.runId, prepared.attemptId);
|
|
967
967
|
observer.beginBoundedCleanup();
|
|
968
968
|
});
|
|
969
969
|
try {
|
|
970
|
-
|
|
970
|
+
kxmGateDispatchSeams.beforeCommandSpawn?.(prepared);
|
|
971
971
|
const outcome = await observer.run();
|
|
972
|
-
|
|
973
|
-
if (
|
|
972
|
+
dropKxmGateStopHook(storePath, prepared.run.runId, prepared.attemptId);
|
|
973
|
+
if (isKxmRuntimeContextClosed(context)) {
|
|
974
974
|
try {
|
|
975
|
-
|
|
975
|
+
markKxmGateHoldUnsettled(storePath, prepared.run.runId, token, prepared.attemptId);
|
|
976
976
|
} catch {
|
|
977
977
|
// already unsettled
|
|
978
978
|
}
|
|
@@ -985,15 +985,15 @@ async function runPreparedCommandGate(
|
|
|
985
985
|
}
|
|
986
986
|
if (outcome.kind === "complete") {
|
|
987
987
|
try {
|
|
988
|
-
|
|
988
|
+
kxmGateDispatchSeams.beforeCommandSettle?.(prepared);
|
|
989
989
|
} catch (error) {
|
|
990
990
|
// Retain the unsettled hold and attempt a truthful recording-error
|
|
991
991
|
// uncertainty only if no observation was committed.
|
|
992
992
|
return recoverCommandRecordingFailure(context, prepared, token, outcome.observation, error);
|
|
993
993
|
}
|
|
994
|
-
if (
|
|
994
|
+
if (isKxmRuntimeContextClosed(context)) {
|
|
995
995
|
try {
|
|
996
|
-
|
|
996
|
+
markKxmGateHoldUnsettled(storePath, prepared.run.runId, token, prepared.attemptId);
|
|
997
997
|
} catch {
|
|
998
998
|
// already unsettled
|
|
999
999
|
}
|
|
@@ -1005,18 +1005,18 @@ async function runPreparedCommandGate(
|
|
|
1005
1005
|
} finally {
|
|
1006
1006
|
unhookClose();
|
|
1007
1007
|
clearBudget();
|
|
1008
|
-
|
|
1008
|
+
unregisterKxmAttemptController(storePath, prepared.run.runId, prepared.attemptId);
|
|
1009
1009
|
}
|
|
1010
1010
|
}
|
|
1011
1011
|
|
|
1012
1012
|
function closedCommandHandoff(
|
|
1013
|
-
context:
|
|
1014
|
-
prepared:
|
|
1013
|
+
context: KxmRuntimeContext,
|
|
1014
|
+
prepared: KxmPreparedGateDispatch,
|
|
1015
1015
|
reason: string,
|
|
1016
|
-
):
|
|
1017
|
-
let state:
|
|
1016
|
+
): KxmRunDriveResult {
|
|
1017
|
+
let state: KxmRunDriveResult["state"];
|
|
1018
1018
|
try {
|
|
1019
|
-
state =
|
|
1019
|
+
state = foldStoredKxmRun(context, requireRun(context, prepared.run.runId));
|
|
1020
1020
|
} catch {
|
|
1021
1021
|
state = prepared.state;
|
|
1022
1022
|
}
|
|
@@ -1032,25 +1032,25 @@ function closedCommandHandoff(
|
|
|
1032
1032
|
}
|
|
1033
1033
|
|
|
1034
1034
|
function commitOwnedCommandSettlement(
|
|
1035
|
-
context:
|
|
1036
|
-
prepared:
|
|
1035
|
+
context: KxmRuntimeContext,
|
|
1036
|
+
prepared: KxmPreparedGateDispatch,
|
|
1037
1037
|
token: string,
|
|
1038
|
-
write: () => { state:
|
|
1039
|
-
observation:
|
|
1040
|
-
):
|
|
1041
|
-
let committed: { state:
|
|
1038
|
+
write: () => { state: KxmRunDriveResult["state"] },
|
|
1039
|
+
observation: KxmGateObservationInput,
|
|
1040
|
+
): KxmRunDriveResult {
|
|
1041
|
+
let committed: { state: KxmRunDriveResult["state"] };
|
|
1042
1042
|
try {
|
|
1043
1043
|
committed = context.eventStore.transaction(() => write());
|
|
1044
1044
|
} catch (error) {
|
|
1045
1045
|
return recoverCommandRecordingFailure(context, prepared, token, observation, error);
|
|
1046
1046
|
}
|
|
1047
1047
|
try {
|
|
1048
|
-
|
|
1048
|
+
kxmGateDispatchSeams.afterSettlementCommit?.(prepared);
|
|
1049
1049
|
} catch (error) {
|
|
1050
1050
|
return retainCommittedCommandProof(context, prepared, token, error);
|
|
1051
1051
|
}
|
|
1052
1052
|
try {
|
|
1053
|
-
|
|
1053
|
+
finishKxmOwnedGate(context.eventStore.path, prepared.run.runId, token, prepared.attemptId);
|
|
1054
1054
|
} catch (error) {
|
|
1055
1055
|
return retainCommittedCommandProof(context, prepared, token, error);
|
|
1056
1056
|
}
|
|
@@ -1058,8 +1058,8 @@ function commitOwnedCommandSettlement(
|
|
|
1058
1058
|
}
|
|
1059
1059
|
|
|
1060
1060
|
function retainCommittedCommandProof(
|
|
1061
|
-
context:
|
|
1062
|
-
prepared:
|
|
1061
|
+
context: KxmRuntimeContext,
|
|
1062
|
+
prepared: KxmPreparedGateDispatch,
|
|
1063
1063
|
token: string,
|
|
1064
1064
|
error: unknown,
|
|
1065
1065
|
): never {
|
|
@@ -1072,11 +1072,11 @@ function retainCommittedCommandProof(
|
|
|
1072
1072
|
}
|
|
1073
1073
|
|
|
1074
1074
|
function finishOwnedGateAfterCommittedTerminalProof(
|
|
1075
|
-
context:
|
|
1076
|
-
prepared:
|
|
1075
|
+
context: KxmRuntimeContext,
|
|
1076
|
+
prepared: KxmPreparedGateDispatch,
|
|
1077
1077
|
token: string,
|
|
1078
1078
|
): void {
|
|
1079
|
-
if (
|
|
1079
|
+
if (isKxmRuntimeContextClosed(context)) return;
|
|
1080
1080
|
const run = context.eventStore.run(prepared.run.runId);
|
|
1081
1081
|
if (!run || run.projectId !== context.projectId || run.homeRuntimeId !== context.homeRuntimeId) return;
|
|
1082
1082
|
const row = context.eventStore.gateAttempt(prepared.attemptId);
|
|
@@ -1095,7 +1095,7 @@ function finishOwnedGateAfterCommittedTerminalProof(
|
|
|
1095
1095
|
const events = context.eventStore.events(prepared.run.runId, 0, 1_000_000);
|
|
1096
1096
|
let state;
|
|
1097
1097
|
try {
|
|
1098
|
-
state =
|
|
1098
|
+
state = foldStoredKxmRun(context, run);
|
|
1099
1099
|
} catch {
|
|
1100
1100
|
return;
|
|
1101
1101
|
}
|
|
@@ -1114,8 +1114,8 @@ function finishOwnedGateAfterCommittedTerminalProof(
|
|
|
1114
1114
|
const terminalProof = isEvaluatedSettledProof(row, observation, evidence)
|
|
1115
1115
|
|| isExcludedTerminalProof(state, row, observation, evidence, events);
|
|
1116
1116
|
if (!terminalProof) return;
|
|
1117
|
-
const hold =
|
|
1118
|
-
const admittedToken =
|
|
1117
|
+
const hold = kxmGateHold(context.eventStore.path, prepared.run.runId);
|
|
1118
|
+
const admittedToken = kxmAdmittedToken(context.eventStore.path, prepared.run.runId);
|
|
1119
1119
|
if (
|
|
1120
1120
|
!hold
|
|
1121
1121
|
|| hold.state !== "active"
|
|
@@ -1125,18 +1125,18 @@ function finishOwnedGateAfterCommittedTerminalProof(
|
|
|
1125
1125
|
) {
|
|
1126
1126
|
return;
|
|
1127
1127
|
}
|
|
1128
|
-
|
|
1128
|
+
finishKxmOwnedGate(context.eventStore.path, prepared.run.runId, token, prepared.attemptId);
|
|
1129
1129
|
}
|
|
1130
1130
|
|
|
1131
1131
|
function recoverCommandRecordingFailure(
|
|
1132
|
-
context:
|
|
1133
|
-
prepared:
|
|
1132
|
+
context: KxmRuntimeContext,
|
|
1133
|
+
prepared: KxmPreparedGateDispatch,
|
|
1134
1134
|
token: string,
|
|
1135
|
-
observation:
|
|
1135
|
+
observation: KxmGateObservationInput,
|
|
1136
1136
|
error: unknown,
|
|
1137
|
-
):
|
|
1138
|
-
|
|
1139
|
-
if (
|
|
1137
|
+
): KxmRunDriveResult {
|
|
1138
|
+
markKxmGateHoldUnsettled(context.eventStore.path, prepared.run.runId, token, prepared.attemptId);
|
|
1139
|
+
if (isKxmRuntimeContextClosed(context)) {
|
|
1140
1140
|
throw error;
|
|
1141
1141
|
}
|
|
1142
1142
|
const existing = context.eventStore.gateObservationForAttempt(prepared.attemptId);
|
|
@@ -1147,7 +1147,7 @@ function recoverCommandRecordingFailure(
|
|
|
1147
1147
|
`command observation recording failed after committed proof: ${error instanceof Error ? error.message : String(error)}`,
|
|
1148
1148
|
);
|
|
1149
1149
|
}
|
|
1150
|
-
const folded =
|
|
1150
|
+
const folded = foldStoredKxmRun(context, requireRun(context, prepared.run.runId));
|
|
1151
1151
|
if (folded.currentStep?.attemptId === prepared.attemptId && folded.currentStep.observationId) {
|
|
1152
1152
|
throw runtimeError(
|
|
1153
1153
|
"run_events_illegal",
|
|
@@ -1155,7 +1155,7 @@ function recoverCommandRecordingFailure(
|
|
|
1155
1155
|
"command observation recording failed after committed proof",
|
|
1156
1156
|
);
|
|
1157
1157
|
}
|
|
1158
|
-
const incomplete:
|
|
1158
|
+
const incomplete: KxmGateObservationInput = {
|
|
1159
1159
|
...observation,
|
|
1160
1160
|
completeness: "incomplete",
|
|
1161
1161
|
errorClass: observation.errorClass ?? "recording-error",
|
|
@@ -1165,19 +1165,19 @@ function recoverCommandRecordingFailure(
|
|
|
1165
1165
|
}
|
|
1166
1166
|
|
|
1167
1167
|
function commitCommandUncertainty(
|
|
1168
|
-
context:
|
|
1169
|
-
prepared:
|
|
1168
|
+
context: KxmRuntimeContext,
|
|
1169
|
+
prepared: KxmPreparedGateDispatch,
|
|
1170
1170
|
token: string,
|
|
1171
1171
|
reason: string,
|
|
1172
|
-
observation:
|
|
1172
|
+
observation: KxmGateObservationInput,
|
|
1173
1173
|
priorError?: unknown,
|
|
1174
|
-
):
|
|
1174
|
+
): KxmRunDriveResult {
|
|
1175
1175
|
try {
|
|
1176
|
-
|
|
1176
|
+
markKxmGateHoldUnsettled(context.eventStore.path, prepared.run.runId, token, prepared.attemptId);
|
|
1177
1177
|
} catch {
|
|
1178
1178
|
// already unsettled
|
|
1179
1179
|
}
|
|
1180
|
-
if (
|
|
1180
|
+
if (isKxmRuntimeContextClosed(context)) {
|
|
1181
1181
|
if (priorError) throw priorError;
|
|
1182
1182
|
return closedCommandHandoff(context, prepared, reason);
|
|
1183
1183
|
}
|
|
@@ -1189,11 +1189,11 @@ function commitCommandUncertainty(
|
|
|
1189
1189
|
"command observation already committed; uncertainty cannot replace it",
|
|
1190
1190
|
);
|
|
1191
1191
|
}
|
|
1192
|
-
const incomplete:
|
|
1192
|
+
const incomplete: KxmGateObservationInput = { ...observation, completeness: "incomplete" };
|
|
1193
1193
|
try {
|
|
1194
1194
|
const written = context.eventStore.transaction(() => {
|
|
1195
1195
|
const result = recordGateUncertainInTransaction(context, prepared.run, prepared.envelope, prepared.stepId, reason, incomplete);
|
|
1196
|
-
|
|
1196
|
+
kxmGateDispatchSeams.afterUncertaintyWrite?.(prepared);
|
|
1197
1197
|
return result;
|
|
1198
1198
|
});
|
|
1199
1199
|
return {
|
|
@@ -1212,9 +1212,9 @@ function commitCommandUncertainty(
|
|
|
1212
1212
|
}
|
|
1213
1213
|
|
|
1214
1214
|
interface PreparedDispatch {
|
|
1215
|
-
run:
|
|
1216
|
-
plan:
|
|
1217
|
-
step:
|
|
1215
|
+
run: KxmRunRecord;
|
|
1216
|
+
plan: KxmCompiledPlan;
|
|
1217
|
+
step: KxmCompiledStep;
|
|
1218
1218
|
stepId: string;
|
|
1219
1219
|
stepAttempt: number;
|
|
1220
1220
|
assignmentId: string;
|
|
@@ -1222,42 +1222,42 @@ interface PreparedDispatch {
|
|
|
1222
1222
|
agentId: string;
|
|
1223
1223
|
capabilityHash: string;
|
|
1224
1224
|
producerId: string;
|
|
1225
|
-
request:
|
|
1225
|
+
request: KxmProducerRequest;
|
|
1226
1226
|
controller: AbortController;
|
|
1227
|
-
state:
|
|
1227
|
+
state: KxmRunState;
|
|
1228
1228
|
}
|
|
1229
1229
|
|
|
1230
1230
|
interface PreparedPanel {
|
|
1231
|
-
run:
|
|
1232
|
-
plan:
|
|
1233
|
-
step:
|
|
1231
|
+
run: KxmRunRecord;
|
|
1232
|
+
plan: KxmCompiledPlan;
|
|
1233
|
+
step: KxmCompiledStep;
|
|
1234
1234
|
stepId: string;
|
|
1235
1235
|
stepAttempt: number;
|
|
1236
1236
|
target: number;
|
|
1237
1237
|
maxParallel: number;
|
|
1238
1238
|
first: PreparedDispatch;
|
|
1239
|
-
state:
|
|
1239
|
+
state: KxmRunState;
|
|
1240
1240
|
}
|
|
1241
1241
|
|
|
1242
|
-
export interface
|
|
1242
|
+
export interface KxmPanelMemberHook {
|
|
1243
1243
|
readonly attemptId: string;
|
|
1244
1244
|
readonly assignmentId: string;
|
|
1245
1245
|
readonly stepId: string;
|
|
1246
1246
|
readonly stepAttempt: number;
|
|
1247
1247
|
}
|
|
1248
1248
|
|
|
1249
|
-
export interface
|
|
1250
|
-
afterBirth?: ((member:
|
|
1251
|
-
beforeAppendExecuting?: ((member:
|
|
1252
|
-
failAppendExecuting?: ((member:
|
|
1253
|
-
beforeInvoke?: ((member:
|
|
1254
|
-
failSettleMember?: ((member:
|
|
1249
|
+
export interface KxmPanelDispatchSeams {
|
|
1250
|
+
afterBirth?: ((member: KxmPanelMemberHook) => void) | undefined;
|
|
1251
|
+
beforeAppendExecuting?: ((member: KxmPanelMemberHook) => void) | undefined;
|
|
1252
|
+
failAppendExecuting?: ((member: KxmPanelMemberHook) => boolean) | undefined;
|
|
1253
|
+
beforeInvoke?: ((member: KxmPanelMemberHook) => void) | undefined;
|
|
1254
|
+
failSettleMember?: ((member: KxmPanelMemberHook) => boolean) | undefined;
|
|
1255
1255
|
skipDispatch?: (() => boolean) | undefined;
|
|
1256
1256
|
}
|
|
1257
1257
|
|
|
1258
|
-
export const
|
|
1258
|
+
export const kxmPanelDispatchSeams: KxmPanelDispatchSeams = {};
|
|
1259
1259
|
|
|
1260
|
-
function unreconciledPanelAttemptId(state:
|
|
1260
|
+
function unreconciledPanelAttemptId(state: KxmRunState): string | undefined {
|
|
1261
1261
|
const current = state.currentStep;
|
|
1262
1262
|
if (!current) return undefined;
|
|
1263
1263
|
for (const assignmentId of current.panel.order) {
|
|
@@ -1274,9 +1274,9 @@ function unreconciledPanelAttemptId(state: VnextRunState): string | undefined {
|
|
|
1274
1274
|
|
|
1275
1275
|
function resolveProducerRoute(
|
|
1276
1276
|
projectRoot: string,
|
|
1277
|
-
step:
|
|
1277
|
+
step: KxmCompiledStep,
|
|
1278
1278
|
agentId: string,
|
|
1279
|
-
): { provider: string; model: string; selector: string } | { error: Omit<
|
|
1279
|
+
): { provider: string; model: string; selector: string } | { error: Omit<KxmRunHandoff, "stepId"> } {
|
|
1280
1280
|
let agentModel: string | undefined;
|
|
1281
1281
|
const agentFile = join(projectRoot, ".kxm", "agents", `${agentId}.yaml`);
|
|
1282
1282
|
if (existsSync(agentFile)) {
|
|
@@ -1346,17 +1346,17 @@ function resolveProducerRoute(
|
|
|
1346
1346
|
}
|
|
1347
1347
|
|
|
1348
1348
|
function prepareDispatch(
|
|
1349
|
-
context:
|
|
1349
|
+
context: KxmRuntimeContext,
|
|
1350
1350
|
runId: string,
|
|
1351
1351
|
producerId?: "driver-simulated" | "pi" | string,
|
|
1352
|
-
): { kind: "panel"; panel: PreparedPanel } | { kind: "return"; state:
|
|
1352
|
+
): { kind: "panel"; panel: PreparedPanel } | { kind: "return"; state: KxmRunState; handoff?: KxmRunHandoff } | ({ kind: "gate" } & KxmPreparedGateDispatch) {
|
|
1353
1353
|
const run = requireRun(context, runId);
|
|
1354
|
-
const plan =
|
|
1355
|
-
const state =
|
|
1356
|
-
if (
|
|
1354
|
+
const plan = rehydrateKxmCompiledPlanFromStore(context.eventStore, run);
|
|
1355
|
+
const state = foldStoredKxmRun(context, run);
|
|
1356
|
+
if (kxmPanelDispatchSeams.skipDispatch?.()) {
|
|
1357
1357
|
return { kind: "return", state };
|
|
1358
1358
|
}
|
|
1359
|
-
if (state.status === "cancelling" &&
|
|
1359
|
+
if (state.status === "cancelling" && kxmAttemptControllers(context.eventStore.path, runId).length === 0) {
|
|
1360
1360
|
return {
|
|
1361
1361
|
kind: "return",
|
|
1362
1362
|
state,
|
|
@@ -1439,7 +1439,7 @@ function prepareDispatch(
|
|
|
1439
1439
|
}
|
|
1440
1440
|
|
|
1441
1441
|
if (step.kind === "gate") {
|
|
1442
|
-
const envelope =
|
|
1442
|
+
const envelope = loadKxmRunPlanEnvelope(context.eventStore, run);
|
|
1443
1443
|
const unsupported = unsupportedGateStep(plan, step, envelope, context);
|
|
1444
1444
|
if (unsupported) return { kind: "return", state, handoff: { ...unsupported, stepId } };
|
|
1445
1445
|
const definition = envelope.gates.definitions[step.gate];
|
|
@@ -1509,23 +1509,23 @@ function prepareDispatch(
|
|
|
1509
1509
|
const stepAttempt = used + 1;
|
|
1510
1510
|
const now = new Date().toISOString();
|
|
1511
1511
|
let sequence = context.eventStore.nextSequence(runId);
|
|
1512
|
-
let mono =
|
|
1513
|
-
const events:
|
|
1512
|
+
let mono = kxmMonotonicNs();
|
|
1513
|
+
const events: KxmRunEvent[] = [];
|
|
1514
1514
|
const push = (eventType: string, payload: Record<string, unknown>): void => {
|
|
1515
1515
|
events.push({
|
|
1516
|
-
...
|
|
1517
|
-
eventId:
|
|
1516
|
+
...kxmEventBase(context, run, now, mono),
|
|
1517
|
+
eventId: newKxmEventId(),
|
|
1518
1518
|
eventType,
|
|
1519
1519
|
sequence: sequence++,
|
|
1520
1520
|
payload,
|
|
1521
1521
|
});
|
|
1522
|
-
mono =
|
|
1522
|
+
mono = kxmIncrementMonotonicNs(mono);
|
|
1523
1523
|
};
|
|
1524
1524
|
push("step.entered", { stepId, stepAttempt, status: "pending" });
|
|
1525
1525
|
push("step.status_changed", { stepId, status: "preparing", previousStatus: "pending" });
|
|
1526
1526
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
1527
|
-
const entered =
|
|
1528
|
-
|
|
1527
|
+
const entered = foldStoredKxmRun(context, run);
|
|
1528
|
+
persistKxmRunState(context, runId, entered, events[events.length - 1]!.sequence);
|
|
1529
1529
|
const first = birthMember(context, {
|
|
1530
1530
|
run,
|
|
1531
1531
|
plan,
|
|
@@ -1552,7 +1552,7 @@ function prepareDispatch(
|
|
|
1552
1552
|
};
|
|
1553
1553
|
}
|
|
1554
1554
|
|
|
1555
|
-
function panelInFlight(state:
|
|
1555
|
+
function panelInFlight(state: KxmRunState): number {
|
|
1556
1556
|
const current = state.currentStep;
|
|
1557
1557
|
if (!current) return 0;
|
|
1558
1558
|
let count = 0;
|
|
@@ -1566,7 +1566,7 @@ function panelInFlight(state: VnextRunState): number {
|
|
|
1566
1566
|
return count;
|
|
1567
1567
|
}
|
|
1568
1568
|
|
|
1569
|
-
function panelFrozenState(state:
|
|
1569
|
+
function panelFrozenState(state: KxmRunState): boolean {
|
|
1570
1570
|
const current = state.currentStep;
|
|
1571
1571
|
if (!current) return true;
|
|
1572
1572
|
return current.outcome !== undefined
|
|
@@ -1575,7 +1575,7 @@ function panelFrozenState(state: VnextRunState): boolean {
|
|
|
1575
1575
|
|| current.status === "cancelled";
|
|
1576
1576
|
}
|
|
1577
1577
|
|
|
1578
|
-
function birthAllowed(state:
|
|
1578
|
+
function birthAllowed(state: KxmRunState, step: KxmCompiledStep): boolean {
|
|
1579
1579
|
if (state.status !== "running" || state.cancelRequested || panelFrozenState(state)) return false;
|
|
1580
1580
|
const born = state.currentStep?.panel.order.length ?? 0;
|
|
1581
1581
|
if (born >= step.assignments.target) return false;
|
|
@@ -1584,11 +1584,11 @@ function birthAllowed(state: VnextRunState, step: VnextCompiledStep): boolean {
|
|
|
1584
1584
|
}
|
|
1585
1585
|
|
|
1586
1586
|
function birthMember(
|
|
1587
|
-
context:
|
|
1587
|
+
context: KxmRuntimeContext,
|
|
1588
1588
|
input: {
|
|
1589
|
-
run:
|
|
1590
|
-
plan:
|
|
1591
|
-
step:
|
|
1589
|
+
run: KxmRunRecord;
|
|
1590
|
+
plan: KxmCompiledPlan;
|
|
1591
|
+
step: KxmCompiledStep;
|
|
1592
1592
|
stepId: string;
|
|
1593
1593
|
stepAttempt: number;
|
|
1594
1594
|
enterRunning?: boolean | undefined;
|
|
@@ -1597,7 +1597,7 @@ function birthMember(
|
|
|
1597
1597
|
},
|
|
1598
1598
|
): PreparedDispatch {
|
|
1599
1599
|
const run = requireRun(context, input.run.runId);
|
|
1600
|
-
const folded =
|
|
1600
|
+
const folded = foldStoredKxmRun(context, run);
|
|
1601
1601
|
if (!birthAllowed(folded, input.step)) {
|
|
1602
1602
|
throw runtimeError("run_events_illegal", run.runId, "member birth is not legal");
|
|
1603
1603
|
}
|
|
@@ -1622,23 +1622,23 @@ function birthMember(
|
|
|
1622
1622
|
resolvedRoute = routeResult;
|
|
1623
1623
|
}
|
|
1624
1624
|
}
|
|
1625
|
-
const assignmentId =
|
|
1626
|
-
const attemptId =
|
|
1625
|
+
const assignmentId = newKxmAssignmentId();
|
|
1626
|
+
const attemptId = newKxmAttemptId();
|
|
1627
1627
|
const minted = mintCapabilitySecret();
|
|
1628
1628
|
const controller = new AbortController();
|
|
1629
1629
|
const now = new Date().toISOString();
|
|
1630
1630
|
let sequence = context.eventStore.nextSequence(run.runId);
|
|
1631
|
-
let mono =
|
|
1632
|
-
const events:
|
|
1631
|
+
let mono = kxmMonotonicNs();
|
|
1632
|
+
const events: KxmRunEvent[] = [];
|
|
1633
1633
|
const push = (eventType: string, payload: Record<string, unknown>): void => {
|
|
1634
1634
|
events.push({
|
|
1635
|
-
...
|
|
1636
|
-
eventId:
|
|
1635
|
+
...kxmEventBase(context, run, now, mono),
|
|
1636
|
+
eventId: newKxmEventId(),
|
|
1637
1637
|
eventType,
|
|
1638
1638
|
sequence: sequence++,
|
|
1639
1639
|
payload,
|
|
1640
1640
|
});
|
|
1641
|
-
mono =
|
|
1641
|
+
mono = kxmIncrementMonotonicNs(mono);
|
|
1642
1642
|
};
|
|
1643
1643
|
push("assignment.created", { assignmentId, stepId: input.stepId, stepAttempt: input.stepAttempt, agentId, status: "created" });
|
|
1644
1644
|
push("assignment.accepted", { assignmentId, status: "accepted" });
|
|
@@ -1664,8 +1664,8 @@ function birthMember(
|
|
|
1664
1664
|
capabilityHash: minted.hash,
|
|
1665
1665
|
state: "issued",
|
|
1666
1666
|
});
|
|
1667
|
-
const next =
|
|
1668
|
-
|
|
1667
|
+
const next = foldStoredKxmRun(context, run);
|
|
1668
|
+
persistKxmRunState(context, run.runId, next, events[events.length - 1]!.sequence);
|
|
1669
1669
|
const rawContextPacket = buildFormalContextPacket({
|
|
1670
1670
|
project: run.projectId,
|
|
1671
1671
|
targetRole: agentId,
|
|
@@ -1724,46 +1724,46 @@ function birthMember(
|
|
|
1724
1724
|
controller,
|
|
1725
1725
|
state: next,
|
|
1726
1726
|
};
|
|
1727
|
-
|
|
1727
|
+
kxmPanelDispatchSeams.afterBirth?.(member);
|
|
1728
1728
|
return member;
|
|
1729
1729
|
}
|
|
1730
1730
|
|
|
1731
|
-
function appendExecuting(context:
|
|
1731
|
+
function appendExecuting(context: KxmRuntimeContext, dispatch: PreparedDispatch): void {
|
|
1732
1732
|
const run = requireRun(context, dispatch.run.runId);
|
|
1733
1733
|
const now = new Date().toISOString();
|
|
1734
1734
|
let sequence = context.eventStore.nextSequence(dispatch.run.runId);
|
|
1735
|
-
let mono =
|
|
1736
|
-
const events:
|
|
1735
|
+
let mono = kxmMonotonicNs();
|
|
1736
|
+
const events: KxmRunEvent[] = [
|
|
1737
1737
|
{
|
|
1738
|
-
...
|
|
1739
|
-
eventId:
|
|
1738
|
+
...kxmEventBase(context, run, now, mono),
|
|
1739
|
+
eventId: newKxmEventId(),
|
|
1740
1740
|
eventType: "assignment.executing",
|
|
1741
1741
|
sequence: sequence++,
|
|
1742
1742
|
payload: { assignmentId: dispatch.assignmentId, status: "executing" },
|
|
1743
1743
|
},
|
|
1744
1744
|
{
|
|
1745
|
-
...
|
|
1746
|
-
eventId:
|
|
1745
|
+
...kxmEventBase(context, run, now, kxmIncrementMonotonicNs(mono)),
|
|
1746
|
+
eventId: newKxmEventId(),
|
|
1747
1747
|
eventType: "attempt.status_changed",
|
|
1748
1748
|
sequence: sequence++,
|
|
1749
1749
|
payload: { attemptId: dispatch.attemptId, status: "executing" },
|
|
1750
1750
|
},
|
|
1751
1751
|
];
|
|
1752
1752
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
1753
|
-
const next =
|
|
1754
|
-
|
|
1753
|
+
const next = foldStoredKxmRun(context, run);
|
|
1754
|
+
persistKxmRunState(context, run.runId, next, events[events.length - 1]!.sequence);
|
|
1755
1755
|
}
|
|
1756
1756
|
|
|
1757
1757
|
type MemberWork =
|
|
1758
|
-
| { attemptId: string; invoked: true; produced: { result?:
|
|
1758
|
+
| { attemptId: string; invoked: true; produced: { result?: KxmProducerResult; error: boolean } }
|
|
1759
1759
|
| { attemptId: string; invoked: false; skipped: true }
|
|
1760
1760
|
| { attemptId: string; invoked: false; appendFailed: true; error: unknown };
|
|
1761
1761
|
|
|
1762
|
-
function singletonPanel(step:
|
|
1762
|
+
function singletonPanel(step: KxmCompiledStep): boolean {
|
|
1763
1763
|
return step.assignments.maximum === 1 && step.assignments.target === 1;
|
|
1764
1764
|
}
|
|
1765
1765
|
|
|
1766
|
-
function unreconciledHandoff(state:
|
|
1766
|
+
function unreconciledHandoff(state: KxmRunState, stepId: string, attemptId?: string): KxmRunDriveResult {
|
|
1767
1767
|
return {
|
|
1768
1768
|
state,
|
|
1769
1769
|
handoff: {
|
|
@@ -1782,9 +1782,9 @@ function capabilityHashBound(stored: string, minted: string): boolean {
|
|
|
1782
1782
|
}
|
|
1783
1783
|
|
|
1784
1784
|
function capabilityMatchesDispatch(
|
|
1785
|
-
capability:
|
|
1785
|
+
capability: KxmAttemptCapabilityRow | undefined,
|
|
1786
1786
|
member: PreparedDispatch,
|
|
1787
|
-
allowedStates: ReadonlyArray<
|
|
1787
|
+
allowedStates: ReadonlyArray<KxmAttemptCapabilityRow["state"]>,
|
|
1788
1788
|
expectedProducerId?: string,
|
|
1789
1789
|
): boolean {
|
|
1790
1790
|
return Boolean(
|
|
@@ -1801,10 +1801,10 @@ function capabilityMatchesDispatch(
|
|
|
1801
1801
|
}
|
|
1802
1802
|
|
|
1803
1803
|
async function drivePanel(
|
|
1804
|
-
context:
|
|
1804
|
+
context: KxmRuntimeContext,
|
|
1805
1805
|
panel: PreparedPanel,
|
|
1806
|
-
producer:
|
|
1807
|
-
): Promise<
|
|
1806
|
+
producer: KxmProducer,
|
|
1807
|
+
): Promise<KxmRunDriveResult> {
|
|
1808
1808
|
const runId = panel.run.runId;
|
|
1809
1809
|
const owned = new Map<string, PreparedDispatch>();
|
|
1810
1810
|
const pending = new Map<string, Promise<MemberWork>>();
|
|
@@ -1813,7 +1813,7 @@ async function drivePanel(
|
|
|
1813
1813
|
|
|
1814
1814
|
const budgetClears = new Map<string, () => void>();
|
|
1815
1815
|
const register = (member: PreparedDispatch): void => {
|
|
1816
|
-
|
|
1816
|
+
registerKxmAttemptController(context.eventStore.path, runId, { attemptId: member.attemptId, controller: member.controller });
|
|
1817
1817
|
budgetClears.set(member.attemptId, armRunDurationBudgetTimer(context, runId));
|
|
1818
1818
|
owned.set(member.attemptId, member);
|
|
1819
1819
|
};
|
|
@@ -1826,15 +1826,15 @@ async function drivePanel(
|
|
|
1826
1826
|
register(member);
|
|
1827
1827
|
const work = (async (): Promise<MemberWork> => {
|
|
1828
1828
|
try {
|
|
1829
|
-
|
|
1829
|
+
kxmPanelDispatchSeams.beforeAppendExecuting?.(member);
|
|
1830
1830
|
const started = context.eventStore.transaction(() => {
|
|
1831
|
-
if (
|
|
1831
|
+
if (kxmPanelDispatchSeams.failAppendExecuting?.(member)) {
|
|
1832
1832
|
throw runtimeError("run_events_illegal", runId, "appendExecuting failed");
|
|
1833
1833
|
}
|
|
1834
1834
|
const run = requireRun(context, runId);
|
|
1835
|
-
const state =
|
|
1835
|
+
const state = foldStoredKxmRun(context, run);
|
|
1836
1836
|
const capability = context.eventStore.capabilityByAttempt(member.attemptId);
|
|
1837
|
-
const located =
|
|
1837
|
+
const located = kxmFoldPanelAttempt(state.currentStep, member.attemptId);
|
|
1838
1838
|
if (
|
|
1839
1839
|
state.cancelRequested
|
|
1840
1840
|
|| state.status === "cancelling"
|
|
@@ -1851,9 +1851,9 @@ async function drivePanel(
|
|
|
1851
1851
|
if (!started) return { attemptId: member.attemptId, invoked: false, skipped: true };
|
|
1852
1852
|
const executingBound = (): boolean => {
|
|
1853
1853
|
const run = requireRun(context, runId);
|
|
1854
|
-
const state =
|
|
1854
|
+
const state = foldStoredKxmRun(context, run);
|
|
1855
1855
|
const capability = context.eventStore.capabilityByAttempt(member.attemptId);
|
|
1856
|
-
const located =
|
|
1856
|
+
const located = kxmFoldPanelAttempt(state.currentStep, member.attemptId);
|
|
1857
1857
|
if (
|
|
1858
1858
|
state.cancelRequested
|
|
1859
1859
|
|| state.status === "cancelling"
|
|
@@ -1867,7 +1867,7 @@ async function drivePanel(
|
|
|
1867
1867
|
return true;
|
|
1868
1868
|
};
|
|
1869
1869
|
if (!executingBound()) return { attemptId: member.attemptId, invoked: false, skipped: true };
|
|
1870
|
-
|
|
1870
|
+
kxmPanelDispatchSeams.beforeInvoke?.(member);
|
|
1871
1871
|
if (!executingBound()) return { attemptId: member.attemptId, invoked: false, skipped: true };
|
|
1872
1872
|
const produced = await invokeProducer(producer, member.request);
|
|
1873
1873
|
return { attemptId: member.attemptId, invoked: true, produced };
|
|
@@ -1883,7 +1883,7 @@ async function drivePanel(
|
|
|
1883
1883
|
if (stopBirths) return undefined;
|
|
1884
1884
|
return context.eventStore.transaction(() => {
|
|
1885
1885
|
const run = requireRun(context, runId);
|
|
1886
|
-
const state =
|
|
1886
|
+
const state = foldStoredKxmRun(context, run);
|
|
1887
1887
|
if (!birthAllowed(state, panel.step)) return undefined;
|
|
1888
1888
|
return birthMember(context, {
|
|
1889
1889
|
run,
|
|
@@ -1896,7 +1896,7 @@ async function drivePanel(
|
|
|
1896
1896
|
});
|
|
1897
1897
|
};
|
|
1898
1898
|
|
|
1899
|
-
const settleInvoked = (member: PreparedDispatch, produced: { result?:
|
|
1899
|
+
const settleInvoked = (member: PreparedDispatch, produced: { result?: KxmProducerResult; error: boolean }): boolean => {
|
|
1900
1900
|
if (produced.result?.effectUncertain) {
|
|
1901
1901
|
settlementFailed = true;
|
|
1902
1902
|
stopBirths = true;
|
|
@@ -1904,7 +1904,7 @@ async function drivePanel(
|
|
|
1904
1904
|
}
|
|
1905
1905
|
try {
|
|
1906
1906
|
context.eventStore.transaction(() => {
|
|
1907
|
-
if (
|
|
1907
|
+
if (kxmPanelDispatchSeams.failSettleMember?.(member)) {
|
|
1908
1908
|
throw runtimeError("run_events_illegal", runId, "member settlement write failed");
|
|
1909
1909
|
}
|
|
1910
1910
|
settleMember(context, member, produced.result, produced.error);
|
|
@@ -1945,7 +1945,7 @@ async function drivePanel(
|
|
|
1945
1945
|
stopBirths = true;
|
|
1946
1946
|
abortOwned();
|
|
1947
1947
|
await drainPendingInvoked();
|
|
1948
|
-
const state =
|
|
1948
|
+
const state = foldStoredKxmRun(context, requireRun(context, runId));
|
|
1949
1949
|
return unreconciledHandoff(state, panel.stepId);
|
|
1950
1950
|
}
|
|
1951
1951
|
}
|
|
@@ -1969,7 +1969,7 @@ async function drivePanel(
|
|
|
1969
1969
|
settleInvoked(sibling, item.produced);
|
|
1970
1970
|
}
|
|
1971
1971
|
}
|
|
1972
|
-
const state =
|
|
1972
|
+
const state = foldStoredKxmRun(context, requireRun(context, runId));
|
|
1973
1973
|
return unreconciledHandoff(state, panel.stepId, member.attemptId);
|
|
1974
1974
|
}
|
|
1975
1975
|
if (!finished.invoked) {
|
|
@@ -1986,12 +1986,12 @@ async function drivePanel(
|
|
|
1986
1986
|
settleInvoked(sibling, item.produced);
|
|
1987
1987
|
}
|
|
1988
1988
|
}
|
|
1989
|
-
const state =
|
|
1989
|
+
const state = foldStoredKxmRun(context, requireRun(context, runId));
|
|
1990
1990
|
return unreconciledHandoff(state, panel.stepId, member.attemptId);
|
|
1991
1991
|
}
|
|
1992
1992
|
}
|
|
1993
1993
|
|
|
1994
|
-
const folded =
|
|
1994
|
+
const folded = foldStoredKxmRun(context, requireRun(context, runId));
|
|
1995
1995
|
const leftover = unreconciledPanelAttemptId(folded);
|
|
1996
1996
|
if (leftover || settlementFailed) {
|
|
1997
1997
|
return unreconciledHandoff(folded, panel.stepId, leftover);
|
|
@@ -2001,7 +2001,7 @@ async function drivePanel(
|
|
|
2001
2001
|
folded.currentStep
|
|
2002
2002
|
&& born > 0
|
|
2003
2003
|
&& folded.currentStep.panel.order.every((assignmentId) => folded.currentStep?.panel.assignments[assignmentId]?.status === "terminal")
|
|
2004
|
-
&&
|
|
2004
|
+
&& kxmFoldPanelAttemptIds(folded.currentStep).every((attemptId) => kxmFoldPanelAttempt(folded.currentStep, attemptId)?.attempt.status === "terminal"),
|
|
2005
2005
|
);
|
|
2006
2006
|
if (!allTerminal) {
|
|
2007
2007
|
return unreconciledHandoff(folded, panel.stepId, leftover);
|
|
@@ -2012,7 +2012,7 @@ async function drivePanel(
|
|
|
2012
2012
|
try {
|
|
2013
2013
|
return context.eventStore.transaction(() => joinPanel(context, panel));
|
|
2014
2014
|
} catch {
|
|
2015
|
-
const state =
|
|
2015
|
+
const state = foldStoredKxmRun(context, requireRun(context, runId));
|
|
2016
2016
|
return unreconciledHandoff(state, panel.stepId);
|
|
2017
2017
|
}
|
|
2018
2018
|
} catch (error) {
|
|
@@ -2022,12 +2022,12 @@ async function drivePanel(
|
|
|
2022
2022
|
} finally {
|
|
2023
2023
|
for (const attemptId of owned.keys()) {
|
|
2024
2024
|
budgetClears.get(attemptId)?.();
|
|
2025
|
-
|
|
2025
|
+
unregisterKxmAttemptController(context.eventStore.path, runId, attemptId);
|
|
2026
2026
|
}
|
|
2027
2027
|
}
|
|
2028
2028
|
}
|
|
2029
2029
|
|
|
2030
|
-
function producerRoutingRecord(context:
|
|
2030
|
+
function producerRoutingRecord(context: KxmRuntimeContext, dispatch: PreparedDispatch, result: KxmProducerResult, now: string): RoutingRecordV2 {
|
|
2031
2031
|
const run = requireRun(context, dispatch.run.runId);
|
|
2032
2032
|
if (result.costBasis === undefined || result.costBasis === null) {
|
|
2033
2033
|
throw runtimeError("settle_missing_cost_basis", run.runId, `attempt settlement rejected: missing required costBasis for attempt ${dispatch.attemptId}`);
|
|
@@ -2038,7 +2038,7 @@ function producerRoutingRecord(context: VnextRuntimeContext, dispatch: PreparedD
|
|
|
2038
2038
|
if (result.costBasis === "metered" && (typeof result.costUsd !== "number" || !Number.isFinite(result.costUsd) || result.costUsd < 0)) {
|
|
2039
2039
|
throw runtimeError("settle_invalid_cost", run.runId, "attempt settlement rejected: metered costBasis requires non-negative finite costUsd");
|
|
2040
2040
|
}
|
|
2041
|
-
const envelope =
|
|
2041
|
+
const envelope = loadKxmRunPlanEnvelope(context.eventStore, run);
|
|
2042
2042
|
const rawHash = result.behavioralSha256 ?? behavioralConfigHash({
|
|
2043
2043
|
requestedModel: result.requestedModel ?? "simulated",
|
|
2044
2044
|
effectiveModel: result.effectiveModel ?? result.requestedModel ?? "simulated",
|
|
@@ -2065,15 +2065,15 @@ function producerRoutingRecord(context: VnextRuntimeContext, dispatch: PreparedD
|
|
|
2065
2065
|
}
|
|
2066
2066
|
|
|
2067
2067
|
function settleMember(
|
|
2068
|
-
context:
|
|
2068
|
+
context: KxmRuntimeContext,
|
|
2069
2069
|
dispatch: PreparedDispatch,
|
|
2070
|
-
result:
|
|
2070
|
+
result: KxmProducerResult | undefined,
|
|
2071
2071
|
produceError: boolean,
|
|
2072
2072
|
): void {
|
|
2073
2073
|
const run = requireRun(context, dispatch.run.runId);
|
|
2074
|
-
const state =
|
|
2074
|
+
const state = foldStoredKxmRun(context, run);
|
|
2075
2075
|
const capability = context.eventStore.capabilityByAttempt(dispatch.attemptId);
|
|
2076
|
-
const located =
|
|
2076
|
+
const located = kxmFoldPanelAttempt(state.currentStep, dispatch.attemptId);
|
|
2077
2077
|
if (
|
|
2078
2078
|
!located
|
|
2079
2079
|
|| located.assignmentId !== dispatch.assignmentId
|
|
@@ -2086,17 +2086,17 @@ function settleMember(
|
|
|
2086
2086
|
const cancelling = state.status === "cancelling" || capability.state === "revoked";
|
|
2087
2087
|
const now = new Date().toISOString();
|
|
2088
2088
|
let sequence = context.eventStore.nextSequence(run.runId);
|
|
2089
|
-
let mono =
|
|
2090
|
-
const events:
|
|
2089
|
+
let mono = kxmMonotonicNs();
|
|
2090
|
+
const events: KxmRunEvent[] = [];
|
|
2091
2091
|
const push = (eventType: string, payload: Record<string, unknown>): void => {
|
|
2092
2092
|
events.push({
|
|
2093
|
-
...
|
|
2094
|
-
eventId:
|
|
2093
|
+
...kxmEventBase(context, run, now, mono),
|
|
2094
|
+
eventId: newKxmEventId(),
|
|
2095
2095
|
eventType,
|
|
2096
2096
|
sequence: sequence++,
|
|
2097
2097
|
payload,
|
|
2098
2098
|
});
|
|
2099
|
-
mono =
|
|
2099
|
+
mono = kxmIncrementMonotonicNs(mono);
|
|
2100
2100
|
};
|
|
2101
2101
|
push("attempt.status_changed", { attemptId: dispatch.attemptId, status: "settling" });
|
|
2102
2102
|
if (cancelling) {
|
|
@@ -2106,7 +2106,7 @@ function settleMember(
|
|
|
2106
2106
|
push("attempt.status_changed", { attemptId: dispatch.attemptId, status: "terminal" });
|
|
2107
2107
|
push("assignment.terminal", { assignmentId: dispatch.assignmentId, outcome: "cancelled", status: "terminal" });
|
|
2108
2108
|
} else if (produceError) {
|
|
2109
|
-
const envelope =
|
|
2109
|
+
const envelope = loadKxmRunPlanEnvelope(context.eventStore, run);
|
|
2110
2110
|
const rawHash = result?.behavioralSha256 ?? behavioralConfigHash({
|
|
2111
2111
|
requestedModel: result?.requestedModel ?? "simulated",
|
|
2112
2112
|
effectiveModel: result?.effectiveModel ?? result?.requestedModel ?? "simulated",
|
|
@@ -2171,49 +2171,49 @@ function settleMember(
|
|
|
2171
2171
|
}
|
|
2172
2172
|
context.eventStore.settleCapability(dispatch.attemptId, "settled");
|
|
2173
2173
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
2174
|
-
const next =
|
|
2175
|
-
|
|
2174
|
+
const next = foldStoredKxmRun(context, run);
|
|
2175
|
+
persistKxmRunState(context, run.runId, next, events[events.length - 1]!.sequence);
|
|
2176
2176
|
}
|
|
2177
2177
|
|
|
2178
|
-
function joinPanel(context:
|
|
2178
|
+
function joinPanel(context: KxmRuntimeContext, panel: PreparedPanel): KxmRunDriveResult {
|
|
2179
2179
|
const run = requireRun(context, panel.run.runId);
|
|
2180
|
-
const state =
|
|
2180
|
+
const state = foldStoredKxmRun(context, run);
|
|
2181
2181
|
const current = state.currentStep;
|
|
2182
2182
|
if (!current || current.stepId !== panel.stepId) {
|
|
2183
2183
|
throw runtimeError("run_events_illegal", run.runId, "join attempted without the prepared step");
|
|
2184
2184
|
}
|
|
2185
|
-
const joined =
|
|
2185
|
+
const joined = kxmJoinAll(panel.step, current.panel);
|
|
2186
2186
|
const now = new Date().toISOString();
|
|
2187
2187
|
let sequence = context.eventStore.nextSequence(run.runId);
|
|
2188
|
-
let mono =
|
|
2189
|
-
const events:
|
|
2188
|
+
let mono = kxmMonotonicNs();
|
|
2189
|
+
const events: KxmRunEvent[] = [];
|
|
2190
2190
|
const push = (eventType: string, payload: Record<string, unknown>): void => {
|
|
2191
2191
|
events.push({
|
|
2192
|
-
...
|
|
2193
|
-
eventId:
|
|
2192
|
+
...kxmEventBase(context, run, now, mono),
|
|
2193
|
+
eventId: newKxmEventId(),
|
|
2194
2194
|
eventType,
|
|
2195
2195
|
sequence: sequence++,
|
|
2196
2196
|
payload,
|
|
2197
2197
|
});
|
|
2198
|
-
mono =
|
|
2198
|
+
mono = kxmIncrementMonotonicNs(mono);
|
|
2199
2199
|
};
|
|
2200
2200
|
|
|
2201
|
-
const failRun = (reason: string):
|
|
2201
|
+
const failRun = (reason: string): KxmRunDriveResult => {
|
|
2202
2202
|
push("step.status_changed", { stepId: panel.stepId, status: "failed", previousStatus: "running" });
|
|
2203
2203
|
push("run.status_changed", { status: "failed", reason, stepId: panel.stepId });
|
|
2204
2204
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
2205
|
-
const next =
|
|
2206
|
-
|
|
2205
|
+
const next = foldStoredKxmRun(context, run);
|
|
2206
|
+
persistKxmRunState(context, run.runId, next, events[events.length - 1]!.sequence);
|
|
2207
2207
|
context.eventStore.updateRunStatus(run.runId, "failed", now);
|
|
2208
2208
|
return { state: next };
|
|
2209
2209
|
};
|
|
2210
2210
|
|
|
2211
|
-
const cancelRun = ():
|
|
2211
|
+
const cancelRun = (): KxmRunDriveResult => {
|
|
2212
2212
|
push("step.status_changed", { stepId: panel.stepId, status: "cancelled", previousStatus: "running" });
|
|
2213
2213
|
push("run.status_changed", { status: "cancelled", reason: cancelReasonFromLog(context, run.runId) });
|
|
2214
2214
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
2215
|
-
const next =
|
|
2216
|
-
|
|
2215
|
+
const next = foldStoredKxmRun(context, run);
|
|
2216
|
+
persistKxmRunState(context, run.runId, next, events[events.length - 1]!.sequence);
|
|
2217
2217
|
context.eventStore.updateRunStatus(run.runId, "cancelled", now);
|
|
2218
2218
|
return { state: next };
|
|
2219
2219
|
};
|
|
@@ -2252,8 +2252,8 @@ function joinPanel(context: VnextRuntimeContext, panel: PreparedPanel): VnextRun
|
|
|
2252
2252
|
if (budget) {
|
|
2253
2253
|
push("run.status_changed", { status: "failed", reason: budget, stepId: panel.stepId, outcome });
|
|
2254
2254
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
2255
|
-
const next =
|
|
2256
|
-
|
|
2255
|
+
const next = foldStoredKxmRun(context, run);
|
|
2256
|
+
persistKxmRunState(context, run.runId, next, events[events.length - 1]!.sequence);
|
|
2257
2257
|
context.eventStore.updateRunStatus(run.runId, "failed", now);
|
|
2258
2258
|
return { state: next };
|
|
2259
2259
|
}
|
|
@@ -2264,60 +2264,60 @@ function joinPanel(context: VnextRuntimeContext, panel: PreparedPanel): VnextRun
|
|
|
2264
2264
|
push("run.status_changed", { status: selected.terminalStatus });
|
|
2265
2265
|
}
|
|
2266
2266
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
2267
|
-
const next =
|
|
2268
|
-
|
|
2269
|
-
context.eventStore.updateRunStatus(run.runId, next.status as
|
|
2267
|
+
const next = foldStoredKxmRun(context, run);
|
|
2268
|
+
persistKxmRunState(context, run.runId, next, events[events.length - 1]!.sequence);
|
|
2269
|
+
context.eventStore.updateRunStatus(run.runId, next.status as KxmRunStatus, now);
|
|
2270
2270
|
return { state: next };
|
|
2271
2271
|
}
|
|
2272
2272
|
|
|
2273
2273
|
function failBudget(
|
|
2274
|
-
context:
|
|
2275
|
-
run:
|
|
2276
|
-
plan:
|
|
2277
|
-
state:
|
|
2274
|
+
context: KxmRuntimeContext,
|
|
2275
|
+
run: KxmRunRecord,
|
|
2276
|
+
plan: KxmCompiledPlan,
|
|
2277
|
+
state: KxmRunState,
|
|
2278
2278
|
reason: string,
|
|
2279
2279
|
stepId: string,
|
|
2280
|
-
): { state:
|
|
2280
|
+
): { state: KxmRunState } {
|
|
2281
2281
|
const now = new Date().toISOString();
|
|
2282
2282
|
const sequence = context.eventStore.nextSequence(run.runId);
|
|
2283
|
-
const event:
|
|
2284
|
-
...
|
|
2285
|
-
eventId:
|
|
2283
|
+
const event: KxmRunEvent = {
|
|
2284
|
+
...kxmEventBase(context, run, now, kxmMonotonicNs()),
|
|
2285
|
+
eventId: newKxmEventId(),
|
|
2286
2286
|
eventType: "run.status_changed",
|
|
2287
2287
|
sequence,
|
|
2288
2288
|
payload: { status: "failed", reason, stepId },
|
|
2289
2289
|
};
|
|
2290
2290
|
context.eventStore.appendEvent(event);
|
|
2291
|
-
const next =
|
|
2292
|
-
|
|
2291
|
+
const next = foldStoredKxmRun(context, run);
|
|
2292
|
+
persistKxmRunState(context, run.runId, next, sequence);
|
|
2293
2293
|
context.eventStore.updateRunStatus(run.runId, "failed", now);
|
|
2294
2294
|
void state;
|
|
2295
2295
|
return { state: next };
|
|
2296
2296
|
}
|
|
2297
2297
|
|
|
2298
|
-
function hardStopUnrecorded(context:
|
|
2298
|
+
function hardStopUnrecorded(context: KxmRuntimeContext, dispatch: PreparedDispatch, reason: string): void {
|
|
2299
2299
|
const run = requireRun(context, dispatch.run.runId);
|
|
2300
2300
|
const now = new Date().toISOString();
|
|
2301
2301
|
const sequence = context.eventStore.nextSequence(run.runId);
|
|
2302
|
-
const event:
|
|
2303
|
-
...
|
|
2304
|
-
eventId:
|
|
2302
|
+
const event: KxmRunEvent = {
|
|
2303
|
+
...kxmEventBase(context, run, now, kxmMonotonicNs()),
|
|
2304
|
+
eventId: newKxmEventId(),
|
|
2305
2305
|
eventType: "run.status_changed",
|
|
2306
2306
|
sequence,
|
|
2307
2307
|
payload: { status: "failed", reason, stepId: dispatch.stepId },
|
|
2308
2308
|
};
|
|
2309
2309
|
context.eventStore.appendEvent(event);
|
|
2310
2310
|
context.eventStore.settleCapability(dispatch.attemptId, "revoked");
|
|
2311
|
-
const next =
|
|
2312
|
-
|
|
2311
|
+
const next = foldStoredKxmRun(context, run);
|
|
2312
|
+
persistKxmRunState(context, run.runId, next, sequence);
|
|
2313
2313
|
context.eventStore.updateRunStatus(run.runId, "failed", now);
|
|
2314
2314
|
}
|
|
2315
2315
|
|
|
2316
|
-
export function
|
|
2316
|
+
export function kxmTransitionBudgetFailure(plan: KxmCompiledPlan, state: KxmRunState, fromStepId: string, outcome: string): string | undefined {
|
|
2317
2317
|
return transitionBudgetFailure(plan, state, fromStepId, outcome);
|
|
2318
2318
|
}
|
|
2319
2319
|
|
|
2320
|
-
function transitionBudgetFailure(plan:
|
|
2320
|
+
function transitionBudgetFailure(plan: KxmCompiledPlan, state: KxmRunState, fromStepId: string, outcome: string): string | undefined {
|
|
2321
2321
|
const selected = plan.steps[fromStepId]?.transitions[outcome];
|
|
2322
2322
|
if (!selected) return "outcome_unknown";
|
|
2323
2323
|
const edgeKey = `${fromStepId}:${outcome}`;
|
|
@@ -2341,11 +2341,11 @@ function elapsedMsBetween(fromIso: string, toIso: string): number {
|
|
|
2341
2341
|
return Math.max(0, Math.trunc(to - from));
|
|
2342
2342
|
}
|
|
2343
2343
|
|
|
2344
|
-
function declaredRunDurationBudget(context:
|
|
2344
|
+
function declaredRunDurationBudget(context: KxmRuntimeContext, runId: string): KxmRunDurationBudget | undefined {
|
|
2345
2345
|
const run = context.eventStore.run(runId);
|
|
2346
2346
|
if (!run) return undefined;
|
|
2347
2347
|
try {
|
|
2348
|
-
const envelope =
|
|
2348
|
+
const envelope = loadKxmRunPlanEnvelope(context.eventStore, run);
|
|
2349
2349
|
return effectiveRunDurationBudget(envelope.plan.limits, envelope.projectLimits);
|
|
2350
2350
|
} catch {
|
|
2351
2351
|
return undefined;
|
|
@@ -2353,15 +2353,15 @@ function declaredRunDurationBudget(context: VnextRuntimeContext, runId: string):
|
|
|
2353
2353
|
}
|
|
2354
2354
|
|
|
2355
2355
|
function runDurationOverrunPayload(
|
|
2356
|
-
context:
|
|
2356
|
+
context: KxmRuntimeContext,
|
|
2357
2357
|
runId: string,
|
|
2358
2358
|
nowIso = new Date().toISOString(),
|
|
2359
|
-
): { budgetMs: number; elapsedMs: number; source:
|
|
2359
|
+
): { budgetMs: number; elapsedMs: number; source: KxmRunDurationBudget["source"] } | undefined {
|
|
2360
2360
|
const declared = declaredRunDurationBudget(context, runId);
|
|
2361
2361
|
if (!declared) return undefined;
|
|
2362
2362
|
const run = context.eventStore.run(runId);
|
|
2363
2363
|
if (!run) return undefined;
|
|
2364
|
-
const state =
|
|
2364
|
+
const state = foldStoredKxmRun(context, run);
|
|
2365
2365
|
if (!state.runningSince) return undefined;
|
|
2366
2366
|
if (isTerminalRunStatus(state.status) || state.status === "cancelling") return undefined;
|
|
2367
2367
|
const elapsedMs = elapsedMsBetween(state.runningSince, nowIso);
|
|
@@ -2369,15 +2369,15 @@ function runDurationOverrunPayload(
|
|
|
2369
2369
|
return { budgetMs: declared.budgetMs, elapsedMs, source: declared.source };
|
|
2370
2370
|
}
|
|
2371
2371
|
|
|
2372
|
-
function cancelRunDurationIfExceeded(context:
|
|
2373
|
-
if (
|
|
2372
|
+
function cancelRunDurationIfExceeded(context: KxmRuntimeContext, runId: string): KxmRunDriveResult | undefined {
|
|
2373
|
+
if (isKxmRuntimeContextClosed(context)) return undefined;
|
|
2374
2374
|
const payload = runDurationOverrunPayload(context, runId);
|
|
2375
2375
|
if (!payload) return undefined;
|
|
2376
|
-
|
|
2377
|
-
return { state:
|
|
2376
|
+
cancelKxmRun(context, runId, { reason: "budget_run_duration", budget: payload });
|
|
2377
|
+
return { state: foldStoredKxmRun(context, requireRun(context, runId)) };
|
|
2378
2378
|
}
|
|
2379
2379
|
|
|
2380
|
-
function driveDeadlineAt(context:
|
|
2380
|
+
function driveDeadlineAt(context: KxmRuntimeContext, runId: string, state: KxmRunState): string | undefined {
|
|
2381
2381
|
const declared = declaredRunDurationBudget(context, runId);
|
|
2382
2382
|
if (!declared || !state.runningSince) return undefined;
|
|
2383
2383
|
const start = Date.parse(state.runningSince);
|
|
@@ -2386,12 +2386,12 @@ function driveDeadlineAt(context: VnextRuntimeContext, runId: string, state: Vne
|
|
|
2386
2386
|
}
|
|
2387
2387
|
|
|
2388
2388
|
function driveReceiptBudget(
|
|
2389
|
-
context:
|
|
2389
|
+
context: KxmRuntimeContext,
|
|
2390
2390
|
runId: string,
|
|
2391
|
-
state:
|
|
2392
|
-
events: readonly
|
|
2391
|
+
state: KxmRunState,
|
|
2392
|
+
events: readonly KxmRunEvent[],
|
|
2393
2393
|
closedAt: string,
|
|
2394
|
-
):
|
|
2394
|
+
): KxmDriveReceiptBudget | null {
|
|
2395
2395
|
const declared = declaredRunDurationBudget(context, runId);
|
|
2396
2396
|
if (!declared) return null;
|
|
2397
2397
|
const elapsedMs = state.runningSince ? elapsedMsBetween(state.runningSince, closedAt) : 0;
|
|
@@ -2406,7 +2406,7 @@ function driveReceiptBudget(
|
|
|
2406
2406
|
};
|
|
2407
2407
|
}
|
|
2408
2408
|
|
|
2409
|
-
function cancelReasonFromLog(context:
|
|
2409
|
+
function cancelReasonFromLog(context: KxmRuntimeContext, runId: string): string {
|
|
2410
2410
|
const events = context.eventStore.events(runId, 0, 1_000_000);
|
|
2411
2411
|
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
2412
2412
|
const event = events[index]!;
|
|
@@ -2417,12 +2417,12 @@ function cancelReasonFromLog(context: VnextRuntimeContext, runId: string): strin
|
|
|
2417
2417
|
return "operator_cancel";
|
|
2418
2418
|
}
|
|
2419
2419
|
|
|
2420
|
-
function armRunDurationBudgetTimer(context:
|
|
2420
|
+
function armRunDurationBudgetTimer(context: KxmRuntimeContext, runId: string): () => void {
|
|
2421
2421
|
const declared = declaredRunDurationBudget(context, runId);
|
|
2422
2422
|
if (!declared) return () => undefined;
|
|
2423
2423
|
const run = context.eventStore.run(runId);
|
|
2424
2424
|
if (!run) return () => undefined;
|
|
2425
|
-
const state =
|
|
2425
|
+
const state = foldStoredKxmRun(context, run);
|
|
2426
2426
|
if (!state.runningSince) return () => undefined;
|
|
2427
2427
|
const startMs = Date.parse(state.runningSince);
|
|
2428
2428
|
if (!Number.isFinite(startMs)) return () => undefined;
|
|
@@ -2431,7 +2431,7 @@ function armRunDurationBudgetTimer(context: VnextRuntimeContext, runId: string):
|
|
|
2431
2431
|
let cleared = false;
|
|
2432
2432
|
const fire = (): void => {
|
|
2433
2433
|
timer = undefined;
|
|
2434
|
-
if (cleared ||
|
|
2434
|
+
if (cleared || isKxmRuntimeContextClosed(context)) return;
|
|
2435
2435
|
const nowIso = new Date().toISOString();
|
|
2436
2436
|
const elapsedMs = elapsedMsBetween(state.runningSince!, nowIso);
|
|
2437
2437
|
if (elapsedMs < declared.budgetMs) {
|
|
@@ -2439,7 +2439,7 @@ function armRunDurationBudgetTimer(context: VnextRuntimeContext, runId: string):
|
|
|
2439
2439
|
return;
|
|
2440
2440
|
}
|
|
2441
2441
|
try {
|
|
2442
|
-
|
|
2442
|
+
cancelKxmRun(context, runId, {
|
|
2443
2443
|
reason: "budget_run_duration",
|
|
2444
2444
|
budget: { budgetMs: declared.budgetMs, elapsedMs, source: declared.source },
|
|
2445
2445
|
});
|
|
@@ -2453,7 +2453,7 @@ function armRunDurationBudgetTimer(context: VnextRuntimeContext, runId: string):
|
|
|
2453
2453
|
timer = setTimeout(fire, delay);
|
|
2454
2454
|
timer.unref();
|
|
2455
2455
|
};
|
|
2456
|
-
const unhook =
|
|
2456
|
+
const unhook = registerKxmRuntimeCloseHook(context, () => {
|
|
2457
2457
|
cleared = true;
|
|
2458
2458
|
if (timer !== undefined) clearTimeout(timer);
|
|
2459
2459
|
timer = undefined;
|
|
@@ -2468,7 +2468,7 @@ function armRunDurationBudgetTimer(context: VnextRuntimeContext, runId: string):
|
|
|
2468
2468
|
};
|
|
2469
2469
|
}
|
|
2470
2470
|
|
|
2471
|
-
function unsupportedLimit(envelope:
|
|
2471
|
+
function unsupportedLimit(envelope: KxmRunPlanEnvelope): KxmRunHandoff | undefined {
|
|
2472
2472
|
if (envelope.plan.limits.maxAgentTimeMs !== undefined) {
|
|
2473
2473
|
return { reason: "limit_unsupported", field: "limits.maxAgentTimeMs", detail: "agent-time budget enforcement is not available in this slice" };
|
|
2474
2474
|
}
|
|
@@ -2479,10 +2479,10 @@ function unsupportedLimit(envelope: VnextRunPlanEnvelope): VnextRunHandoff | und
|
|
|
2479
2479
|
}
|
|
2480
2480
|
|
|
2481
2481
|
function unsupportedStep(
|
|
2482
|
-
plan:
|
|
2483
|
-
step:
|
|
2482
|
+
plan: KxmCompiledPlan,
|
|
2483
|
+
step: KxmCompiledStep,
|
|
2484
2484
|
producerId?: "driver-simulated" | "pi" | string,
|
|
2485
|
-
): Omit<
|
|
2485
|
+
): Omit<KxmRunHandoff, "stepId"> | undefined {
|
|
2486
2486
|
if (step.kind === "gate") throw runtimeError("run_plan_corrupt", plan.workflowId, `unsupportedStep called on gate without envelope; use unsupportedGateStep instead`);
|
|
2487
2487
|
if (step.kind !== "agent" && step.kind !== "moa" && step.kind !== "approval" && step.kind !== "wait") {
|
|
2488
2488
|
return { reason: "step_unsupported", field: "kind", detail: `step kind ${step.kind} is not executed in this slice` };
|
|
@@ -2531,7 +2531,7 @@ function unsupportedStep(
|
|
|
2531
2531
|
return undefined;
|
|
2532
2532
|
}
|
|
2533
2533
|
|
|
2534
|
-
function unsupportedGateStep(plan:
|
|
2534
|
+
function unsupportedGateStep(plan: KxmCompiledPlan, step: KxmCompiledStep & { kind: "gate" }, envelope: KxmRunPlanEnvelope, context?: { projectRoot: string }): Omit<KxmRunHandoff, "stepId"> | undefined {
|
|
2535
2535
|
if (!step.gate) return { reason: "step_unsupported", field: "gate", detail: "gate step is missing gate id" };
|
|
2536
2536
|
if (envelope.gates.registry === null) {
|
|
2537
2537
|
return { reason: "step_unsupported", field: "gate", detail: `step ${step.id} refers to a gate but registry is null` };
|
|
@@ -2672,15 +2672,15 @@ function unsupportedGateStep(plan: VnextCompiledPlan, step: VnextCompiledStep &
|
|
|
2672
2672
|
}
|
|
2673
2673
|
|
|
2674
2674
|
function pinnedGatesForCompiledPlan(
|
|
2675
|
-
plan:
|
|
2676
|
-
bundle:
|
|
2675
|
+
plan: KxmCompiledPlan,
|
|
2676
|
+
bundle: KxmProjectBundle,
|
|
2677
2677
|
projectRoot: string,
|
|
2678
|
-
):
|
|
2678
|
+
): KxmPinnedGates {
|
|
2679
2679
|
const referenced = new Set<string>();
|
|
2680
2680
|
for (const step of Object.values(plan.steps)) {
|
|
2681
2681
|
if (step.kind === "gate") referenced.add(step.gate);
|
|
2682
2682
|
}
|
|
2683
|
-
const definitions = Object.create(null) as
|
|
2683
|
+
const definitions = Object.create(null) as KxmPinnedGates["definitions"] extends Readonly<infer T> ? T : Record<string, never>;
|
|
2684
2684
|
const registryValue = bundle.gateRegistry?.value;
|
|
2685
2685
|
const gatesObject = registryValue && typeof registryValue.gates === "object" && registryValue.gates && !Array.isArray(registryValue.gates)
|
|
2686
2686
|
? registryValue.gates as Record<string, unknown>
|
|
@@ -2694,7 +2694,7 @@ function pinnedGatesForCompiledPlan(
|
|
|
2694
2694
|
: bundle.gateRegistry && referenced.size === 0
|
|
2695
2695
|
? { schema: "kxm.gate-registry.v1", hash: gateRegistryHash(bundle.gateRegistry.value) }
|
|
2696
2696
|
: null,
|
|
2697
|
-
definitions: definitions as
|
|
2697
|
+
definitions: definitions as KxmPinnedGates["definitions"],
|
|
2698
2698
|
controlRoot: { repositoryId: "control", projectKey: projectRuntimeKey(projectRoot) },
|
|
2699
2699
|
};
|
|
2700
2700
|
}
|
|
@@ -2704,7 +2704,7 @@ export interface GateRecoveryPreflightResult {
|
|
|
2704
2704
|
blocking: Array<{ runId: string; attemptId: string }>;
|
|
2705
2705
|
}
|
|
2706
2706
|
|
|
2707
|
-
export function gateRecoveryPreflight(context:
|
|
2707
|
+
export function gateRecoveryPreflight(context: KxmRuntimeContext): GateRecoveryPreflightResult {
|
|
2708
2708
|
const attempts = context.eventStore.gateAttemptsForProject(context.projectId);
|
|
2709
2709
|
const capabilities = context.eventStore.issuedOrRevokedCapabilities();
|
|
2710
2710
|
const seenAttempts = new Set<string>();
|
|
@@ -2750,9 +2750,9 @@ export function gateRecoveryPreflight(context: VnextRuntimeContext): GateRecover
|
|
|
2750
2750
|
}
|
|
2751
2751
|
|
|
2752
2752
|
function classifyRecoverableGateAttempt(
|
|
2753
|
-
context:
|
|
2754
|
-
row:
|
|
2755
|
-
capability:
|
|
2753
|
+
context: KxmRuntimeContext,
|
|
2754
|
+
row: KxmGateAttemptRow,
|
|
2755
|
+
capability: KxmAttemptCapabilityRow | undefined,
|
|
2756
2756
|
): "excluded" | "owned" | "blocking" {
|
|
2757
2757
|
if (row.producerId !== "kxm-gate") {
|
|
2758
2758
|
throw runtimeError(
|
|
@@ -2789,7 +2789,7 @@ function classifyRecoverableGateAttempt(
|
|
|
2789
2789
|
}
|
|
2790
2790
|
let state;
|
|
2791
2791
|
try {
|
|
2792
|
-
state =
|
|
2792
|
+
state = foldStoredKxmRun(context, run);
|
|
2793
2793
|
} catch (error) {
|
|
2794
2794
|
throw runtimeError(
|
|
2795
2795
|
"gate_recovery_corrupt",
|
|
@@ -2832,10 +2832,10 @@ function classifyRecoverableGateAttempt(
|
|
|
2832
2832
|
&& current.assignmentId === row.assignmentId
|
|
2833
2833
|
&& current.effectId === row.effectId
|
|
2834
2834
|
&& current.stepId === row.stepId;
|
|
2835
|
-
const controller =
|
|
2835
|
+
const controller = kxmAttemptController(context.eventStore.path, row.runId, row.attemptId);
|
|
2836
2836
|
if (row.gateKind === "command") {
|
|
2837
|
-
const hold =
|
|
2838
|
-
const admittedToken =
|
|
2837
|
+
const hold = kxmGateHold(context.eventStore.path, row.runId);
|
|
2838
|
+
const admittedToken = kxmAdmittedToken(context.eventStore.path, row.runId);
|
|
2839
2839
|
if (hold && hold.attemptId !== row.attemptId) {
|
|
2840
2840
|
throw runtimeError("gate_recovery_corrupt", row.attemptId, "gate recovery preflight: gate hold does not match the attempt");
|
|
2841
2841
|
}
|
|
@@ -2874,8 +2874,8 @@ function gateRowIdentityMatches(
|
|
|
2874
2874
|
|
|
2875
2875
|
function isEvaluatedSettledProof(
|
|
2876
2876
|
row: { attemptId: string; assignmentId: string; effectId: string; stepId: string; runId: string },
|
|
2877
|
-
observation:
|
|
2878
|
-
evidence:
|
|
2877
|
+
observation: KxmGateObservationRow | undefined,
|
|
2878
|
+
evidence: KxmGateEvidenceRow | undefined,
|
|
2879
2879
|
): boolean {
|
|
2880
2880
|
if (!evidence || !observation) return false;
|
|
2881
2881
|
if (!gateRowIdentityMatches(row, observation) || !gateRowIdentityMatches(row, evidence)) return false;
|
|
@@ -2884,9 +2884,9 @@ function isEvaluatedSettledProof(
|
|
|
2884
2884
|
}
|
|
2885
2885
|
|
|
2886
2886
|
function proofOnlySettledEvent(
|
|
2887
|
-
events: readonly
|
|
2887
|
+
events: readonly KxmRunEvent[],
|
|
2888
2888
|
row: { attemptId: string; assignmentId: string; effectId: string; stepId: string; runId: string },
|
|
2889
|
-
):
|
|
2889
|
+
): KxmRunEvent | undefined {
|
|
2890
2890
|
return events.find((event) => {
|
|
2891
2891
|
if (event.eventType !== "effect.settled" || event.runId !== row.runId) return false;
|
|
2892
2892
|
if (
|
|
@@ -2903,11 +2903,11 @@ function proofOnlySettledEvent(
|
|
|
2903
2903
|
}
|
|
2904
2904
|
|
|
2905
2905
|
function isExcludedTerminalProof(
|
|
2906
|
-
state:
|
|
2906
|
+
state: KxmRunState,
|
|
2907
2907
|
row: { attemptId: string; assignmentId: string; effectId: string; stepId: string; runId: string; stepAttempt?: number },
|
|
2908
|
-
observation:
|
|
2909
|
-
evidence:
|
|
2910
|
-
events: readonly
|
|
2908
|
+
observation: KxmGateObservationRow | undefined,
|
|
2909
|
+
evidence: KxmGateEvidenceRow | undefined,
|
|
2910
|
+
events: readonly KxmRunEvent[],
|
|
2911
2911
|
): boolean {
|
|
2912
2912
|
if (evidence) return false;
|
|
2913
2913
|
if (!observation) return false;
|
|
@@ -2944,12 +2944,12 @@ function isExcludedTerminalProof(
|
|
|
2944
2944
|
}
|
|
2945
2945
|
|
|
2946
2946
|
function settlePreparedGate(
|
|
2947
|
-
context:
|
|
2948
|
-
prepared:
|
|
2949
|
-
observation:
|
|
2947
|
+
context: KxmRuntimeContext,
|
|
2948
|
+
prepared: KxmPreparedGateDispatch,
|
|
2949
|
+
observation: KxmGateObservationInput,
|
|
2950
2950
|
) {
|
|
2951
2951
|
const run = requireRun(context, prepared.run.runId);
|
|
2952
|
-
const folded =
|
|
2952
|
+
const folded = foldStoredKxmRun(context, run);
|
|
2953
2953
|
const current = folded.currentStep;
|
|
2954
2954
|
if (
|
|
2955
2955
|
!current
|
|
@@ -2981,11 +2981,11 @@ function settlePreparedGate(
|
|
|
2981
2981
|
const settled = cancelled
|
|
2982
2982
|
? recordGateCancelObservedInTransaction(context, run, prepared.envelope, prepared.stepId, observation)
|
|
2983
2983
|
: recordGateSettlementInTransaction(context, run, prepared.envelope, prepared.stepId, observation);
|
|
2984
|
-
|
|
2984
|
+
kxmGateDispatchSeams.afterObservationWrite?.(prepared);
|
|
2985
2985
|
return settled;
|
|
2986
2986
|
}
|
|
2987
2987
|
|
|
2988
|
-
function requireRun(context:
|
|
2988
|
+
function requireRun(context: KxmRuntimeContext, runId: string): KxmRunRecord {
|
|
2989
2989
|
const run = context.eventStore.run(runId);
|
|
2990
2990
|
if (!run) throw runtimeError("run_unknown", runId, "run does not exist in this event store");
|
|
2991
2991
|
if (run.projectId !== context.projectId || run.homeRuntimeId !== context.homeRuntimeId) {
|
|
@@ -2994,20 +2994,20 @@ function requireRun(context: VnextRuntimeContext, runId: string): VnextRunRecord
|
|
|
2994
2994
|
return run;
|
|
2995
2995
|
}
|
|
2996
2996
|
|
|
2997
|
-
export interface
|
|
2997
|
+
export interface KxmGateRecoveryRequest {
|
|
2998
2998
|
action: "retry" | "fail" | "cancel" | "unblock";
|
|
2999
2999
|
reason?: string;
|
|
3000
3000
|
commandId?: string;
|
|
3001
3001
|
}
|
|
3002
3002
|
|
|
3003
|
-
export function
|
|
3004
|
-
context:
|
|
3003
|
+
export function recoverKxmRun(
|
|
3004
|
+
context: KxmRuntimeContext,
|
|
3005
3005
|
runId: string,
|
|
3006
|
-
request:
|
|
3007
|
-
): { state:
|
|
3006
|
+
request: KxmGateRecoveryRequest,
|
|
3007
|
+
): { state: KxmRunState; unblocked: boolean } {
|
|
3008
3008
|
return context.eventStore.transaction(() => {
|
|
3009
3009
|
const run = requireRun(context, runId);
|
|
3010
|
-
let state =
|
|
3010
|
+
let state = foldStoredKxmRun(context, run);
|
|
3011
3011
|
|
|
3012
3012
|
if (state.status !== "blocked_uncertain" && state.status !== "cancelling" && state.currentStep?.effectState !== "blocked_uncertain") {
|
|
3013
3013
|
throw runtimeError("run_events_illegal", runId, `run ${runId} is not in blocked_uncertain status`);
|
|
@@ -3018,17 +3018,17 @@ export function recoverVnextRun(
|
|
|
3018
3018
|
|
|
3019
3019
|
const now = new Date().toISOString();
|
|
3020
3020
|
let sequence = context.eventStore.nextSequence(runId);
|
|
3021
|
-
let mono =
|
|
3022
|
-
const events:
|
|
3021
|
+
let mono = kxmMonotonicNs();
|
|
3022
|
+
const events: KxmRunEvent[] = [];
|
|
3023
3023
|
const push = (eventType: string, payload: Record<string, unknown>): void => {
|
|
3024
3024
|
events.push({
|
|
3025
|
-
...
|
|
3026
|
-
eventId:
|
|
3025
|
+
...kxmEventBase(context, run, now, mono, request.commandId),
|
|
3026
|
+
eventId: newKxmEventId(),
|
|
3027
3027
|
eventType,
|
|
3028
3028
|
sequence: sequence++,
|
|
3029
3029
|
payload,
|
|
3030
3030
|
});
|
|
3031
|
-
mono =
|
|
3031
|
+
mono = kxmIncrementMonotonicNs(mono);
|
|
3032
3032
|
};
|
|
3033
3033
|
|
|
3034
3034
|
if (state.status === "running" && state.currentStep?.effectState === "blocked_uncertain") {
|
|
@@ -3039,14 +3039,14 @@ export function recoverVnextRun(
|
|
|
3039
3039
|
});
|
|
3040
3040
|
for (const ev of events) context.eventStore.appendEvent(ev);
|
|
3041
3041
|
events.length = 0;
|
|
3042
|
-
state =
|
|
3042
|
+
state = foldStoredKxmRun(context, run);
|
|
3043
3043
|
context.eventStore.updateRunStatus(runId, "blocked_uncertain", now);
|
|
3044
3044
|
}
|
|
3045
3045
|
|
|
3046
3046
|
const attemptId = state.currentStep?.attemptId;
|
|
3047
3047
|
if (attemptId) {
|
|
3048
|
-
|
|
3049
|
-
|
|
3048
|
+
resolveKxmGateHold(context.eventStore.path, runId, attemptId);
|
|
3049
|
+
unregisterKxmAttemptController(context.eventStore.path, runId, attemptId);
|
|
3050
3050
|
const cap = context.eventStore.capabilityByAttempt(attemptId);
|
|
3051
3051
|
if (cap && cap.state !== "settled") {
|
|
3052
3052
|
context.eventStore.settleCapability(attemptId, "settled");
|
|
@@ -3060,8 +3060,8 @@ export function recoverVnextRun(
|
|
|
3060
3060
|
reason: request.reason ?? "operator_retry",
|
|
3061
3061
|
});
|
|
3062
3062
|
for (const ev of events) context.eventStore.appendEvent(ev);
|
|
3063
|
-
const nextState =
|
|
3064
|
-
|
|
3063
|
+
const nextState = foldStoredKxmRun(context, run);
|
|
3064
|
+
persistKxmRunState(context, runId, nextState, events[events.length - 1]!.sequence);
|
|
3065
3065
|
context.eventStore.updateRunStatus(runId, "running", now);
|
|
3066
3066
|
return { state: nextState, unblocked: true };
|
|
3067
3067
|
}
|
|
@@ -3073,8 +3073,8 @@ export function recoverVnextRun(
|
|
|
3073
3073
|
reason: request.reason ?? "operator_fail",
|
|
3074
3074
|
});
|
|
3075
3075
|
for (const ev of events) context.eventStore.appendEvent(ev);
|
|
3076
|
-
const nextState =
|
|
3077
|
-
|
|
3076
|
+
const nextState = foldStoredKxmRun(context, run);
|
|
3077
|
+
persistKxmRunState(context, runId, nextState, events[events.length - 1]!.sequence);
|
|
3078
3078
|
context.eventStore.updateRunStatus(runId, "failed", now);
|
|
3079
3079
|
return { state: nextState, unblocked: true };
|
|
3080
3080
|
}
|
|
@@ -3095,8 +3095,8 @@ export function recoverVnextRun(
|
|
|
3095
3095
|
reason: request.reason ?? "operator_cancel",
|
|
3096
3096
|
});
|
|
3097
3097
|
for (const ev of events) context.eventStore.appendEvent(ev);
|
|
3098
|
-
const nextState =
|
|
3099
|
-
|
|
3098
|
+
const nextState = foldStoredKxmRun(context, run);
|
|
3099
|
+
persistKxmRunState(context, runId, nextState, events[events.length - 1]!.sequence);
|
|
3100
3100
|
context.eventStore.updateRunStatus(runId, "cancelled", now);
|
|
3101
3101
|
return { state: nextState, unblocked: true };
|
|
3102
3102
|
}
|