@eir-labs/coltrane 0.16.0 → 0.17.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 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.16.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";
34
+ export declare const USAGE = "coltrane 0.17.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[];
@@ -25,7 +25,7 @@
25
25
  * own fails closed. That is the correct posture for a box running work nobody is watching.
26
26
  */
27
27
  import { type ToolProviderRegistry } from "./tool_providers.js";
28
- import type { BudgetInput } from "./runtime.js";
28
+ import type { BudgetInput, RunDeps } from "./runtime.js";
29
29
  /**
30
30
  * Every engine tool as an in-house provider, tagged with the engine's own MCP server.
31
31
  *
@@ -50,3 +50,63 @@ export declare function engineToolProviders(): ToolProviderRegistry;
50
50
  */
51
51
  export declare function drainBudget(input: Record<string, unknown> | undefined): BudgetInput;
52
52
  export declare function drainTimeoutMs(): number;
53
+ /**
54
+ * WHICH REPOSITORY THIS GIG'S WORKING TREE IS. One home for the decision, shared by every door — the
55
+ * drain reads it as `resolveWorkingRepo(claim)` and the dispatch door as
56
+ * `resolveWorkingRepo({ input }, args['repo_url'])`, so a change-request's repository resolves the
57
+ * SAME way however the gig arrived.
58
+ *
59
+ * The order is the fix, in three tiers:
60
+ * 1. the TYPED input `repository` — a first-class field in every code-touching standard's input
61
+ * contract (domain_types/change-request.json, change-context.json). The correct value was always
62
+ * being passed; a direct dispatch was the door that ignored it.
63
+ * 2. an EXPLICIT repo_url argument — the dispatch door's named override, honoured beneath the typed
64
+ * input and above the org default so `repo_url` on gig_dispatch keeps working as a fallback.
65
+ * 3. the org column (`claim.repo_url`) — the store's per-organization default, so existing
66
+ * single-repo deployments keep working unchanged.
67
+ *
68
+ * Null is a normal answer, not a degraded one — a standard whose contract names no repository touches
69
+ * no tree and mints no git credential, which is already correct for research work.
70
+ *
71
+ * `explicitRepoUrl` has a default initializer so `Function.length === 1`: the drain calls this with one
72
+ * argument and tests/the_repo_is_typed_input pins that arity. `claim` is typed structurally (no
73
+ * ClaimedGig import) so this shared module keeps its no-new-dependency posture.
74
+ */
75
+ export declare function resolveWorkingRepo(claim: {
76
+ input?: unknown;
77
+ repo_url?: string | null | undefined;
78
+ }, explicitRepoUrl?: string | undefined): string | null;
79
+ /**
80
+ * The SHARED run-deps every gig runs under, assembled once so the four call sites cannot drift.
81
+ *
82
+ * WHY THIS EXISTS. `runGig` takes its dependencies as a bag of optionals, and `runGig` has four call
83
+ * sites (server.ts sync + async dispatch, worker.ts drain, chart.ts movement), each of which used to
84
+ * hand-assemble its own body. Read one and each omission looks deliberate; read them side by side and
85
+ * they had already diverged — a wire added to one branch was carried to the other by a comment asking
86
+ * the next reader to remember. This function is that shared body: every door builds its run-deps from
87
+ * here, so a wire is INHERITED, not copied by reminder.
88
+ *
89
+ * The fields that legitimately DIFFER per door are supplied by explicit ARGUMENT, never defaulted:
90
+ * · `mcpServerConfigs` — empty on the drain (a freshly cloned, untrusted repo must not declare
91
+ * servers for the seat reading it; see the header above) and the bootstrap map on the server. A
92
+ * default would silently hand one door the wrong value — the exact class of hand-carried
93
+ * divergence this whole change exists to eliminate — so it is a REQUIRED argument.
94
+ * · the venue trio (`venue`/`venues`/`venueRealizer`) and `repoUrl` — conditionally spread so an
95
+ * absent one is threaded to nothing, keeping the venue-less / repo-less path byte-identical.
96
+ *
97
+ * DOOR-SPECIFIC fields (gig_id, signal, onProgress, depth, reuse/human wiring, resume_from,
98
+ * seed_outputs, chart, checkpoints) are NOT here: each call site spreads its own onto this result. The
99
+ * assembler unifies exactly the wires that diverged and no more.
100
+ */
101
+ export type AssembleRunDepsArgs = Pick<RunDeps, "outputs" | "ledger" | "invoke" | "model_version" | "skills" | "skill_dirs" | "evals" | "budget" | "toolProviders" | "venue" | "venues" | "venueRealizer"> & {
102
+ /**
103
+ * The enforcement environment, supplied per door: {} on the drain, the bootstrap map on the server
104
+ * (which is itself `Record | undefined`). REQUIRED — the property may hold undefined but may never be
105
+ * OMITTED, so every door states this wire at its call site rather than inheriting a silent default;
106
+ * that omission is the exact hand-carried divergence this assembler exists to foreclose.
107
+ */
108
+ mcpServerConfigs: Readonly<Record<string, unknown>> | undefined;
109
+ /** The repository this run operates on, already resolved via `resolveWorkingRepo`. Null → not threaded. */
110
+ repoUrl?: string | null | undefined;
111
+ };
112
+ export declare function assembleRunDeps(args: AssembleRunDepsArgs): RunDeps;
@@ -71,4 +71,56 @@ export function drainTimeoutMs() {
71
71
  const env = Number(process.env["COLTRANE_GIG_TIMEOUT_MS"]);
72
72
  return Number.isFinite(env) && env > 0 ? env : DEFAULT_DRAIN_TIMEOUT_MS;
73
73
  }
