@eir-labs/coltrane 0.23.1 → 0.24.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/dist/src/cli.d.ts +1 -1
- package/dist/src/fs_atomic.d.ts +43 -0
- package/dist/src/fs_atomic.js +135 -3
- package/dist/src/fs_atomic.js.map +1 -1
- package/dist/src/gig_tracker.d.ts +10 -0
- package/dist/src/gig_tracker.js +12 -0
- package/dist/src/gig_tracker.js.map +1 -1
- package/dist/src/server.js +113 -9
- package/dist/src/server.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
package/dist/src/cli.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface CliIO {
|
|
|
31
31
|
/** Injected for tests. Production reads stdin for `--input -`. */
|
|
32
32
|
stdin?: () => string;
|
|
33
33
|
}
|
|
34
|
-
export declare const USAGE = "coltrane 0.
|
|
34
|
+
export declare const USAGE = "coltrane 0.24.0\n\n coltrane validate load the genome; exit non-zero on load errors\n coltrane dispatch <standard> run a standard\n coltrane monitor <gig-id> report a gig's progress\n coltrane logs <gig-id> per-chair logs for a gig\n coltrane abort <gig-id> stop a running gig\n coltrane trace <output-id> walk an output's provenance\n coltrane simulate <standard> cost/shape a standard without running it\n coltrane health engine + store health\n coltrane serve run the MCP server on stdio\n coltrane enqueue <standard> queue a gig on the LOCAL file queue (no store, no key)\n (env: COLTRANE_QUEUE_DIR \u2014 its absence is the whole\n difference between local and hosted)\n coltrane work claim one queued gig from the org store and run it\n coltrane work --check report whether this box's drain environment is configured\n and exit without claiming (0 ready, 1 not ready)\n (env: COLTRANE_STORE_URL, COLTRANE_STORE_ANON,\n COLTRANE_AGENT_TOKEN; results drain via\n COLTRANE_DRAIN_URL + COLTRANE_DRAIN_KEY;\n checkpoints under COLTRANE_WORKER_CHECKPOINTS,\n default ~/.coltrane/worker-checkpoints;\n exit 0 complete or parked, 1 failed, 3 queue empty)\n\nOptions\n --input <json|@file|-> dispatch payload; @file reads a file, - reads stdin\n --depth <skim|standard|deep> tighten the per-chair turn cap\n --budget <dollars> per-gig ceiling; the run stops when it is gone\n --reuse allow chair-level reuse of prior sealed outputs\n --resume <gig-id> continue a gig that died mid-pipeline\n --approve <role>=<json|@file> a human chair's verdict; repeat once per chair\n --as <name> who is approving; sealed as the verdict's author\n --wait block until the run finishes\n --follow poll until the gig reaches a terminal state\n --direction <upstream|downstream|both> for trace\n --json print the raw result to stdout\n --genome <path> genome root (default: $COLTRANE_GENOME or cwd)\n --help, --version\n\nA dispatch that reaches a chair a HUMAN holds parks: it names the waiting chair and exits 0,\nbecause a gig waiting on a person is not a failed gig. Approve it on the resume \u2014\n\n coltrane dispatch <standard> --resume <gig> \\\n --approve approve=@verdict.json --as eugene\n\n--input is NOT required on an approve-only resume: when every remaining chair is human the\ncheckpoint's recorded payload stands and the omission inherits it. If you DO pass --input it is\nchecked against the checkpoint, and a disagreement still refuses the resume.\n";
|
|
35
35
|
interface Parsed {
|
|
36
36
|
cmd: string | undefined;
|
|
37
37
|
positional: string[];
|
package/dist/src/fs_atomic.d.ts
CHANGED
|
@@ -1 +1,44 @@
|
|
|
1
1
|
export declare function writeFileAtomic(file: string, text: string): void;
|
|
2
|
+
/** The lock record — names WHO holds the tree, so a refusal can say who to wait on or abort. */
|
|
3
|
+
export interface RepoLockRecord {
|
|
4
|
+
gig_id: string;
|
|
5
|
+
pid: number;
|
|
6
|
+
started_at: string;
|
|
7
|
+
genome_dir: string;
|
|
8
|
+
}
|
|
9
|
+
/** The outcome of a claim: the tree, or the live holder that refused it. */
|
|
10
|
+
export type RepoLockAcquire = {
|
|
11
|
+
ok: true;
|
|
12
|
+
record: RepoLockRecord;
|
|
13
|
+
reacquired: boolean;
|
|
14
|
+
} | {
|
|
15
|
+
ok: false;
|
|
16
|
+
held_by: RepoLockRecord;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The canonical lock path for a genome root: `<genomeDir>/.coltrane/repo-lock-<hex>.json`, where
|
|
20
|
+
* `<hex>` is a stable SHA-256 digest of the ABSOLUTE genome_dir truncated to 16 hex chars. The
|
|
21
|
+
* digest keeps the filename slash/space-free and bounded while the full path is preserved inside
|
|
22
|
+
* the record. `.coltrane/` is gitignored (beside the ledger and the mirror), so the artifact is a
|
|
23
|
+
* runtime lock, never committed.
|
|
24
|
+
*/
|
|
25
|
+
export declare function repoLockPath(genomeDir: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Claim the per-repo lock for `genomeDir`, keyed on the resolved genome root.
|
|
28
|
+
*
|
|
29
|
+
* Atomic and exclusive via O_EXCL. On an existing destination:
|
|
30
|
+
* - the SAME gig_id (a parked gig's own resume) re-acquires the tree it already holds;
|
|
31
|
+
* - a LIVE holder refuses the claim, carrying `held_by`;
|
|
32
|
+
* - a DEAD holder (or a torn file) is broken — recorded, unlinked — and the claim retried.
|
|
33
|
+
*/
|
|
34
|
+
export declare function acquireRepoLock(genomeDir: string, holder: {
|
|
35
|
+
gigId: string;
|
|
36
|
+
pid: number;
|
|
37
|
+
startedAt: string;
|
|
38
|
+
}): RepoLockAcquire;
|
|
39
|
+
/**
|
|
40
|
+
* Release the per-repo lock — but ONLY when the recorded holder is `gigId`. A release that finds a
|
|
41
|
+
* different holder never steals it (that lock belongs to another run). Returns whether a lock this
|
|
42
|
+
* gig held was actually removed.
|
|
43
|
+
*/
|
|
44
|
+
export declare function releaseRepoLock(genomeDir: string, gigId: string): boolean;
|
package/dist/src/fs_atomic.js
CHANGED
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
* Two implementations of one concern, one of them documented and one of them absent, is the same
|
|
21
21
|
* shape as the two identity gates that disagreed in the resume work.
|
|
22
22
|
*/
|
|
23
|
-
import { mkdirSync, writeFileSync, renameSync } from "node:fs";
|
|
24
|
-
import { dirname } from "node:path";
|
|
25
|
-
import { randomUUID } from "node:crypto";
|
|
23
|
+
import { mkdirSync, writeFileSync, renameSync, readFileSync, unlinkSync } from "node:fs";
|
|
24
|
+
import { dirname, join, resolve } from "node:path";
|
|
25
|
+
import { randomUUID, createHash } from "node:crypto";
|
|
26
26
|
export function writeFileAtomic(file, text) {
|
|
27
27
|
mkdirSync(dirname(file), { recursive: true });
|
|
28
28
|
// randomUUID, not pid: two containers sharing a mounted volume routinely both have low pids,
|
|
@@ -31,4 +31,136 @@ export function writeFileAtomic(file, text) {
|
|
|
31
31
|
writeFileSync(tmp, text, "utf8");
|
|
32
32
|
renameSync(tmp, file);
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The canonical lock path for a genome root: `<genomeDir>/.coltrane/repo-lock-<hex>.json`, where
|
|
36
|
+
* `<hex>` is a stable SHA-256 digest of the ABSOLUTE genome_dir truncated to 16 hex chars. The
|
|
37
|
+
* digest keeps the filename slash/space-free and bounded while the full path is preserved inside
|
|
38
|
+
* the record. `.coltrane/` is gitignored (beside the ledger and the mirror), so the artifact is a
|
|
39
|
+
* runtime lock, never committed.
|
|
40
|
+
*/
|
|
41
|
+
export function repoLockPath(genomeDir) {
|
|
42
|
+
const abs = resolve(genomeDir);
|
|
43
|
+
const hex = createHash("sha256").update(abs).digest("hex").slice(0, 16);
|
|
44
|
+
return join(abs, ".coltrane", `repo-lock-${hex}.json`);
|
|
45
|
+
}
|
|
46
|
+
function isCode(e, code) {
|
|
47
|
+
return typeof e === "object" && e !== null && e.code === code;
|
|
48
|
+
}
|
|
49
|
+
/** A holder pid is alive iff `process.kill(pid, 0)` does not throw ESRCH. EPERM means the process
|
|
50
|
+
* exists but is not ours to signal — still alive. Any other error is treated conservatively as
|
|
51
|
+
* alive so a transient fault never breaks a lock that might still be live. */
|
|
52
|
+
function pidAlive(pid) {
|
|
53
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
54
|
+
return false;
|
|
55
|
+
try {
|
|
56
|
+
process.kill(pid, 0);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
if (isCode(e, "ESRCH"))
|
|
61
|
+
return false;
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function readLockRecord(path) {
|
|
66
|
+
try {
|
|
67
|
+
const rec = JSON.parse(readFileSync(path, "utf8"));
|
|
68
|
+
if (rec && typeof rec.gig_id === "string" && typeof rec.pid === "number")
|
|
69
|
+
return rec;
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return undefined; // ENOENT (raced away) or a torn/unparsable file — treat as no readable holder
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Record a broken stale lock to stderr — never silent, so an operator can audit which dead gig's
|
|
77
|
+
* grip was released and reclaimed. Shaped as a one-line JSON event like the gig milestone log. */
|
|
78
|
+
function recordStaleBreak(dest, held) {
|
|
79
|
+
const line = JSON.stringify({
|
|
80
|
+
t: new Date().toISOString(),
|
|
81
|
+
ev: "repo_lock_stale_break",
|
|
82
|
+
stale_gig_id: held?.gig_id ?? "(unreadable)",
|
|
83
|
+
stale_pid: held?.pid ?? null,
|
|
84
|
+
reason: "holder pid not alive",
|
|
85
|
+
lock: dest,
|
|
86
|
+
});
|
|
87
|
+
try {
|
|
88
|
+
process.stderr.write(line + "\n");
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
/* best-effort — the break still happens; only its record is lost */
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Claim the per-repo lock for `genomeDir`, keyed on the resolved genome root.
|
|
96
|
+
*
|
|
97
|
+
* Atomic and exclusive via O_EXCL. On an existing destination:
|
|
98
|
+
* - the SAME gig_id (a parked gig's own resume) re-acquires the tree it already holds;
|
|
99
|
+
* - a LIVE holder refuses the claim, carrying `held_by`;
|
|
100
|
+
* - a DEAD holder (or a torn file) is broken — recorded, unlinked — and the claim retried.
|
|
101
|
+
*/
|
|
102
|
+
export function acquireRepoLock(genomeDir, holder) {
|
|
103
|
+
const dest = repoLockPath(genomeDir);
|
|
104
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
105
|
+
const record = {
|
|
106
|
+
gig_id: holder.gigId,
|
|
107
|
+
pid: holder.pid,
|
|
108
|
+
started_at: holder.startedAt,
|
|
109
|
+
genome_dir: resolve(genomeDir),
|
|
110
|
+
};
|
|
111
|
+
const payload = JSON.stringify(record);
|
|
112
|
+
// A small retry budget: each stale break unlinks and loops to re-create. More than one iteration
|
|
113
|
+
// is only reached under an active race (another process re-took the freed slot), which the next
|
|
114
|
+
// read resolves — never an unbounded spin.
|
|
115
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
116
|
+
try {
|
|
117
|
+
writeFileSync(dest, payload, { encoding: "utf8", flag: "wx" });
|
|
118
|
+
return { ok: true, record, reacquired: false };
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
if (!isCode(e, "EEXIST"))
|
|
122
|
+
throw e;
|
|
123
|
+
}
|
|
124
|
+
const held = readLockRecord(dest);
|
|
125
|
+
if (held && held.gig_id === holder.gigId)
|
|
126
|
+
return { ok: true, record: held, reacquired: true };
|
|
127
|
+
if (held && pidAlive(held.pid))
|
|
128
|
+
return { ok: false, held_by: held };
|
|
129
|
+
// Dead holder or torn file — break it (recorded) and retry the exclusive create.
|
|
130
|
+
recordStaleBreak(dest, held);
|
|
131
|
+
try {
|
|
132
|
+
unlinkSync(dest);
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
if (!isCode(e, "ENOENT"))
|
|
136
|
+
throw e;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Exhausted the budget: another process keeps re-taking the freed slot. Refuse with the best
|
|
140
|
+
// holder we can read rather than clobber a live claim.
|
|
141
|
+
const held = readLockRecord(dest);
|
|
142
|
+
if (held && held.gig_id === holder.gigId)
|
|
143
|
+
return { ok: true, record: held, reacquired: true };
|
|
144
|
+
return { ok: false, held_by: held ?? record };
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Release the per-repo lock — but ONLY when the recorded holder is `gigId`. A release that finds a
|
|
148
|
+
* different holder never steals it (that lock belongs to another run). Returns whether a lock this
|
|
149
|
+
* gig held was actually removed.
|
|
150
|
+
*/
|
|
151
|
+
export function releaseRepoLock(genomeDir, gigId) {
|
|
152
|
+
const dest = repoLockPath(genomeDir);
|
|
153
|
+
const held = readLockRecord(dest);
|
|
154
|
+
if (!held || held.gig_id !== gigId)
|
|
155
|
+
return false;
|
|
156
|
+
try {
|
|
157
|
+
unlinkSync(dest);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
if (isCode(e, "ENOENT"))
|
|
162
|
+
return false;
|
|
163
|
+
throw e;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
34
166
|
//# sourceMappingURL=fs_atomic.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs_atomic.js","sourceRoot":"","sources":["../../src/fs_atomic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"fs_atomic.js","sourceRoot":"","sources":["../../src/fs_atomic.ts"],"names":[],"mappings":"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"}
|
|
@@ -75,6 +75,16 @@ export interface GigRunState {
|
|
|
75
75
|
/** Why. Surfaced by gig_monitor so `aborted` reads as a decision, not a mystery. */
|
|
76
76
|
abort_reason?: string;
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Is a gig's status TERMINAL — the run is over and holds the tree no longer?
|
|
80
|
+
*
|
|
81
|
+
* The single-flight per-repo lock (change c1d0c2e0) releases on exactly this set: complete, failed,
|
|
82
|
+
* and aborted. `awaiting_approval` is DELIBERATELY EXCLUDED — a parked gig is not terminal. It holds
|
|
83
|
+
* uncommitted work in the working tree (the chairs before the human gate already ran and left
|
|
84
|
+
* changes on disk) and will continue from that exact tree when approved, so it must RETAIN the lock
|
|
85
|
+
* while parked. Naming the terminal set here keeps the release site from re-deriving it and drifting.
|
|
86
|
+
*/
|
|
87
|
+
export declare function isTerminalStatus(status: GigStatus | string): boolean;
|
|
78
88
|
export declare function newGigRun(gig_id: string, standard_slug: string, phases_total: number, now: string): GigRunState;
|
|
79
89
|
export declare function applyGigProgress(state: GigRunState, ev: GigProgressEvent): void;
|
|
80
90
|
/**
|
package/dist/src/gig_tracker.js
CHANGED
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
// runtime fires GigProgressEvents as it walks the phase graph; this module folds those into
|
|
5
5
|
// a GigRunState that gig_monitor reads. State is in-memory (per server lifetime): a restart
|
|
6
6
|
// drops in-flight tracking — acceptable for v0, and a restart mid-gig is its own problem.
|
|
7
|
+
/**
|
|
8
|
+
* Is a gig's status TERMINAL — the run is over and holds the tree no longer?
|
|
9
|
+
*
|
|
10
|
+
* The single-flight per-repo lock (change c1d0c2e0) releases on exactly this set: complete, failed,
|
|
11
|
+
* and aborted. `awaiting_approval` is DELIBERATELY EXCLUDED — a parked gig is not terminal. It holds
|
|
12
|
+
* uncommitted work in the working tree (the chairs before the human gate already ran and left
|
|
13
|
+
* changes on disk) and will continue from that exact tree when approved, so it must RETAIN the lock
|
|
14
|
+
* while parked. Naming the terminal set here keeps the release site from re-deriving it and drifting.
|
|
15
|
+
*/
|
|
16
|
+
export function isTerminalStatus(status) {
|
|
17
|
+
return status === "complete" || status === "failed" || status === "aborted";
|
|
18
|
+
}
|
|
7
19
|
export function newGigRun(gig_id, standard_slug, phases_total, now) {
|
|
8
20
|
return {
|
|
9
21
|
gig_id, standard_slug, status: "running", started_at: now,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gig_tracker.js","sourceRoot":"","sources":["../../src/gig_tracker.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"gig_tracker.js","sourceRoot":"","sources":["../../src/gig_tracker.ts"],"names":[],"mappings":"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"}
|
package/dist/src/server.js
CHANGED
|
@@ -43,7 +43,8 @@ import { readFileSync, existsSync, mkdirSync, appendFileSync, readdirSync, write
|
|
|
43
43
|
import { randomUUID, createHash } from "node:crypto";
|
|
44
44
|
import { join, dirname } from "node:path";
|
|
45
45
|
import { tmpdir } from "node:os";
|
|
46
|
-
import { newGigRun, applyGigProgress, gigEventLogLine, pruneGigRuns } from "./gig_tracker.js";
|
|
46
|
+
import { newGigRun, applyGigProgress, gigEventLogLine, pruneGigRuns, isTerminalStatus } from "./gig_tracker.js";
|
|
47
|
+
import { acquireRepoLock, releaseRepoLock } from "./fs_atomic.js";
|
|
47
48
|
import { isGig } from "./ledger.js";
|
|
48
49
|
import { SubthreadRecorder, ApiVersionMismatchError } from "./subthread_recorder.js";
|
|
49
50
|
import { canonJson, runFingerprint, CANONICAL_FORM_VERSION } from "./canonical_form.js";
|
|
@@ -96,6 +97,14 @@ function governanceRow(event, subject_slug, detail, subject_gig_id) {
|
|
|
96
97
|
finished_at: now,
|
|
97
98
|
};
|
|
98
99
|
}
|
|
100
|
+
/** The prose a single-flight refusal teaches a human: WHO holds the tree, and the two ways out
|
|
101
|
+
* (wait for it to settle, or abort it). The machine-readable holder rides in `data.held_by`. */
|
|
102
|
+
function heldTreeError(held) {
|
|
103
|
+
return (`the working tree at "${held.genome_dir}" is held by a running gig "${held.gig_id}" ` +
|
|
104
|
+
`(pid ${held.pid}, since ${held.started_at}). Single-flight is law: a second dispatch against ` +
|
|
105
|
+
`the same repository is REFUSED — it does not queue and does not wait. Let that gig reach a ` +
|
|
106
|
+
`terminal state, or abort it with gig_abort "${held.gig_id}".`);
|
|
107
|
+
}
|
|
99
108
|
const KNOWN_SLUGS = new Set(MCP_TOOLS.map((t) => t.slug));
|
|
100
109
|
// Live registry of admissible tool slugs — the cage gate for agent_define's
|
|
101
110
|
// allowed_tools. Seeded with the static MCP_TOOLS surface; tool_register grows
|
|
@@ -887,9 +896,41 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
887
896
|
...(res.gates_approved ? { gates_approved: res.gates_approved } : {}),
|
|
888
897
|
...(res.resumed ? { resumed: res.resumed } : {}),
|
|
889
898
|
});
|
|
899
|
+
// The gig id the whole performance runs under — minted ONCE for both the wait and async
|
|
900
|
+
// doors, so the single-flight lock names the same holder either way.
|
|
901
|
+
const chartGigId = resumeArg ?? randomUUID();
|
|
902
|
+
// ── single-flight: ONE lock for the CHART's full lifetime, never per-movement ──────────
|
|
903
|
+
// A chart runs its movements sequentially under one promise. The lock is claimed ONCE here
|
|
904
|
+
// (holder = the chart's own gig_id) BEFORE the first movement and released only when the
|
|
905
|
+
// performance settles. A per-movement acquire/release would either open a gap between
|
|
906
|
+
// movements where a concurrent dispatch could slip in, or deadlock movement 2 against the
|
|
907
|
+
// lock movement 1 still holds. Released on every terminal outcome; RETAINED through
|
|
908
|
+
// awaiting_approval (a parked performance holds uncommitted work in the tree).
|
|
909
|
+
let releaseChartLock;
|
|
910
|
+
let chartLockReacquired = false; // re-entered a parked performance's own lock, vs freshly minted
|
|
911
|
+
if (deps.genome_dir) {
|
|
912
|
+
const acq = acquireRepoLock(deps.genome_dir, {
|
|
913
|
+
gigId: chartGigId, pid: process.pid, startedAt: new Date().toISOString(),
|
|
914
|
+
});
|
|
915
|
+
if (!acq.ok) {
|
|
916
|
+
return {
|
|
917
|
+
ok: false, requires_approval: approval, refusal: "repo_locked",
|
|
918
|
+
error: heldTreeError(acq.held_by), data: { held_by: acq.held_by },
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
chartLockReacquired = acq.reacquired;
|
|
922
|
+
const genomeDir = deps.genome_dir;
|
|
923
|
+
releaseChartLock = () => { try {
|
|
924
|
+
releaseRepoLock(genomeDir, chartGigId);
|
|
925
|
+
}
|
|
926
|
+
catch { /* best-effort */ } };
|
|
927
|
+
}
|
|
890
928
|
if (wait) {
|
|
891
929
|
try {
|
|
892
|
-
const res = await runChart(plan, gigInput, chartDeps);
|
|
930
|
+
const res = await runChart(plan, gigInput, { ...chartDeps, gig_id: chartGigId });
|
|
931
|
+
// Terminal (complete / budget-exhausted) frees the tree; a parked chart RETAINS it.
|
|
932
|
+
if (releaseChartLock && res.status !== "awaiting_approval")
|
|
933
|
+
releaseChartLock();
|
|
893
934
|
return {
|
|
894
935
|
ok: true, requires_approval: approval,
|
|
895
936
|
data: {
|
|
@@ -902,9 +943,15 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
902
943
|
}
|
|
903
944
|
catch (e) {
|
|
904
945
|
if (e instanceof ResumeRefused) {
|
|
946
|
+
// A re-entered lock belongs to the parked performance; a fresh one is freed here.
|
|
947
|
+
if (releaseChartLock && !chartLockReacquired)
|
|
948
|
+
releaseChartLock();
|
|
905
949
|
return { ok: false, requires_approval: approval, error: e.message,
|
|
906
950
|
data: { resume_refused: true, gig_id: e.gig_id, drift: e.drift } };
|
|
907
951
|
}
|
|
952
|
+
// Any other terminal throw (budget exhausted, a movement failed) frees the tree.
|
|
953
|
+
if (releaseChartLock)
|
|
954
|
+
releaseChartLock();
|
|
908
955
|
if (e instanceof BudgetExhausted) {
|
|
909
956
|
const partial = partialGigUsage(e);
|
|
910
957
|
return { ok: false, requires_approval: approval, error: e.message,
|
|
@@ -917,7 +964,6 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
917
964
|
// Async, the default. Same live-state row an async standard dispatch registers, so
|
|
918
965
|
// gig_monitor and gig_abort reach a performance exactly as they reach a run: the row
|
|
919
966
|
// names the standard the performance OPENS with, and `chart_slug` names the arrangement.
|
|
920
|
-
const chartGigId = resumeArg ?? randomUUID();
|
|
921
967
|
const chartRuns = deps.gig_runs ?? (deps.gig_runs = new Map());
|
|
922
968
|
const priorChartState = chartRuns.get(chartGigId);
|
|
923
969
|
const chartState = newGigRun(chartGigId, plan.movements[0].standard.slug, plan.movements.reduce((n, m) => n + m.standard.phases.length, 0), new Date().toISOString());
|
|
@@ -993,7 +1039,14 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
993
1039
|
chartState.budget_state = bs;
|
|
994
1040
|
onChartProgress({ type: "gig_failed", error: chartState.error });
|
|
995
1041
|
})
|
|
996
|
-
.finally(() => {
|
|
1042
|
+
.finally(() => {
|
|
1043
|
+
chartState.controller = undefined; // don't pin a controller past settle
|
|
1044
|
+
// Free the tree on every terminal outcome (complete / failed / aborted); a parked
|
|
1045
|
+
// performance RETAINS it. A refused resume that re-entered a parked holder's lock
|
|
1046
|
+
// leaves it held; one that minted a fresh lock still frees it.
|
|
1047
|
+
if (releaseChartLock && isTerminalStatus(chartState.status) && !(chartRefusal && chartLockReacquired))
|
|
1048
|
+
releaseChartLock();
|
|
1049
|
+
});
|
|
997
1050
|
if (resumeArg !== undefined) {
|
|
998
1051
|
await Promise.resolve(); // one turn — see the ordering note on the standard path below
|
|
999
1052
|
if (chartRefusal) {
|
|
@@ -1040,14 +1093,50 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
1040
1093
|
venue, venues: deps.venues, venueRealizer: deps.venueRealizer,
|
|
1041
1094
|
repoUrl: dispatchRepoUrl,
|
|
1042
1095
|
});
|
|
1096
|
+
// The gig id this run seals under — minted ONCE for both doors so the single-flight lock
|
|
1097
|
+
// names the same holder whether the caller blocked (wait:true) or polled (async default).
|
|
1098
|
+
const gigId = resumeArg ?? randomUUID();
|
|
1099
|
+
// ── single-flight: claim the working tree BEFORE any chair runs ─────────────────────────
|
|
1100
|
+
// Every LOCAL dispatch entry point funnels here (the in-process gig_dispatch tool AND the
|
|
1101
|
+
// CLI, which calls dispatchTool). The lock is per genome ROOT (deps.genome_dir): two gigs
|
|
1102
|
+
// against the same tree each derive their sealed change-set from `git diff` of a tree the
|
|
1103
|
+
// other mutates, so the second is REFUSED — never queued — with a structured error naming
|
|
1104
|
+
// the holder. A parked gig's OWN resume (same gig_id) re-enters the tree it already holds.
|
|
1105
|
+
// The lock activates only when a genome_dir is present: a hosted/bare-deps drain carries no
|
|
1106
|
+
// local tree to lock and is unaffected by construction.
|
|
1107
|
+
let releaseLock;
|
|
1108
|
+
// Did this claim RE-ENTER a lock the same gig already held (a parked gig's own resume), as
|
|
1109
|
+
// opposed to minting a fresh one? A refused resume must NOT free a re-entered lock (the
|
|
1110
|
+
// parked holder keeps its tree), but MUST free a fresh one (nothing else holds it).
|
|
1111
|
+
let lockReacquired = false;
|
|
1112
|
+
if (deps.genome_dir) {
|
|
1113
|
+
const acq = acquireRepoLock(deps.genome_dir, {
|
|
1114
|
+
gigId, pid: process.pid, startedAt: new Date().toISOString(),
|
|
1115
|
+
});
|
|
1116
|
+
if (!acq.ok) {
|
|
1117
|
+
return {
|
|
1118
|
+
ok: false, requires_approval: approval, refusal: "repo_locked",
|
|
1119
|
+
error: heldTreeError(acq.held_by), data: { held_by: acq.held_by },
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
lockReacquired = acq.reacquired;
|
|
1123
|
+
const genomeDir = deps.genome_dir;
|
|
1124
|
+
releaseLock = () => { try {
|
|
1125
|
+
releaseRepoLock(genomeDir, gigId);
|
|
1126
|
+
}
|
|
1127
|
+
catch { /* best-effort */ } };
|
|
1128
|
+
}
|
|
1043
1129
|
// Synchronous mode (opt-in via wait:true) — block, return the manifest. The
|
|
1044
1130
|
// deterministic test path and any caller that wants the answer in one call.
|
|
1045
1131
|
if (wait) {
|
|
1046
1132
|
try {
|
|
1047
1133
|
const res = await runGig(standard, gigInput, {
|
|
1048
|
-
...dispatchDeps,
|
|
1134
|
+
...dispatchDeps, gig_id: gigId,
|
|
1049
1135
|
...(depth ? { depth } : {}), ...reuseWiring, ...humanWiring,
|
|
1050
1136
|
});
|
|
1137
|
+
// Terminal (complete) frees the tree; a parked gig (awaiting_approval) RETAINS it.
|
|
1138
|
+
if (releaseLock && res.status !== "awaiting_approval")
|
|
1139
|
+
releaseLock();
|
|
1051
1140
|
return {
|
|
1052
1141
|
ok: true, requires_approval: approval,
|
|
1053
1142
|
data: {
|
|
@@ -1069,11 +1158,18 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
1069
1158
|
}
|
|
1070
1159
|
catch (e) {
|
|
1071
1160
|
// A refused resume is a REFUSAL, not a crash: nothing ran, nothing was spent, and
|
|
1072
|
-
// the caller needs the drift list to decide whether to re-dispatch cold.
|
|
1161
|
+
// the caller needs the drift list to decide whether to re-dispatch cold. The holder it
|
|
1162
|
+
// re-entered is unchanged, so the tree is NOT freed here.
|
|
1073
1163
|
if (e instanceof ResumeRefused) {
|
|
1164
|
+
// Free only a FRESH claim; a re-entered lock belongs to the parked holder, untouched.
|
|
1165
|
+
if (releaseLock && !lockReacquired)
|
|
1166
|
+
releaseLock();
|
|
1074
1167
|
return { ok: false, requires_approval: approval, error: e.message,
|
|
1075
1168
|
data: { resume_refused: true, gig_id: e.gig_id, drift: e.drift } };
|
|
1076
1169
|
}
|
|
1170
|
+
// Every other terminal throw (budget exhausted, a chair failed) frees the tree.
|
|
1171
|
+
if (releaseLock)
|
|
1172
|
+
releaseLock();
|
|
1077
1173
|
if (e instanceof BudgetExhausted) {
|
|
1078
1174
|
// #236 — the synchronous half: a depleted gig also burned real dollars before it
|
|
1079
1175
|
// stopped, and the operator needs them in the same reply as the depletion notice.
|
|
@@ -1091,8 +1187,8 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
1091
1187
|
// A resumed run CONTINUES the gig it resumes — same id — so the restored outputs stay
|
|
1092
1188
|
// in-gig and `output_trace` still reaches them. The live-state entry for the earlier
|
|
1093
1189
|
// attempt is replaced: that gig is running again, and showing its old `failed` state
|
|
1094
|
-
// while it runs would be a lie the operator acts on.
|
|
1095
|
-
|
|
1190
|
+
// while it runs would be a lie the operator acts on. (`gigId` was minted above, before the
|
|
1191
|
+
// wait/async split, so the single-flight lock names this same id.)
|
|
1096
1192
|
const runs = deps.gig_runs ?? (deps.gig_runs = new Map());
|
|
1097
1193
|
// #278 review — keep the prior attempt's record so a REFUSED resume can put it back.
|
|
1098
1194
|
// Overwriting it is right when the resume proceeds (that gig is running again), and
|
|
@@ -1203,7 +1299,15 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
1203
1299
|
state.budget_state = bs;
|
|
1204
1300
|
onProgress({ type: "gig_failed", error: state.error });
|
|
1205
1301
|
})
|
|
1206
|
-
.finally(() => {
|
|
1302
|
+
.finally(() => {
|
|
1303
|
+
state.controller = undefined; // don't pin a controller past settle
|
|
1304
|
+
// Free the tree on every terminal outcome (complete / failed / aborted); a parked gig
|
|
1305
|
+
// (awaiting_approval) RETAINS it — it holds uncommitted work the resume continues from.
|
|
1306
|
+
// A refused resume that RE-ENTERED a parked holder's lock leaves it held; a refused
|
|
1307
|
+
// resume that minted a FRESH lock still frees it (nothing else holds that tree).
|
|
1308
|
+
if (releaseLock && isTerminalStatus(state.status) && !(resumeRefusal && lockReacquired))
|
|
1309
|
+
releaseLock();
|
|
1310
|
+
});
|
|
1207
1311
|
if (resumeArg !== undefined) {
|
|
1208
1312
|
await Promise.resolve(); // one turn — see the ordering note above
|
|
1209
1313
|
if (resumeRefusal) {
|