bareloop 0.23.0 → 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/types/judged.d.ts CHANGED
@@ -1,3 +1,102 @@
1
+ /**
2
+ * RESOLVE WHICH MODEL GRADES THIS JOB (PRD item 32.1).
3
+ *
4
+ * hamr, 2026-09-10: *"what i care about is that judge becomes llm agnostic and
5
+ * not set to one model or provider"* — and the shape he named: *"defaults to
6
+ * whatever llm is used and can be overriden"*.
7
+ *
8
+ * So, in precedence order:
9
+ * 1. `spec.judge` — an explicit, SIGNED `{provider, model}`. This is how a run
10
+ * pins a judge deliberately different from its worker (an independent
11
+ * reader, or a judge whose calibration a job wants to keep while the worker
12
+ * moves). It is a spec field the HUMAN signs, part of `jobSpecHash` like
13
+ * every other; the drafting agent cannot express it, because naming your
14
+ * own examiner is arbiter territory.
15
+ * 2. otherwise the job's OWN worker provider and model. A DeepSeek job judges
16
+ * on DeepSeek and needs no second account — which is the whole point: after
17
+ * item 31.4 a green job runs on its own key alone, and this is what lets a
18
+ * JUDGED one do the same.
19
+ *
20
+ * There is deliberately NO third fallback to `JUDGE_MODEL`. A caller that cannot
21
+ * say which model will grade has not wired a judge, and the grading path stops
22
+ * as a wiring gap rather than grading under an identity nobody chose — the same
23
+ * answer an absent `judgeLoop` already gets. A silent default here would be the
24
+ * exact silent-ignore class that `endpointKey` and F149 exist to prevent, except
25
+ * pointed at the arbiter.
26
+ *
27
+ * SELF-GRADING is licensed by measurement, not by this function: the judge
28
+ * renders no verdict (it locates facts; `decide()` is deterministic), and the
29
+ * calibration gate — 10 cases, a 10-of-10 floor, no partial credit, cases that
30
+ * MUST fail — is exactly the instrument that catches a judge which cannot see
31
+ * failures. If a worker-family judge cannot pass its own calibration, that is
32
+ * the finding, and the gate refuses to sign.
33
+ *
34
+ * @param {{specJudge?: any, workerProvider?: string, workerModel?: string}} o
35
+ * @returns {{provider: string, model: string}}
36
+ */
37
+ export function resolveJudge({ specJudge, workerProvider, workerModel }?: {
38
+ specJudge?: any;
39
+ workerProvider?: string;
40
+ workerModel?: string;
41
+ }): {
42
+ provider: string;
43
+ model: string;
44
+ };
45
+ /**
46
+ * `resolveJudge` needs a worker MODEL already resolved, and an authoring
47
+ * pipeline has to compute one identity at TWO different moments — once at
48
+ * compose time, to STAMP `closeDecl.calibration.judgeModel` into the spec it
49
+ * is about to sign, and again at the calibration gate that grades against
50
+ * that stamp — from a value that is not yet a validated spec (the operator's
51
+ * draft) the first time and is one (the assembled spec) the second. Two
52
+ * hand-spelled compositions of `resolveWorkerModel` + `resolveJudge` is
53
+ * exactly the kind of pair that drifts (PRD F45's "a rule with no detector is
54
+ * prose" applies to a rule spelled TWICE just as much as to one spelled
55
+ * nowhere) — this is the one spelling both calls make, and it mirrors the
56
+ * SAME composition `scripts/run-u.mjs` applies at launch time: the named
57
+ * provider's own `tiers.sonnet` defaults an absent model, and a signed
58
+ * `judge` override wins over the worker default.
59
+ *
60
+ * `source` is read for `provider`/`model`/`judge` only, so a draft and the
61
+ * spec `assembleSpec` folds it into resolve identically — `assembleSpec`
62
+ * spreads the draft into the spec untouched on those three fields — and any
63
+ * draft that goes on to validate and sign therefore stamps and grades under
64
+ * the exact same identity `scripts/run-u.mjs` will resolve at run time.
65
+ *
66
+ * `source.provider` absent, or unresolvable, or `source.judge` malformed all
67
+ * THROW here exactly as `resolveProvider`/`resolveJudge` already do on their
68
+ * own — a caller composing this from a pre-validation draft is expected to
69
+ * catch that and use a placeholder, because a draft that cannot resolve here
70
+ * is a draft `validateJob` reds a few lines later regardless, before anything
71
+ * gets signed under the placeholder.
72
+ *
73
+ * `resolveWorkerModelFn` is taken as a PARAMETER rather than imported — this
74
+ * module cannot import `./job.js` directly: `job.js` imports
75
+ * `./declaredclose.js`, which imports `resolveJudge` from THIS module, and a
76
+ * `judged.js` → `job.js` edge would close that into a cycle. `resolveProvider`
77
+ * has no such conflict (`./providers.js` imports nothing local) and is
78
+ * imported directly.
79
+ *
80
+ * @param {{provider?: string, model?: string, judge?: any}} source draft or spec
81
+ * @param {string} fallbackProviderName used only when `source.provider` is absent
82
+ * @param {(o: {specModel?: string, flagModel?: string, defaultModel: string}) => {model: string, source: string}} resolveWorkerModelFn `resolveWorkerModel` from `./job.js`
83
+ * @returns {{provider: string, model: string}}
84
+ */
85
+ export function resolveJobJudge(source: {
86
+ provider?: string;
87
+ model?: string;
88
+ judge?: any;
89
+ }, fallbackProviderName: string, resolveWorkerModelFn: (o: {
90
+ specModel?: string;
91
+ flagModel?: string;
92
+ defaultModel: string;
93
+ }) => {
94
+ model: string;
95
+ source: string;
96
+ }): {
97
+ provider: string;
98
+ model: string;
99
+ };
1
100
  /**
2
101
  * The rubric card: Q6's answer compiled into enumerated items, signed by the
3
102
  * person and enumerated in the spec hash. `rule` selects from `JUDGE_RULES`;
@@ -201,25 +300,35 @@ export function decide(facts: any, card: any, { artifactText }?: {
201
300
  reason: string | null;
202
301
  };
203
302
  /**
204
- * THE JUDGE MODEL, PINNED. Never agent-selectable and never a step knob: it is
205
- * the only tier with established injection resistance upstream (BA-20's
206
- * INJECTION_BATTERY is haiku-4.5 only), and §4.2's whole safety argument is
207
- * worth exactly as much as that evidence. A judge-model bump forces a full
208
- * recalibration — mandatory, per the model-bump dead-weight replay rule.
303
+ * THE JUDGE'S HISTORICAL PIN. Until PRD item 32 (2026-09-10) this constant WAS
304
+ * the judge: every grading path imported it, so bareloop could run a worker on
305
+ * any provider and still judge only on Claude.
306
+ *
307
+ * It is no longer read by any grading path. The judge identity is now RESOLVED
308
+ * per job (`resolveJudge` below) and travels as data — `ctx.judgeModel` into
309
+ * `runJudgedFloor`, an explicit argument into the calibration gate, and
310
+ * `closeDecl.calibration.judgeModel` into the signed spec, exactly as before.
311
+ * Keeping the export is deliberate: it is part of the public API
312
+ * (`src/index.js`) and an adopter importing it should not break on a minor.
313
+ * What it means now is "the judge bareloop pinned before item 32", which is
314
+ * still the right DEFAULT for an anthropic-api job and still the only tier with
315
+ * established injection resistance upstream (BA-20's `INJECTION_BATTERY` is
316
+ * haiku-4.5 only).
209
317
  *
210
- * THAT IS ENFORCED, not asked for, and it is enforced in three places because a
211
- * rule with no wired detector is prose (F45):
212
- * 1. the value here is STORED with the set it graded — `foldJudgedArtifacts`
318
+ * THE ENFORCEMENT IS UNCHANGED, and still lives in three wired places because a
319
+ * rule with no detector is prose (F45) — only the value being compared moved
320
+ * from this constant to the resolved identity:
321
+ * 1. the identity is STORED with the set it graded — `foldJudgedArtifacts`
213
322
  * writes it as `closeDecl.calibration.judgeModel`, inside the spec, so
214
- * `jobSpecHash` covers it by construction. Bump this constant and every
323
+ * `jobSpecHash` covers it by construction. Change the judge and every
215
324
  * signed judged spec hashes differently: the signature dies, and re-signing
216
325
  * is what re-runs the calibration gate.
217
326
  * 2. `validateCloseDecl` REQUIRES it on any stored set — a set nobody can
218
- * attribute to a tier is a floor nobody can tell apart from a bumped one.
219
- * 3. `runJudgedFloor` refuses to grade when the stored tier is not this one,
220
- * naming both models. It does not degrade in either direction: grading on
221
- * the new tier judges against a floor nobody calibrated, and the old tier is
222
- * not on offer.
327
+ * attribute to a judge is a floor nobody can tell apart from a moved one.
328
+ * 3. `runJudgedFloor` refuses to grade when the stored identity is not the one
329
+ * about to grade, naming both. It does not degrade in either direction:
330
+ * grading on the new judge judges against a floor nobody calibrated, and
331
+ * the old judge is not on offer.
223
332
  */