74
+ /**
75
+ * WHICH REPOSITORY THIS GIG'S WORKING TREE IS. One home for the decision, shared by every door — the
76
+ * drain reads it as `resolveWorkingRepo(claim)` and the dispatch door as
77
+ * `resolveWorkingRepo({ input }, args['repo_url'])`, so a change-request's repository resolves the
78
+ * SAME way however the gig arrived.
79
+ *
80
+ * The order is the fix, in three tiers:
81
+ * 1. the TYPED input `repository` — a first-class field in every code-touching standard's input
82
+ * contract (domain_types/change-request.json, change-context.json). The correct value was always
83
+ * being passed; a direct dispatch was the door that ignored it.
84
+ * 2. an EXPLICIT repo_url argument — the dispatch door's named override, honoured beneath the typed
85
+ * input and above the org default so `repo_url` on gig_dispatch keeps working as a fallback.
86
+ * 3. the org column (`claim.repo_url`) — the store's per-organization default, so existing
87
+ * single-repo deployments keep working unchanged.
88
+ *
89
+ * Null is a normal answer, not a degraded one — a standard whose contract names no repository touches
90
+ * no tree and mints no git credential, which is already correct for research work.
91
+ *
92
+ * `explicitRepoUrl` has a default initializer so `Function.length === 1`: the drain calls this with one
93
+ * argument and tests/the_repo_is_typed_input pins that arity. `claim` is typed structurally (no
94
+ * ClaimedGig import) so this shared module keeps its no-new-dependency posture.
95
+ */
96
+ export function resolveWorkingRepo(claim, explicitRepoUrl = undefined) {
97
+ const typed = claim.input?.repository;
98
+ if (typeof typed === "string" && typed.trim().length > 0)
99
+ return typed;
100
+ if (typeof explicitRepoUrl === "string" && explicitRepoUrl.trim().length > 0)
101
+ return explicitRepoUrl;
102
+ const orgDefault = claim.repo_url;
103
+ return typeof orgDefault === "string" && orgDefault.trim().length > 0 ? orgDefault : null;
104
+ }
105
+ export function assembleRunDeps(args) {
106
+ return {
107
+ outputs: args.outputs,
108
+ ledger: args.ledger,
109
+ invoke: args.invoke,
110
+ model_version: args.model_version,
111
+ skills: args.skills,
112
+ skill_dirs: args.skill_dirs,
113
+ evals: args.evals,
114
+ budget: args.budget,
115
+ toolProviders: args.toolProviders,
116
+ mcpServerConfigs: args.mcpServerConfigs,
117
+ // The venue trio and the repository, threaded only when present so an absent one is threaded to
118
+ // nothing — runGig's `deps.venue !== undefined` gate stays untripped and the venue-less path is
119
+ // byte-identical.
120
+ ...(args.venue ? { venue: args.venue } : {}),
121
+ ...(args.venues ? { venues: args.venues } : {}),
122
+ ...(args.venueRealizer ? { venueRealizer: args.venueRealizer } : {}),
123
+ ...(args.repoUrl ? { repoUrl: args.repoUrl } : {}),
124
+ };
125
+ }
74
126
  //# sourceMappingURL=run_deps.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"run_deps.js","sourceRoot":"","sources":["../../src/run_deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,iBAAiB,EAAgD,MAAM,qBAAqB,CAAC;AAGtG;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,IAAI,GAAG,CACZ,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,UAAmB,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC,CACvG,CAAC;AACJ,CAAC;AAED,0DAA0D;AAC1D,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAEnC;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CAAC,KAA0C;IACpE,MAAM,KAAK,GAAI,KAAK,EAAE,CAAC,QAAQ,CAAuC,EAAE,CAAC,SAAS,CAAC,CAAC;IACpF,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAEhG,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,CAAC;IAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,qBAAqB,EAAE,CAAC;AACpF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,wBAAwB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEhD,MAAM,UAAU,cAAc;IAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC,CAAC;IAC3D,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,wBAAwB,CAAC;AAC1E,CAAC"}
