@kontextmind/kxm 0.6.0
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 +19 -0
- package/.kxm/README.md +14 -0
- package/.kxm/assets/README.md +5 -0
- package/.kxm/assets/retrospectives/README.md +5 -0
- package/.kxm/config/README.md +5 -0
- package/.kxm/config/agents.json +43 -0
- package/.kxm/config/env.example +56 -0
- package/.kxm/config/update.example.yaml +9 -0
- package/.kxm/config/workflows/fix.json +160 -0
- package/.kxm/config/workflows/jira-development.json +116 -0
- package/.kxm/config/workflows/provenance-quorum.json +150 -0
- package/.kxm/config/workflows/v04-dogfood.json +72 -0
- package/CHANGELOG.md +465 -0
- package/LICENSE +21 -0
- package/README.md +306 -0
- package/SECURITY.md +72 -0
- package/docs/README.md +48 -0
- package/docs/agent-communication-envelopes-and-gates.md +553 -0
- package/docs/architecture.md +242 -0
- package/docs/assignment-runner.md +241 -0
- package/docs/configuration.md +361 -0
- package/docs/continuous-improvement.md +114 -0
- package/docs/getting-started.md +253 -0
- package/docs/kxm-handbook.md +1090 -0
- package/docs/operations.md +205 -0
- package/docs/provenance-gates.md +291 -0
- package/docs/skills.md +45 -0
- package/docs/templates/README.md +95 -0
- package/docs/templates/adr.md +88 -0
- package/docs/templates/architecture.md +120 -0
- package/docs/templates/bug-fix.md +109 -0
- package/docs/templates/feature.md +108 -0
- package/docs/templates/handoff.md +72 -0
- package/docs/templates/postmortem.md +77 -0
- package/docs/templates/research.md +100 -0
- package/docs/templates/review.md +85 -0
- package/docs/templates/runbook.md +73 -0
- package/docs/templates/test-plan.md +87 -0
- package/docs/templates/test-report.md +72 -0
- package/docs/test-matrix.md +121 -0
- package/docs/troubleshooting.md +249 -0
- package/docs/vnext/README.md +62 -0
- package/docs/vnext/architecture.md +185 -0
- package/docs/vnext/effects-and-recovery.md +172 -0
- package/docs/vnext/lifecycles.md +235 -0
- package/docs/vnext/migration.md +220 -0
- package/docs/vnext/routing.md +184 -0
- package/docs/vnext/synchronization.md +172 -0
- package/docs/vnext/terminology.md +240 -0
- package/docs/vnext/validation.md +335 -0
- package/docs/webhook-workflows.md +240 -0
- package/docs/workflow-guide.md +1150 -0
- package/examples/README.md +102 -0
- package/examples/provenance-workflow.json +40 -0
- package/examples/requester.ts +30 -0
- package/examples/reviewer-agent.ts +29 -0
- package/examples/roundtrip.ts +46 -0
- package/examples/vnext/.kxm/agents/coordinator.yaml +16 -0
- package/examples/vnext/.kxm/agents/critic-1.yaml +16 -0
- package/examples/vnext/.kxm/agents/critic-2.yaml +15 -0
- package/examples/vnext/.kxm/agents/critic-3.yaml +15 -0
- package/examples/vnext/.kxm/agents/implementer.yaml +15 -0
- package/examples/vnext/.kxm/agents/planner.yaml +13 -0
- package/examples/vnext/.kxm/agents/reproducer.yaml +15 -0
- package/examples/vnext/.kxm/agents/reviewer.yaml +15 -0
- package/examples/vnext/.kxm/gates.yaml +8 -0
- package/examples/vnext/.kxm/models/critic-claude.yaml +11 -0
- package/examples/vnext/.kxm/models/critic-gemini.yaml +11 -0
- package/examples/vnext/.kxm/models/critic-grok.yaml +12 -0
- package/examples/vnext/.kxm/models/implementation.yaml +14 -0
- package/examples/vnext/.kxm/models/primary.yaml +17 -0
- package/examples/vnext/.kxm/prices.yaml +111 -0
- package/examples/vnext/.kxm/project/env.yaml +7 -0
- package/examples/vnext/.kxm/project.yaml +32 -0
- package/examples/vnext/.kxm/repo/repo.yaml +8 -0
- package/examples/vnext/.kxm/workflows/default.yaml +92 -0
- package/examples/vnext/.kxm/workflows/fix.yaml +376 -0
- package/examples/vnext/.kxm/workflows/improve.yaml +57 -0
- package/examples/vnext/README.md +53 -0
- package/examples/vnext/records/assignment-result-recorded.json +63 -0
- package/examples/vnext/records/assignment-result.json +46 -0
- package/examples/vnext/records/context-candidate.json +42 -0
- package/examples/vnext/records/delivery-manifest.json +66 -0
- package/examples/vnext/records/effect-uncertainty-resolved-sync.json +67 -0
- package/examples/vnext/records/effect-uncertainty-resolved.json +62 -0
- package/examples/vnext/records/run-created.json +54 -0
- package/examples/vnext/records/sync-event.json +65 -0
- package/examples/vnext/repositories/api/.kxm/repo/env.yaml +7 -0
- package/examples/vnext/repositories/api/.kxm/repo/repo.yaml +8 -0
- package/examples/vnext/repositories/web/.kxm/repo/repo.yaml +8 -0
- package/examples/workflow-signal.ts +63 -0
- package/package.json +129 -0
- package/plugins/kxm/.claude-plugin/plugin.json +73 -0
- package/plugins/kxm/.mcp.json +19 -0
- package/plugins/kxm/README.md +93 -0
- package/plugins/kxm/dist/cli.js +42853 -0
- package/plugins/kxm/dist/client.js +416 -0
- package/plugins/kxm/dist/core.js +1823 -0
- package/plugins/kxm/dist/extension.js +3797 -0
- package/plugins/kxm/dist/mcp-server.js +17104 -0
- package/plugins/kxm/dist/runtime.js +23361 -0
- package/plugins/kxm/dist/server.js +13640 -0
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +21109 -0
- package/plugins/kxm/package.json +12 -0
- package/plugins/kxm/skills/kxm/SKILL.md +97 -0
- package/plugins/kxm/skills/kxm/references/protocol.md +103 -0
- package/plugins/kxm/skills/kxm-session/SKILL.md +53 -0
- package/plugins/kxm/src/arbiter.ts +355 -0
- package/plugins/kxm/src/artifacts-exist.ts +62 -0
- package/plugins/kxm/src/autocomplete.ts +236 -0
- package/plugins/kxm/src/cli.ts +3707 -0
- package/plugins/kxm/src/client.ts +614 -0
- package/plugins/kxm/src/commands.ts +1063 -0
- package/plugins/kxm/src/config.ts +290 -0
- package/plugins/kxm/src/context/providers.ts +101 -0
- package/plugins/kxm/src/context-packet.ts +332 -0
- package/plugins/kxm/src/context.ts +499 -0
- package/plugins/kxm/src/core.ts +6 -0
- package/plugins/kxm/src/database.ts +563 -0
- package/plugins/kxm/src/diagnostics.ts +184 -0
- package/plugins/kxm/src/envelope.ts +118 -0
- package/plugins/kxm/src/extension.ts +895 -0
- package/plugins/kxm/src/external-effects.ts +299 -0
- package/plugins/kxm/src/github-watch.ts +255 -0
- package/plugins/kxm/src/hub-binding.ts +160 -0
- package/plugins/kxm/src/hub.ts +2502 -0
- package/plugins/kxm/src/improve.ts +383 -0
- package/plugins/kxm/src/inbox.ts +10 -0
- package/plugins/kxm/src/kxm-install-kind.ts +113 -0
- package/plugins/kxm/src/kxm-update-config.ts +39 -0
- package/plugins/kxm/src/kxm-update.ts +238 -0
- package/plugins/kxm/src/local-snapshot.ts +406 -0
- package/plugins/kxm/src/logger.ts +198 -0
- package/plugins/kxm/src/mcp-server.ts +143 -0
- package/plugins/kxm/src/memory.ts +385 -0
- package/plugins/kxm/src/nous-pi.ts +287 -0
- package/plugins/kxm/src/nous-provider.ts +729 -0
- package/plugins/kxm/src/price-calc.ts +87 -0
- package/plugins/kxm/src/prices.ts +121 -0
- package/plugins/kxm/src/protocol.ts +172 -0
- package/plugins/kxm/src/recovery.ts +211 -0
- package/plugins/kxm/src/redact.ts +26 -0
- package/plugins/kxm/src/retrospective.ts +400 -0
- package/plugins/kxm/src/routing.ts +830 -0
- package/plugins/kxm/src/runtime.ts +9 -0
- package/plugins/kxm/src/server.ts +117 -0
- package/plugins/kxm/src/session-work.ts +571 -0
- package/plugins/kxm/src/session.ts +184 -0
- package/plugins/kxm/src/skills.ts +535 -0
- package/plugins/kxm/src/state.ts +326 -0
- package/plugins/kxm/src/store.ts +637 -0
- package/plugins/kxm/src/studio-layout.ts +268 -0
- package/plugins/kxm/src/suggest.ts +162 -0
- package/plugins/kxm/src/task-manager.ts +244 -0
- package/plugins/kxm/src/telemetry.ts +116 -0
- package/plugins/kxm/src/tui.ts +1046 -0
- package/plugins/kxm/src/vnext-bindings.ts +403 -0
- package/plugins/kxm/src/vnext-config.ts +1646 -0
- package/plugins/kxm/src/vnext-engine-artifacts.ts +86 -0
- package/plugins/kxm/src/vnext-engine-command.ts +533 -0
- package/plugins/kxm/src/vnext-engine-compile.ts +722 -0
- package/plugins/kxm/src/vnext-engine-evidence.ts +273 -0
- package/plugins/kxm/src/vnext-engine-fold.ts +1400 -0
- package/plugins/kxm/src/vnext-engine-gate-records.ts +583 -0
- package/plugins/kxm/src/vnext-engine-plan.ts +717 -0
- package/plugins/kxm/src/vnext-engine.ts +2458 -0
- package/plugins/kxm/src/vnext-gate-hash.ts +10 -0
- package/plugins/kxm/src/vnext-harness.ts +1142 -0
- package/plugins/kxm/src/vnext-init.ts +430 -0
- package/plugins/kxm/src/vnext-migrate.ts +1848 -0
- package/plugins/kxm/src/vnext-oneshot-producer.ts +424 -0
- package/plugins/kxm/src/vnext-permission.ts +936 -0
- package/plugins/kxm/src/vnext-pi-producer.ts +628 -0
- package/plugins/kxm/src/vnext-repair.ts +1094 -0
- package/plugins/kxm/src/vnext-runtime-owner.ts +320 -0
- package/plugins/kxm/src/vnext-runtime-store.ts +1560 -0
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +586 -0
- package/plugins/kxm/src/vnext-runtime.ts +663 -0
- package/plugins/kxm/src/vnext-template.ts +247 -0
- package/plugins/kxm/src/wiki.ts +313 -0
- package/plugins/kxm/src/workflow.ts +1548 -0
- package/schemas/vnext/README.md +46 -0
- package/schemas/vnext/agent.schema.json +40 -0
- package/schemas/vnext/assignment-result.schema.json +66 -0
- package/schemas/vnext/backup-manifest.schema.json +89 -0
- package/schemas/vnext/candidate.schema.json +109 -0
- package/schemas/vnext/common.schema.json +422 -0
- package/schemas/vnext/context-candidate.schema.json +76 -0
- package/schemas/vnext/context-packet.schema.json +192 -0
- package/schemas/vnext/delivery-manifest.schema.json +159 -0
- package/schemas/vnext/environment.schema.json +66 -0
- package/schemas/vnext/gate-registry.schema.json +109 -0
- package/schemas/vnext/handoff-manifest.schema.json +146 -0
- package/schemas/vnext/init-operation.schema.json +61 -0
- package/schemas/vnext/local-repository-bindings.schema.json +30 -0
- package/schemas/vnext/memory-record.schema.json +45 -0
- package/schemas/vnext/migration-decision.schema.json +26 -0
- package/schemas/vnext/migration-plan.schema.json +123 -0
- package/schemas/vnext/migration-receipt.schema.json +52 -0
- package/schemas/vnext/model.schema.json +42 -0
- package/schemas/vnext/permission-diff.schema.json +57 -0
- package/schemas/vnext/prices.schema.json +115 -0
- package/schemas/vnext/project.schema.json +85 -0
- package/schemas/vnext/repository.schema.json +24 -0
- package/schemas/vnext/run-event.schema.json +460 -0
- package/schemas/vnext/session-brief.schema.json +153 -0
- package/schemas/vnext/sync-event.schema.json +234 -0
- package/schemas/vnext/template-provenance.schema.json +38 -0
- package/schemas/vnext/workflow.schema.json +248 -0
- package/scripts/assignment-run.d.mts +354 -0
- package/scripts/assignment-run.mjs +4451 -0
- package/scripts/build-runtime.mjs +56 -0
- package/scripts/check-generated.mjs +77 -0
- package/scripts/check-versions.mjs +34 -0
- package/scripts/emit-codex-artifacts.d.mts +9 -0
- package/scripts/emit-codex-artifacts.mjs +91 -0
- package/scripts/harness-run.d.mts +83 -0
- package/scripts/harness-run.mjs +2095 -0
- package/scripts/kxm-hub.mjs +105 -0
- package/scripts/kxm-publish-npm.mjs +327 -0
- package/scripts/kxm-release-github.mjs +472 -0
- package/scripts/kxm-runtime-supervisor.mjs +7 -0
- package/scripts/kxm-worker.mjs +1127 -0
- package/scripts/kxm.mjs +27 -0
- package/scripts/roster-policy.d.mts +20 -0
- package/scripts/roster-policy.mjs +161 -0
- package/scripts/smoke-multi-pi.mjs +479 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { runtimeError } from "./vnext-runtime-store.ts";
|
|
3
|
+
|
|
4
|
+
export interface VnextOwnedAttempt {
|
|
5
|
+
attemptId: string;
|
|
6
|
+
controller: AbortController;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface VnextGateHoldView {
|
|
10
|
+
attemptId: string;
|
|
11
|
+
token: string;
|
|
12
|
+
state: "active" | "unsettled";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface GateHold {
|
|
16
|
+
attemptId: string;
|
|
17
|
+
token: string;
|
|
18
|
+
state: "active" | "unsettled";
|
|
19
|
+
stop?: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface AdmissionRecord {
|
|
23
|
+
token: string;
|
|
24
|
+
configRevision: string;
|
|
25
|
+
gateHold?: GateHold;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface QueueItem {
|
|
29
|
+
runId: string;
|
|
30
|
+
configRevision: string;
|
|
31
|
+
bound: number;
|
|
32
|
+
start: (token: string) => Promise<unknown>;
|
|
33
|
+
fail: (error: unknown) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface OwnerPolicy {
|
|
37
|
+
bound: number;
|
|
38
|
+
configRevision: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface OwnerRecord {
|
|
42
|
+
handles: number;
|
|
43
|
+
scheduler?: OwnerPolicy;
|
|
44
|
+
implicit?: OwnerPolicy;
|
|
45
|
+
admitted: Map<string, AdmissionRecord>;
|
|
46
|
+
queue: QueueItem[];
|
|
47
|
+
attempts: Map<string, Map<string, VnextOwnedAttempt>>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const owners = new Map<string, OwnerRecord>();
|
|
51
|
+
|
|
52
|
+
function record(storePath: string): OwnerRecord {
|
|
53
|
+
const existing = owners.get(storePath);
|
|
54
|
+
if (existing) return existing;
|
|
55
|
+
const created: OwnerRecord = {
|
|
56
|
+
handles: 0,
|
|
57
|
+
admitted: new Map(),
|
|
58
|
+
queue: [],
|
|
59
|
+
attempts: new Map(),
|
|
60
|
+
};
|
|
61
|
+
owners.set(storePath, created);
|
|
62
|
+
return created;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function maybeDelete(storePath: string, owner: OwnerRecord): void {
|
|
66
|
+
if (owner.handles === 0 && owner.admitted.size === 0 && owner.queue.length === 0 && owner.attempts.size === 0) {
|
|
67
|
+
owners.delete(storePath);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function activePolicy(owner: OwnerRecord): OwnerPolicy | undefined {
|
|
72
|
+
return owner.scheduler ?? owner.implicit;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function clearImplicitIfIdle(owner: OwnerRecord): void {
|
|
76
|
+
if (owner.admitted.size === 0 && owner.queue.length === 0) {
|
|
77
|
+
delete owner.implicit;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function registerVnextRuntimeHandle(storePath: string): void {
|
|
82
|
+
record(storePath).handles += 1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function unregisterVnextRuntimeHandle(storePath: string): void {
|
|
86
|
+
const owner = owners.get(storePath);
|
|
87
|
+
if (!owner) return;
|
|
88
|
+
owner.handles = Math.max(0, owner.handles - 1);
|
|
89
|
+
maybeDelete(storePath, owner);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function vnextRuntimeHandleCount(storePath: string): number {
|
|
93
|
+
return owners.get(storePath)?.handles ?? 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function registerVnextAttemptController(storePath: string, runId: string, attempt: VnextOwnedAttempt): void {
|
|
97
|
+
const owner = record(storePath);
|
|
98
|
+
let runAttempts = owner.attempts.get(runId);
|
|
99
|
+
if (!runAttempts) {
|
|
100
|
+
runAttempts = new Map();
|
|
101
|
+
owner.attempts.set(runId, runAttempts);
|
|
102
|
+
}
|
|
103
|
+
if (runAttempts.has(attempt.attemptId)) {
|
|
104
|
+
throw runtimeError("attempt_controller_duplicate", attempt.attemptId, `attempt ${attempt.attemptId} is already registered`);
|
|
105
|
+
}
|
|
106
|
+
runAttempts.set(attempt.attemptId, attempt);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function vnextAttemptController(storePath: string, runId: string, attemptId: string): VnextOwnedAttempt | undefined {
|
|
110
|
+
return owners.get(storePath)?.attempts.get(runId)?.get(attemptId);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function vnextAttemptControllers(storePath: string, runId: string): VnextOwnedAttempt[] {
|
|
114
|
+
const runAttempts = owners.get(storePath)?.attempts.get(runId);
|
|
115
|
+
if (!runAttempts) return [];
|
|
116
|
+
return [...runAttempts.values()];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function unregisterVnextAttemptController(storePath: string, runId: string, attemptId: string): void {
|
|
120
|
+
const owner = owners.get(storePath);
|
|
121
|
+
if (!owner) return;
|
|
122
|
+
const runAttempts = owner.attempts.get(runId);
|
|
123
|
+
if (!runAttempts) return;
|
|
124
|
+
if (!runAttempts.has(attemptId)) return;
|
|
125
|
+
runAttempts.delete(attemptId);
|
|
126
|
+
if (runAttempts.size === 0) owner.attempts.delete(runId);
|
|
127
|
+
maybeDelete(storePath, owner);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function bindVnextSchedulerPolicy(storePath: string, bound: number, configRevision: string): void {
|
|
131
|
+
const owner = record(storePath);
|
|
132
|
+
const current = activePolicy(owner);
|
|
133
|
+
if (current && (current.configRevision !== configRevision || current.bound !== bound)) {
|
|
134
|
+
if (owner.admitted.size > 0 || owner.queue.length > 0) {
|
|
135
|
+
throw runtimeError("scheduler_policy_conflict", storePath, "queued or admitted work still uses the previous scheduler policy");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
owner.scheduler = { bound, configRevision };
|
|
139
|
+
delete owner.implicit;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function vnextSchedulerPolicy(storePath: string): OwnerPolicy | undefined {
|
|
143
|
+
return owners.get(storePath)?.scheduler;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function admitVnextRun(
|
|
147
|
+
storePath: string,
|
|
148
|
+
runId: string,
|
|
149
|
+
envelopeRevision: string,
|
|
150
|
+
envelopeBound: number,
|
|
151
|
+
): string {
|
|
152
|
+
const owner = record(storePath);
|
|
153
|
+
if (owner.admitted.has(runId)) {
|
|
154
|
+
throw runtimeError("run_busy", runId, `run ${runId} is already admitted`);
|
|
155
|
+
}
|
|
156
|
+
if (owner.queue.some((item) => item.runId === runId)) {
|
|
157
|
+
throw runtimeError("run_busy", runId, `run ${runId} is already queued`);
|
|
158
|
+
}
|
|
159
|
+
const policy = activePolicy(owner);
|
|
160
|
+
if (policy) {
|
|
161
|
+
if (policy.configRevision !== envelopeRevision) {
|
|
162
|
+
throw runtimeError("scheduler_policy_conflict", runId, "run envelope revision does not match the active admission policy");
|
|
163
|
+
}
|
|
164
|
+
if (policy.bound !== envelopeBound) {
|
|
165
|
+
throw runtimeError("scheduler_policy_conflict", runId, "run envelope bound does not match the active admission policy");
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
owner.implicit = { bound: envelopeBound, configRevision: envelopeRevision };
|
|
169
|
+
}
|
|
170
|
+
const bound = activePolicy(owner)!.bound;
|
|
171
|
+
if (owner.admitted.size >= bound) {
|
|
172
|
+
throw runtimeError("run_admission_exceeded", runId, `project already has ${bound} admitted runs`);
|
|
173
|
+
}
|
|
174
|
+
const token = randomUUID();
|
|
175
|
+
owner.admitted.set(runId, { token, configRevision: envelopeRevision });
|
|
176
|
+
return token;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function releaseVnextRun(storePath: string, runId: string, token: string): void {
|
|
180
|
+
const owner = owners.get(storePath);
|
|
181
|
+
if (!owner) return;
|
|
182
|
+
const current = owner.admitted.get(runId);
|
|
183
|
+
if (!current || current.token !== token) return;
|
|
184
|
+
if (current.gateHold) return;
|
|
185
|
+
owner.admitted.delete(runId);
|
|
186
|
+
pump(storePath, owner);
|
|
187
|
+
clearImplicitIfIdle(owner);
|
|
188
|
+
maybeDelete(storePath, owner);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function armVnextGateHold(
|
|
192
|
+
storePath: string,
|
|
193
|
+
runId: string,
|
|
194
|
+
token: string,
|
|
195
|
+
attemptId: string,
|
|
196
|
+
stop: () => void,
|
|
197
|
+
): void {
|
|
198
|
+
const owner = record(storePath);
|
|
199
|
+
const current = owner.admitted.get(runId);
|
|
200
|
+
if (!current || current.token !== token) {
|
|
201
|
+
throw runtimeError("run_events_illegal", runId, "gate hold requires the exact admitted token");
|
|
202
|
+
}
|
|
203
|
+
if (current.gateHold) {
|
|
204
|
+
throw runtimeError("run_events_illegal", runId, "admission already has a gate hold");
|
|
205
|
+
}
|
|
206
|
+
current.gateHold = { attemptId, token, state: "active", stop };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function markVnextGateHoldUnsettled(storePath: string, runId: string, token: string, attemptId: string): void {
|
|
210
|
+
const owner = owners.get(storePath);
|
|
211
|
+
const current = owner?.admitted.get(runId);
|
|
212
|
+
if (!current || current.token !== token) {
|
|
213
|
+
throw runtimeError("run_events_illegal", runId, "unsettled hold requires the exact admitted token");
|
|
214
|
+
}
|
|
215
|
+
const hold = current.gateHold;
|
|
216
|
+
if (!hold || hold.attemptId !== attemptId || hold.token !== token) {
|
|
217
|
+
throw runtimeError("run_events_illegal", runId, "unsettled hold does not match the armed attempt");
|
|
218
|
+
}
|
|
219
|
+
hold.state = "unsettled";
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function finishVnextOwnedGate(storePath: string, runId: string, token: string, attemptId: string): void {
|
|
223
|
+
const owner = owners.get(storePath);
|
|
224
|
+
const current = owner?.admitted.get(runId);
|
|
225
|
+
if (!current || current.token !== token) {
|
|
226
|
+
throw runtimeError("run_events_illegal", runId, "owned gate finish requires the exact admitted token");
|
|
227
|
+
}
|
|
228
|
+
const hold = current.gateHold;
|
|
229
|
+
if (!hold || hold.attemptId !== attemptId || hold.token !== token) {
|
|
230
|
+
throw runtimeError("run_events_illegal", runId, "owned gate finish does not match the armed attempt");
|
|
231
|
+
}
|
|
232
|
+
if (hold.state === "unsettled") {
|
|
233
|
+
throw runtimeError("run_events_illegal", runId, "unsettled gate hold cannot be finished");
|
|
234
|
+
}
|
|
235
|
+
delete current.gateHold;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function dropVnextGateStopHook(storePath: string, runId: string, attemptId: string): void {
|
|
239
|
+
const hold = owners.get(storePath)?.admitted.get(runId)?.gateHold;
|
|
240
|
+
if (!hold || hold.attemptId !== attemptId) return;
|
|
241
|
+
delete hold.stop;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function resolveVnextGateHold(storePath: string, runId: string, attemptId: string): void {
|
|
245
|
+
const owner = owners.get(storePath);
|
|
246
|
+
if (!owner) return;
|
|
247
|
+
const current = owner.admitted.get(runId);
|
|
248
|
+
if (!current) return;
|
|
249
|
+
const hold = current.gateHold;
|
|
250
|
+
if (!hold || hold.attemptId !== attemptId) return;
|
|
251
|
+
try {
|
|
252
|
+
hold.stop?.();
|
|
253
|
+
} catch {
|
|
254
|
+
// ignore cleanup errors
|
|
255
|
+
}
|
|
256
|
+
delete current.gateHold;
|
|
257
|
+
owner.admitted.delete(runId);
|
|
258
|
+
pump(storePath, owner);
|
|
259
|
+
clearImplicitIfIdle(owner);
|
|
260
|
+
maybeDelete(storePath, owner);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function vnextGateHold(storePath: string, runId: string): VnextGateHoldView | undefined {
|
|
264
|
+
const hold = owners.get(storePath)?.admitted.get(runId)?.gateHold;
|
|
265
|
+
if (!hold) return undefined;
|
|
266
|
+
return { attemptId: hold.attemptId, token: hold.token, state: hold.state };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function vnextAdmittedToken(storePath: string, runId: string): string | undefined {
|
|
270
|
+
return owners.get(storePath)?.admitted.get(runId)?.token;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function enqueueVnextScheduledRun(
|
|
274
|
+
storePath: string,
|
|
275
|
+
runId: string,
|
|
276
|
+
configRevision: string,
|
|
277
|
+
bound: number,
|
|
278
|
+
start: (token: string) => Promise<unknown>,
|
|
279
|
+
): Promise<unknown> {
|
|
280
|
+
const owner = record(storePath);
|
|
281
|
+
if (owner.admitted.has(runId) || owner.queue.some((item) => item.runId === runId)) {
|
|
282
|
+
return Promise.reject(runtimeError("run_busy", runId, `run ${runId} is already admitted or queued`));
|
|
283
|
+
}
|
|
284
|
+
return new Promise((resolve, reject) => {
|
|
285
|
+
owner.queue.push({
|
|
286
|
+
runId,
|
|
287
|
+
configRevision,
|
|
288
|
+
bound,
|
|
289
|
+
start: (token) => start(token).then(resolve, reject),
|
|
290
|
+
fail: (error: unknown) => reject(error),
|
|
291
|
+
});
|
|
292
|
+
pump(storePath, owner);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function vnextActiveScheduledRuns(storePath: string): number {
|
|
297
|
+
return owners.get(storePath)?.admitted.size ?? 0;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function vnextQueuedScheduledRuns(storePath: string): number {
|
|
301
|
+
return owners.get(storePath)?.queue.length ?? 0;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function pump(storePath: string, owner: OwnerRecord): void {
|
|
305
|
+
const bound = activePolicy(owner)?.bound ?? 1;
|
|
306
|
+
while (owner.admitted.size < bound && owner.queue.length > 0) {
|
|
307
|
+
const next = owner.queue.shift();
|
|
308
|
+
if (!next) break;
|
|
309
|
+
let token: string;
|
|
310
|
+
try {
|
|
311
|
+
token = admitVnextRun(storePath, next.runId, next.configRevision, next.bound);
|
|
312
|
+
} catch (error) {
|
|
313
|
+
next.fail(error);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
void next.start(token).finally(() => {
|
|
317
|
+
releaseVnextRun(storePath, next.runId, token);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|