224
333
  export const JUDGE_MODEL: "claude-haiku-4-5";
225
334
  /**
package/types/kinds.d.ts CHANGED
@@ -1,3 +1,35 @@
1
+ /**
2
+ * The judged stages of a close declaration, and whether it has any — the ONE
3
+ * spelling of "does this close ask a model to render anything?".
4
+ *
5
+ * The comment above `JUDGED_FLOOR_KIND` says five literals is how the readers
6
+ * come to disagree, and by PRD item 31.4 there were SIX open-coded
7
+ * `stages.filter(s => s.kind === JUDGED_FLOOR_KIND)` across the tree
8
+ * (`src/authorjob.js` twice, `src/cardauthor.js`, `src/declaredclose.js`,
9
+ * `scripts/run-author.mjs`, `scripts/author-readout.mjs`). 31.4 needed a
10
+ * seventh, in the runner, to decide whether a judge KEY is demanded — and a
11
+ * seventh spelling deciding a REFUSAL is the drift `scripts/run-u.mjs`'s own
12
+ * judge-provider comment warns about ("a second reading of the declaration
13
+ * that can drift from the one the runner actually executes"). So there is one
14
+ * reading now, and every caller shares it.
15
+ *
16
+ * Defensive on shape ON PURPOSE: callers pass raw specs read off disk and
17
+ * half-built drafts, not validated declarations. An absent/!object `closeDecl`
18
+ * or `stages` is "no judged stages", never a throw — the close VALIDATOR is
19
+ * what rejects a malformed declaration, and this predicate answering a
20
+ * question about one is not the place to re-litigate it.
21
+ * @param {any} closeDecl a close declaration (or anything, including undefined)
22
+ * @returns {any[]} the judged-floor stages, in declaration order
23
+ */
24
+ export function judgedStages(closeDecl: any): any[];
25
+ /**
26
+ * Does this close declaration judge — i.e. will a judge model be CALLED?
27
+ * The question `scripts/run-u.mjs` asks before demanding a judge key (PRD item
28
+ * 31.4): a job that never judges must run on its own provider's key alone.
29
+ * @param {any} closeDecl
30
+ * @returns {boolean}
31
+ */
32
+ export function closeJudges(closeDecl: any): boolean;
1
33
  /**
2
34
  * Does this run end at a review door? The operator's flag WINS in both
3
35
  * directions and is never inferred: `true` opens it for any class, `false` shuts
@@ -531,10 +563,16 @@ export type Red = {
531
563
  * runner — never authored, never defaulted, and absent on every ordinary run.
532
564
  * `judgeLoop` is the PAID seam the judged stage runs its locate call through, and
533
565
  * it is operator territory for the same reason the worker's provider is: this
534
- * module owns no provider and never picks one, and the judge tier is PINNED
535
- * (`JUDGE_MODEL`) rather than chosen. Absent is not a fall-back to some other
536
- * model — the stage stops as a wiring gap, the same answer `runPlan` gives a
566
+ * module owns no provider and never picks one. Absent is not a fall-back to some
567
+ * other model — the stage stops as a wiring gap, the same answer `runPlan` gives a
537
568
  * native job with no native factory.
569
+ * `judgeModel` is the seam's other half (PRD item 32.1): WHICH model that loop
570
+ * drives, resolved by the runner (`resolveJudge`, src/judged.js — the spec's signed
571
+ * `judge` override, else the job's own worker model) rather than pinned in this
572
+ * library. It is required whenever `judgeLoop` is, and for the same reason: a
573
+ * verdict whose grader cannot be named is a floor nobody can attribute, and the
574
+ * signed calibration record has nothing to compare against. The two travel
575
+ * together or the stage stops.
538
576
  * `onJudgeCost` is the METER, and it is the only reason a judged stage can be
539
577
  * funded honestly: this executor cannot reach the run's ledger, so every locate
540
578
  * call's cost is reported OUT through it, once per call, on every route including
@@ -566,6 +604,7 @@ export type Ctx = {
566
604
  judgeLoop?: (o: {
567
605
  system: string;
568
606
  }) => any;
607
+ judgeModel?: string;
569
608
  onJudgeCost?: (c: {
570
609
  stage: string | null;
571
610
  kind: string | null;
@@ -196,8 +196,15 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
196
196
  * `false` → the drafter has no tools, so a native session would report NO cost — return a
197
197
  * metered claude-json TEXT provider (`--output-format json`, `parse:'claude-json'`) instead,
198
198
  * so its spend is never invisible. The Loop path (`anthropic-api`) never touches this.
199
+ * @param {string|null} [opts.judgeModel] softgreen — WHICH model that judge provider drives
200
+ * (PRD item 32.1). Resolved by the CALLER (`resolveJudge`, src/judged.js: the spec's signed
201
+ * `judge` override, else the job's own worker model), never pinned in this library. It
202
+ * travels with `judgeProvider` or the judged stage stops as a wiring gap: a verdict whose
203
+ * grader cannot be named is a floor nobody can attribute.
199
204
  * @param {any} [opts.judgeProvider] softgreen — the provider a JUDGED close stage runs its
200
- * locate call through, pinned by the operator to `JUDGE_MODEL` (src/judged.js). Separate
205
+ * locate call through, built by the CALLER from the RESOLVED judge identity (PRD item
206
+ * 32.1: `resolveJudge`, src/judged.js — the spec's signed `judge` override, else the job's
207
+ * own worker provider and model), never pinned in this library. Separate
201
208
  * from `provider`/`providerFor` on purpose: the judge tier is not a step knob and never
202
209
  * agent-selectable, and the worker's binding is not a legal stand-in for it. Absent is not
203
210
  * a fall-back — a judged stage with no seam instrument-STOPS as a wiring gap, the same
@@ -410,7 +417,7 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
410
417
  * 'branch-red' | 'cap-halt' | 'wall-halt' | 'provider-red' | 'interpreter-red' |
411
418
  * 'step-stalled' | 'hitl-pause' | 'hitl-decision-red' | `step-red:<id>`
412
419
  */