1
+ {"version":3,"file":"run_deps.js","sourceRoot":"","sources":["../../src/run_deps.ts"],"names":[],"mappings":"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"}
@@ -24,6 +24,7 @@ import { FileLedger, LedgerError, LEDGER_SCHEMA_VERSION, defaultLedgerPath, } fr
24
24
  import { sealDrill } from "./seal_drill.js";
25
25
  import { standardSimulate } from "./simulate.js";
26
26
  import { runGig, BudgetExhausted, GigAborted, ResumeRefused, partialGigUsage, partialBudgetState } from "./runtime.js";
27
+ import { assembleRunDeps, resolveWorkingRepo } from "./run_deps.js";
27
28
  import { createCheckpointStore, createReuseStore } from "./reuse.js";
28
29
  import { makeClaudeInvoker, killLiveChairChildren } from "./claude_invoker.js";
29
30
  import { dockerComposeRealizer } from "./venue_realizer.js";
@@ -1017,23 +1018,31 @@ async function runImpl(slug, args, deps, approval) {
1017
1018
  // Reached only when the target is a standard: the chart branch above always returns, and an
1018
1019
  // unresolvable standard slug returned before the preflights. So there is exactly one here.
1019
1020
  const standard = targetStandards[0];
1021
+ // The repository this dispatch resolves for the run, through the ONE shared resolver both doors
1022
+ // use (run_deps.ts): the TYPED input.repository is authoritative, and the explicit repo_url
1023
+ // argument is honoured only BENEATH it (non-goal — repo_url on gig_dispatch keeps working as a
1024
+ // fallback). This is the C2/C3 fix: a change-request carrying a typed `repository` is now
1025
+ // honoured by a direct dispatch exactly as the drain honours it, instead of being ignored.
1026
+ const dispatchRepoUrl = resolveWorkingRepo({ input: gigInput }, repoUrl);
1027
+ // The SHARED run-deps for BOTH dispatch branches, built ONCE from the assembler so the wait:true
1028
+ // and async paths cannot diverge by construction — a wire is inherited, not carried across by a
1029
+ // reminder comment. The venue trio and repository are threaded (conditionally) inside the
1030
+ // assembler; `mcpServerConfigs` is stated here as the explicit argument it requires. Each branch
1031
+ // spreads its own door-specific fields (gig_id, onProgress, signal, depth, reuse/human wiring).
1032
+ const dispatchDeps = assembleRunDeps({
1033
+ outputs: deps.outputs, ledger: deps.ledger, invoke: deps.invoke,
1034
+ model_version: deps.model_version, skills: deps.skills, skill_dirs: deps.skill_dirs,
1035
+ evals: deps.evals, budget,
1036
+ toolProviders: deps.toolProviders, mcpServerConfigs: deps.mcpServerConfigs, // dispatch preflight resolves against the invoker's environment
1037
+ venue, venues: deps.venues, venueRealizer: deps.venueRealizer,
1038
+ repoUrl: dispatchRepoUrl,
1039
+ });
1020
1040
  // Synchronous mode (opt-in via wait:true) — block, return the manifest. The
1021
1041
  // deterministic test path and any caller that wants the answer in one call.
1022
1042
  if (wait) {
1023
1043
  try {
1024
1044
  const res = await runGig(standard, gigInput, {
1025
- outputs: deps.outputs, ledger: deps.ledger, invoke: deps.invoke,
1026
- model_version: deps.model_version, skills: deps.skills, skill_dirs: deps.skill_dirs, evals: deps.evals, budget,
1027
- toolProviders: deps.toolProviders, mcpServerConfigs: deps.mcpServerConfigs, // dispatch preflight resolves against the invoker's environment
1028
- // THE ROOM THIS DISPATCH NAMED, threaded so runGig realizes it or refuses fail-closed —
1029
- // the same conditional-spread trio the chart path uses (server.ts:935-941). `venue` is
1030
- // threaded only when non-empty so runGig's `deps.venue !== undefined` gate is never
1031
- // tripped by an absent one; `venues`/`venueRealizer` come from bootstrap (ServerDeps).
1032
- ...(venue ? { venue } : {}),
1033
- ...(deps.venues ? { venues: deps.venues } : {}),
1034
- ...(deps.venueRealizer ? { venueRealizer: deps.venueRealizer } : {}),
1035
- // The repository the run names, threaded so runGig populates the room's tree from it.
1036
- ...(repoUrl ? { repoUrl } : {}),
1045
+ ...dispatchDeps,
1037
1046
  ...(depth ? { depth } : {}), ...reuseWiring, ...humanWiring,
1038
1047
  });
1039
1048
  return {
@@ -1116,19 +1125,11 @@ async function runImpl(slug, args, deps, approval) {
1116
1125
  catch { /* best-effort */ }
1117
1126
  }
1118
1127
  };
1128
+ // The SAME assembled base as the wait:true branch — the venue trio and the repository wire are
1129
+ // INHERITED from `dispatchDeps`, not copied here by a reminder to keep the two branches in step.
1130
+ // A wire added to the shared assembler reaches this default async path by construction.
1119
1131
  const runPromise = runGig(standard, gigInput, {
1120
- outputs: deps.outputs, ledger: deps.ledger, invoke: deps.invoke,
1121
- model_version: deps.model_version, skills: deps.skills, skill_dirs: deps.skill_dirs, evals: deps.evals, budget,
1122
- toolProviders: deps.toolProviders, mcpServerConfigs: deps.mcpServerConfigs, // dispatch preflight resolves against the invoker's environment
1123
- // Same venue trio as the sync path above — the DEFAULT dispatch mode must honour a named
1124
- // room too, or the fix covers only the deterministic wait:true test path and leaves the
1125
- // path the product actually dispatches through discarding the venue.
1126
- ...(venue ? { venue } : {}),
1127
- ...(deps.venues ? { venues: deps.venues } : {}),
1128
- ...(deps.venueRealizer ? { venueRealizer: deps.venueRealizer } : {}),
1129
- // Same repository wire as the sync path above — the default async dispatch must populate a
1130
- // named room's tree too, not only the deterministic wait:true path.
1131
- ...(repoUrl ? { repoUrl } : {}),
1132
+ ...dispatchDeps,
1132
1133
  gig_id: gigId, onProgress, signal: controller.signal, ...(depth ? { depth } : {}), ...reuseWiring, ...humanWiring,
1133
1134
  });
1134
1135
  // A REFUSED resume must be answered in THIS reply, not discovered later by polling. The