413
- export function runPlan(job: any, { workdir, provider, nativeProvider, providerFor, judgeProvider, emit, remainingUsd, isUnpriced, spendComplete, capRuns, strikeLimit, closeTimeoutMs, closeDir, maxStepRounds, layerRoot, readShim, scout, scoutRounds, bridge, now, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, priorSpentUsd, reviewDoor, doorRerun, resumable }: {
420
+ export function runPlan(job: any, { workdir, provider, nativeProvider, providerFor, judgeProvider, judgeModel, emit, remainingUsd, isUnpriced, spendComplete, capRuns, strikeLimit, closeTimeoutMs, closeDir, maxStepRounds, layerRoot, readShim, scout, scoutRounds, bridge, now, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, priorSpentUsd, reviewDoor, doorRerun, resumable }: {
414
421
  workdir: string;
415
422
  provider: any;
416
423
  nativeProvider?: ((o: {
@@ -419,6 +426,7 @@ export function runPlan(job: any, { workdir, provider, nativeProvider, providerF
419
426
  maxTurns: number;
420
427
  hasTools: boolean;
421
428
  }) => any) | undefined;
429
+ judgeModel?: string | null | undefined;
422
430
  judgeProvider?: any;
423
431
  emit: (type: string, data?: object) => object;
424
432
  remainingUsd: () => number;
@@ -31,23 +31,31 @@ export function makeProvider(providerName: string, { apiKey, model, baseUrl }?:
31
31
  * before this factory existed — that decision stays where each runner's own
32
32
  * `--model` flag/spec precedence lives, not duplicated in here).
33
33
  *
34
- * The JUDGE stays pinned to `anthropic-api`/`judgeModel` regardless of
35
- * `providerName` — arbiter territory (PRD item 28: "the judge stays PINNED
36
- * … regardless of the job's worker provider"). A signed `judge:{provider,
37
- * model}` field with its own calibration record is PRD item 28 part (2),
38
- * NOT built here; until it lands, the judge always needs its own
39
- * `judgeApiKey` (today, always `ANTHROPIC_API_KEY`), independent of which
40
- * key the worker used.
34
+ * THE JUDGE IS NO LONGER PINNED TO ANTHROPIC (PRD item 32.2, hamr 2026-09-10:
35
+ * *"what i care about is that judge becomes llm agnostic and not set to one
36
+ * model or provider"*). It is constructed through this same factory, from a
37
+ * `judgeProviderName`/`judgeModel` pair the CALLER resolved (`resolveJudge`,
38
+ * src/judged.js — the spec's signed `judge` override, else the job's own worker
39
+ * provider and model). Item 28's pin, and item 28 part (2)'s deferral of a
40
+ * signed `judge:{provider,model}` field, both close here.
41
+ *
42
+ * `judgeApiKey` stays a SEPARATE argument rather than being derived: the judge
43
+ * may legitimately be a different provider from the worker, and reading its key
44
+ * for it would put a second env-var reader in a module whose whole job is to
45
+ * have exactly one. The caller reads the resolved judge provider's own `envKey`
46
+ * and hands the key in — the same contract the worker key already has here.
41
47
  * @param {object} args
42
48
  * @param {string} args.providerName spec.provider (or the anthropic-api default)
43
49
  * @param {string|undefined} args.apiKey the WORKER's key, read from the provider's own `envKey`
44
50
  * @param {string} args.model the already-resolved worker model id
45
51
  * @param {Readonly<Record<string, string>>} args.tierModels the already-resolved tier -> model map (spec override folded in by the caller)
46
52
  * @param {string|undefined} [args.baseUrl] spec.baseUrl, if any
47
- * @param {string|undefined} args.judgeApiKey the judge's own key (today: `ANTHROPIC_API_KEY`)
48
- * @param {string} args.judgeModel `JUDGE_MODEL`
53
+ * @param {string|undefined} args.judgeApiKey the judge's own key, read by the caller from the RESOLVED judge provider's `envKey`
54
+ * @param {string} args.judgeModel the resolved judge model id
55
+ * @param {string} [args.judgeProviderName] the resolved judge provider name; defaults to `providerName` (the job's own worker provider), which is `resolveJudge`'s own default
56
+ * @param {string|undefined} [args.judgeBaseUrl] the judge provider's endpoint, when it takes one
49
57
  */
50
- export function buildRunnerProviders({ providerName, apiKey, model, tierModels, baseUrl, judgeApiKey, judgeModel, }: {
58
+ export function buildRunnerProviders({ providerName, apiKey, model, tierModels, baseUrl, judgeApiKey, judgeModel, judgeProviderName, judgeBaseUrl, }: {
51
59
  providerName: string;
52
60
  apiKey: string | undefined;
53
61
  model: string;
@@ -55,11 +63,32 @@ export function buildRunnerProviders({ providerName, apiKey, model, tierModels,
55
63
  baseUrl?: string | undefined;
56
64
  judgeApiKey: string | undefined;
57
65
  judgeModel: string;
66
+ judgeProviderName?: string | undefined;
67
+ judgeBaseUrl?: string | undefined;
58
68
  }): {
59
69
  provider: any;
60
70
  providerFor: (tier: string) => any;
61
71
  judgeProvider: any;
62
72
  };
73
+ /**
74
+ * The launch-time warning lines for an ADMITTED-PENDING-PROBE provider, or
75
+ * `null` when the provider has paid for its own end-to-end probe (and for an
76
+ * off-table name — an unknown provider is `resolveProvider`'s named throw to
77
+ * report, never this function's warning to soften).
78
+ *
79
+ * This lives here, next to `PROBE_STATUS`, rather than inline in the runner,
80
+ * for one reason: inline it was UNTESTABLE. `scripts/run-u.mjs` reaches this
81
+ * point only with a real job row (a machine-local patient path) and a real
82
+ * signed spec, so nothing in the suite could reach the branch, and a warning
83
+ * nothing executes drifts exactly the way the green-only prose did (item 31.2).
84
+ *
85
+ * It never REFUSES, and it never returns a shape a caller can mistake for a
86
+ * refusal: hamr admitted `gemini-api` to the menu, and a probe table that
87
+ * quietly withheld it would be a second ruling nobody made.
88
+ * @param {string} providerName
89
+ * @returns {string[]|null} lines to print, in order, or null when there is nothing to say
90
+ */
91
+ export function probeWarningLines(providerName: string): string[] | null;
63
92
  /**
64
93
  * Today's anthropic-api tier table — EXACTLY the `DEFAULT_TIER_MODELS` both
65
94
  * runners hardcoded before this factory existed. Any change to these two
@@ -84,6 +113,35 @@ export const OPENAI_TIER_MODELS: Readonly<{
84
113
  sonnet: "deepseek-chat";
85
114
  haiku: "deepseek-chat";
86
115
  }>;
116
+ /**
117
+ * Gemini (PRD item 31.3, hamr 2026-09-09: "anthropic, openai, gemini drop
118
+ * ollama for now"). Two real tiers, unlike DeepSeek's single model: `flash` is
119
+ * the cheap tier by design, so `haiku` maps to it honestly rather than
120
+ * repeating one id.
121
+ *
122
+ * ADMITTED-PENDING-PROBE. The probe rule stands (PRD item 28 ruling (d),
123
+ * 2026-09-09): no endpoint is in the menu without its own clean paid probe,
124
+ * and `gemini-api` has ZERO runs. `anthropic-api` is the original (~167
125
+ * archived runs) and `openai-api` was probed green through the shipped runner
126
+ * (run mtu12vks, $1.64/$4, F157); gemini owes that fire and has not paid it.
127
+ * `PROBE_STATUS` below is the machine-readable form of that debt — nothing in
128
+ * here silently pretends the provider is proven.
129
+ */
130
+ export const GEMINI_TIER_MODELS: Readonly<{
131
+ sonnet: "gemini-2.5-pro";
132
+ haiku: "gemini-2.5-flash";
133
+ }>;
134
+ /**
135
+ * Which admitted providers have paid for their own end-to-end probe, and which
136
+ * have not. Read by the runner to print a loud marker; never read to REFUSE —
137
+ * hamr admitted gemini to the menu, and a table that quietly withheld it would
138
+ * be a second, invisible ruling. An unprobed provider runs, and says so.
139
+ * @type {Readonly<Record<string, {probed: boolean, evidence: string}>>}
140
+ */
141
+ export const PROBE_STATUS: Readonly<Record<string, {
142
+ probed: boolean;
143
+ evidence: string;
144
+ }>>;
87
145
  export type ProviderTableEntry = {
88
146
  /**
89
147
  * the bare-agent provider class
@@ -98,7 +156,11 @@ export type ProviderTableEntry = {
98
156
  */
99
157
  tiers: Readonly<Record<string, string>>;
100
158
  /**
101
- * per-model request-key/param gating, applied on TOP of {exposeErrorBody:true, apiKey, model, baseUrl?}
159
+ * the CONSTRUCTOR OPTION NAME this provider reads its endpoint from — NOT assumed to be `baseUrl` (see below)
160
+ */
161
+ endpointKey: string;
162
+ /**
163
+ * per-model request-key/param gating, applied on TOP of {exposeErrorBody:true, apiKey, model, <endpointKey>?}
102
164
  */
103
165
  paramsFor: (model: string | undefined) => Record<string, any>;
104
166
  };
package/types/run.d.ts CHANGED
@@ -34,9 +34,14 @@
34
34
  * metered claude-json text provider for the toolless drafter); ignored on every Loop-driven provider
35
35
  * @param {(type: string, data?: object) => object} opts.emit spine emitter
36
36
  * @param {(tier: string) => any} [opts.providerFor] P: per-step model-tier provider factory (forwarded to the plan flow)
37
+ * @param {string|null} [opts.judgeModel] SOFTGREEN — WHICH model the judge provider drives
38
+ * (PRD item 32.1), resolved by the CALLER (`resolveJudge`, src/judged.js) and passed
39
+ * through untouched. Travels with `judgeProvider`; absent, a judged stage stops as a
40
+ * wiring gap rather than grading under an identity nobody named.
37
41
  * @param {any} [opts.judgeProvider] SOFTGREEN — the provider a JUDGED close stage runs its
38
- * locate call through, wired by the operator and PINNED to `JUDGE_MODEL` (src/judged.js:
39
- * the only tier with established injection resistance upstream). Forwarded to the plan
42
+ * locate call through, built by the CALLER from the RESOLVED judge identity (PRD item
43
+ * 32.1: `resolveJudge`/`resolveJobJudge`, src/judged.js — the spec's signed `judge`
44
+ * override, else the job's own worker provider and model), never pinned. Forwarded to the plan
40
45
  * flow. It is its own seam and not `provider`/`providerFor`, because the judge tier is
41
46
  * never a step knob and never agent-selectable; absent, a judged stage instrument-stops as
42
47
  * a wiring gap rather than grading on whatever binding happened to be at hand. Its spend
@@ -176,7 +181,7 @@
176
181
  * deliberately NOT in the resumable/checkpoint set (`src/reuse.js`) — an
177
182
  * unknown in-flight state is not a known-safe resume point.
178
183
  */
179
- export function runJob(rawSpec: object | string, { approvals, workdir, provider, nativeProvider, providerFor, judgeProvider, emit, capRuns, strikeLimit, shellCapUsd, closeTimeoutMs, closeDir, layerRoot, readShim, scout, bridge, priorSpentUsd, priorSpendComplete, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, reviewDoor, doorRerun, resumable }: {
184
+ export function runJob(rawSpec: object | string, { approvals, workdir, provider, nativeProvider, providerFor, judgeProvider, judgeModel, emit, capRuns, strikeLimit, shellCapUsd, closeTimeoutMs, closeDir, layerRoot, readShim, scout, bridge, priorSpentUsd, priorSpendComplete, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, reviewDoor, doorRerun, resumable }: {
180
185
  approvals: unknown;
181
186
  workdir: string;
182
187
  provider: any;
@@ -188,6 +193,7 @@ export function runJob(rawSpec: object | string, { approvals, workdir, provider,
188
193
  }) => any) | undefined;
189
194
  emit: (type: string, data?: object) => object;
190
195
  providerFor?: ((tier: string) => any) | undefined;
196
+ judgeModel?: string | null | undefined;
191
197
  judgeProvider?: any;
192
198
  capRuns?: number | undefined;
193
199
  strikeLimit?: number | undefined;