onto-mcp 0.4.13 → 0.4.14

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.
@@ -0,0 +1,262 @@
1
+ import { defaultAuthForProvider, } from "../llm/model-switcher.js";
2
+ import { reviewExecutionUnitActor } from "../review/review-execution-profile.js";
3
+ import { PerCallLlmOverrideSchema, RECONSTRUCT_ACTOR_KEYS, REVIEW_EXECUTION_UNIT_IDS, } from "./settings-chain.js";
4
+ /**
5
+ * Per-call LLM override overlay (design v4 §2.2). A `PerCallLlmOverride` is an
6
+ * ephemeral settings-`llm` edit applied to the resolved {@link OntoSettings}
7
+ * for ONE review/reconstruct invocation, after which the existing pipeline runs
8
+ * unchanged. These helpers are PURE and IMMUTABLE: they never mutate the input
9
+ * settings, and an absent/empty override is the IDENTITY (returns the same
10
+ * object reference), which is what makes the default-off path byte-identical.
11
+ *
12
+ * Per-block semantics:
13
+ * - `override.provider` present → REPLACE: the block becomes `{...override}`.
14
+ * The switched-in provider's transport (base_url/api_key_env/service_tier/
15
+ * timeout_ms) resolves fresh from model-switcher defaults; the old provider's
16
+ * transport MUST NOT leak, so the previous block is dropped entirely. The
17
+ * override schema excludes transport/credential fields, so a plain copy is
18
+ * already transport-clean.
19
+ * - `override.provider` absent → field-OVERLAY: `{...block, ...override}` — only
20
+ * the named fields (effort/model/service_tier/auth) win over the existing
21
+ * same-provider block.
22
+ */
23
+ function isEmptyOverride(override) {
24
+ return Object.keys(override).length === 0;
25
+ }
26
+ /**
27
+ * Parse a per-call override serialized as a single `--llm-override <json>` CLI
28
+ * arg back into a validated {@link PerCallLlmOverride}. The review pipeline
29
+ * crosses a process/argv boundary, so the override is JSON-serialized by the API
30
+ * seam and re-materialized (+ shape-revalidated via the canonical schema) at
31
+ * each settings-resolution seam that must apply it. Absent/empty → undefined
32
+ * (identity). Throws on malformed JSON or a shape violation (fail-loud).
33
+ */
34
+ export function parsePerCallLlmOverrideArg(raw) {
35
+ if (raw === undefined || raw.length === 0)
36
+ return undefined;
37
+ const parsed = PerCallLlmOverrideSchema.parse(JSON.parse(raw));
38
+ // An EMPTY override is semantically an omission. Canonicalize it to undefined
39
+ // so every downstream truthiness check agrees: otherwise the overlay treats
40
+ // `{}` as identity while a `if (llmOverride)` gate still fires, making `{}`
41
+ // observably different from omission (breaking the default-off guarantee).
42
+ return isEmptyOverride(parsed) ? undefined : parsed;
43
+ }
44
+ /**
45
+ * Route identity of a block AFTER an override. Compared by EFFECTIVE value, not
46
+ * by field presence: an override may legitimately restate the current
47
+ * provider/auth just to change the model, and that is NOT a route change — so it
48
+ * must keep the block's route-scoped transport (api_key_env/base_url/
49
+ * service_tier) and any provider-scoped runtime settings.
50
+ */
51
+ function effectiveRoute(block, override) {
52
+ // Compare the DEFAULTED auth, not the raw field: a block that omits `auth`
53
+ // still dispatches on `defaultAuthForProvider(provider)` (anthropic/grok →
54
+ // api_key, openai → oauth, lmstudio → local). Comparing the raw undefined
55
+ // would read an override that merely restates that effective auth as a switch
56
+ // and discard the block's still-valid transport (api_key_env/base_url).
57
+ const blockAuth = block.auth ??
58
+ (block.provider !== undefined ? defaultAuthForProvider(block.provider) : undefined);
59
+ return {
60
+ providerChanged: (override.provider ?? block.provider) !== block.provider,
61
+ authChanged: (override.auth ?? blockAuth) !== blockAuth,
62
+ };
63
+ }
64
+ /** Whether an override changes the block's ROUTE identity (provider or auth). */
65
+ function overrideChangesRoute(block, override) {
66
+ const { providerChanged, authChanged } = effectiveRoute(block, override);
67
+ return providerChanged || authChanged;
68
+ }
69
+ /**
70
+ * Core per-`llm`-block override (REPLACE vs route-cleaned vs field-OVERLAY).
71
+ *
72
+ * `routeBasis` is the block whose ROUTE identity the decision is made on, which
73
+ * is not always `block` itself: a review UNIT's llm is a PARTIAL block merged
74
+ * over its default actor, so its route lives in the merged result. Judging a
75
+ * provider-less unit on its own partial block would report a provider change for
76
+ * every provider-bearing override (comparing against `undefined`). The override
77
+ * is still APPLIED to `block` so the unit keeps its partial shape.
78
+ */
79
+ function applyLlmBlockOverride(block, override, routeBasis = block) {
80
+ const { providerChanged, authChanged } = effectiveRoute(routeBasis, override);
81
+ if (providerChanged) {
82
+ // REPLACE (provider switch): drop the old block so no stale transport
83
+ // (base_url/api_key_env/service_tier/timeout_ms) leaks into the new
84
+ // provider. override carries only {provider, auth?, model?, effort?,
85
+ // service_tier?}, so the copy is transport-clean by construction.
86
+ return { ...override };
87
+ }
88
+ if (authChanged) {
89
+ // AUTH switch is ALSO a route switch (oauth ↔ api_key normalize to
90
+ // different execution routes/adapters), so the previous route's scoped
91
+ // fields must not survive: `service_tier` is openai+oauth-only (
92
+ // normalizeLlmModelSwitcher rejects it on any other route) and
93
+ // `api_key_env`/`base_url` belong to the direct-call routes. Keeping them
94
+ // would turn an otherwise valid auth switch into a hard failure (or leak an
95
+ // api-key endpoint into an OAuth route). The override may re-state
96
+ // service_tier; base_url/api_key_env stay settings-owned by design.
97
+ const { service_tier: _serviceTier, api_key_env: _apiKeyEnv, base_url: _baseUrl, ...routeAgnostic } = block;
98
+ return { ...routeAgnostic, ...override };
99
+ }
100
+ // OVERLAY (same route — including an override that restates the current
101
+ // provider/auth): only the named fields win; transport is preserved.
102
+ return { ...block, ...override };
103
+ }
104
+ // ── review scope ──────────────────────────────────────────────────────────
105
+ const REVIEW_ACTOR_KEYS = ["teamlead", "lens", "synthesize"];
106
+ /**
107
+ * Apply a per-call override to the review dispatch seats of `settings`
108
+ * (design v4 §2.2). Overlays every configured actor llm
109
+ * (`review.execution.{teamlead,lens,synthesize}.llm`) and every configured unit
110
+ * llm (`review.execution.units[id].llm`), plus the salvage transcription model.
111
+ * Actors/units WITHOUT an explicit llm are left untouched (their inheritance is
112
+ * unchanged). In REPLACE mode each unit's own llm is DROPPED so it inherits the
113
+ * replaced actor rather than keeping a stale unit model.
114
+ */
115
+ export function applyReviewLlmOverride(settings, override) {
116
+ if (!override || isEmptyOverride(override))
117
+ return settings; // IDENTITY
118
+ const execution = settings.review?.execution;
119
+ if (!execution)
120
+ return settings;
121
+ const nextExecution = { ...execution };
122
+ for (const actorKey of REVIEW_ACTOR_KEYS) {
123
+ const actor = execution[actorKey];
124
+ if (actor?.llm) {
125
+ nextExecution[actorKey] = {
126
+ ...actor,
127
+ llm: applyLlmBlockOverride(actor.llm, override),
128
+ };
129
+ }
130
+ }
131
+ if (execution.units) {
132
+ const nextUnits = {};
133
+ for (const unitId of REVIEW_EXECUTION_UNIT_IDS) {
134
+ const unit = execution.units[unitId];
135
+ if (!unit)
136
+ continue;
137
+ if (!unit.llm) {
138
+ nextUnits[unitId] = unit;
139
+ continue;
140
+ }
141
+ // A unit's llm is a PARTIAL block that the runtime merges over its default
142
+ // actor ({...actorLlm, ...unitLlm}), so the unit's real route is that
143
+ // merge — judge the override against it, not against the partial block.
144
+ const unitRouteBasis = {
145
+ ...(execution[reviewExecutionUnitActor(unitId)]?.llm ?? {}),
146
+ ...unit.llm,
147
+ };
148
+ if (effectiveRoute(unitRouteBasis, override).providerChanged) {
149
+ // REPLACE: drop the unit's llm so it inherits the replaced actor
150
+ // (no stale unit model on the old provider).
151
+ const { llm: _dropped, ...rest } = unit;
152
+ nextUnits[unitId] = rest;
153
+ }
154
+ else {
155
+ // Units go through the SAME block rules as actors — a raw spread here
156
+ // would keep the previous route's scoped fields (e.g. a unit's own
157
+ // openai/oauth `service_tier` surviving an `auth: "api_key"` override),
158
+ // so the unit route would be rejected even though the actor was cleaned.
159
+ nextUnits[unitId] = {
160
+ ...unit,
161
+ llm: applyLlmBlockOverride(unit.llm, override, unitRouteBasis),
162
+ };
163
+ }
164
+ }
165
+ nextExecution.units = nextUnits;
166
+ }
167
+ const transcription = execution.retry?.salvage?.transcription_llm;
168
+ if (transcription) {
169
+ nextExecution.retry = {
170
+ ...execution.retry,
171
+ salvage: {
172
+ ...execution.retry?.salvage,
173
+ transcription_llm: applySalvageTranscriptionOverride(transcription, override),
174
+ },
175
+ };
176
+ }
177
+ return {
178
+ ...settings,
179
+ review: { ...settings.review, execution: nextExecution },
180
+ };
181
+ }
182
+ /**
183
+ * Salvage transcription_llm has a RESTRICTED shape ({provider?: "anthropic" |
184
+ * "openai"; model: string}) — it is a cheap-tier transcription model run by the
185
+ * unit's OWN adapter, so only anthropic/openai are representable. Overlay the
186
+ * model always; overlay the provider only when salvage can express it.
187
+ *
188
+ * A grok/lmstudio override leaves the seat untouched, and that is INERT rather
189
+ * than a mixed route: those providers resolve to the direct-call route, and the
190
+ * runner only attempts salvage on a `claude_code`/`codex` worker executor (see
191
+ * run-review-prompt-execution: `salvageAdapter === "claude_code" || "codex"`),
192
+ * so salvage cannot dispatch there at all. This is also exactly what editing the
193
+ * same provider into settings would do — the override is a settings overlay, so
194
+ * it must not invent a stricter contract than the settings path has.
195
+ */
196
+ function applySalvageTranscriptionOverride(transcription, override) {
197
+ // A switch to a salvage-incompatible provider (grok/lmstudio) leaves salvage
198
+ // ENTIRELY unchanged — the model would otherwise land on an anthropic/openai
199
+ // route it does not belong to. Salvage is opt-in / default-off, so
200
+ // under-applying here fails closed, never dispatches an unverified call.
201
+ if (override.provider === "grok" || override.provider === "lmstudio") {
202
+ return transcription;
203
+ }
204
+ const next = { ...transcription };
205
+ if (override.model !== undefined)
206
+ next.model = override.model;
207
+ if (override.provider === "anthropic" || override.provider === "openai") {
208
+ next.provider = override.provider;
209
+ }
210
+ return next;
211
+ }
212
+ // ── reconstruct scope ───────────────────────────────────────────────────────
213
+ /**
214
+ * Apply a per-call override to the reconstruct dispatch seats of `settings`
215
+ * (design v4 §2.2). Overlays the actor seats
216
+ * (`reconstruct.execution.actors.{semantic_author,confirmation_provider,
217
+ * semantic_map_synthesize}.llm`) with full REPLACE/OVERLAY semantics, and threads
218
+ * the override EFFORT into the opt-in `dispatch_fallback.llm` (the faithful
219
+ * replacement of the removed `llmEffort` pin).
220
+ */
221
+ export function applyReconstructLlmOverride(settings, override) {
222
+ if (!override || isEmptyOverride(override))
223
+ return settings; // IDENTITY
224
+ const execution = settings.reconstruct?.execution;
225
+ if (!execution)
226
+ return settings;
227
+ const nextExecution = { ...execution };
228
+ if (execution.actors) {
229
+ const nextActors = { ...execution.actors };
230
+ for (const actorKey of RECONSTRUCT_ACTOR_KEYS) {
231
+ const actor = execution.actors[actorKey];
232
+ if (actor) {
233
+ const llm = applyLlmBlockOverride(actor.llm, override);
234
+ // `llm_runtime` (openai Responses output headroom) is scoped to the
235
+ // openai + api_key direct-call route, so it must NOT survive a route
236
+ // change: reconstruct would apply openai-only headroom to the new route
237
+ // and fail before dispatch, and the caller cannot clear it through
238
+ // `llmOverride` (transport/runtime fields are settings-owned).
239
+ nextActors[actorKey] = overrideChangesRoute(actor.llm, override)
240
+ ? { llm }
241
+ : { ...actor, llm };
242
+ }
243
+ }
244
+ nextExecution.actors = nextActors;
245
+ }
246
+ // dispatch_fallback.llm is a RESTRICTED, alternate-provider shape (requires
247
+ // api_key_env; provider constrained to openai/anthropic; model is
248
+ // provider-specific). It exists specifically to be a DIFFERENT provider than
249
+ // the primary, so a per-call provider/model switch must NOT collapse it onto
250
+ // the primary. Only the provider-agnostic EFFORT knob flows through — this is
251
+ // exactly what the removed `llmEffort` pin used to apply here (v4 §6(a)).
252
+ if (execution.dispatch_fallback?.enabled === true && override.effort !== undefined) {
253
+ nextExecution.dispatch_fallback = {
254
+ ...execution.dispatch_fallback,
255
+ llm: { ...execution.dispatch_fallback.llm, effort: override.effort },
256
+ };
257
+ }
258
+ return {
259
+ ...settings,
260
+ reconstruct: { ...settings.reconstruct, execution: nextExecution },
261
+ };
262
+ }
@@ -163,7 +163,7 @@ export function assembleReviewCertRecord(args) {
163
163
  if (guardViolations.length > 0) {
164
164
  return { record: null, violations: guardViolations };
165
165
  }
166
- const aggregates = computeReviewCertAggregates(args.runs, args.fixtures.map((fixture) => fixture.fixture_id));
166
+ const aggregates = computeReviewCertAggregates(args.runs, args.fixtures);
167
167
  const armDispatch = (arm) => {
168
168
  const effort = projection.witnessed[arm].reasoning_effort;
169
169
  return effort !== undefined ? { reasoning_effort: effort } : {};
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { SEMANTIC_QUALITY_GATE_CHECK_IDS, } from "../review/semantic-quality-gate.js";
2
+ import { CLEAN_TARGET_EXCLUDED_CHECK_IDS, SEMANTIC_QUALITY_GATE_CHECK_IDS, } from "../review/semantic-quality-gate.js";
3
3
  import { SynthesizeCertDispatchConfigSchema } from "./synthesize-cert-record.js";
4
4
  // ─────────────────────────────────────────────────────────────────────────────
5
5
  // review-cert/v1 — the evidence contract behind the `review` role
@@ -51,14 +51,49 @@ const Sha256Schema = z.string().regex(/^[0-9a-f]{64}$/, "expected a lowercase sh
51
51
  const ArmModelSchema = z
52
52
  .object({ provider: IdSchema, model: IdSchema })
53
53
  .strict();
54
+ const CheckIdSchema = z.enum(SEMANTIC_QUALITY_GATE_CHECK_IDS);
55
+ /** The single legal reduced applicable set (v3 clean-target): the full gate
56
+ * universe minus the checks a clean target omits. Derived from the gate's
57
+ * CLEAN_TARGET_EXCLUDED authority so the two cannot drift. Sorted for compare. */
58
+ const CLEAN_TARGET_APPLICABLE_CHECK_IDS = [
59
+ ...SEMANTIC_QUALITY_GATE_CHECK_IDS,
60
+ ]
61
+ .filter((id) => !CLEAN_TARGET_EXCLUDED_CHECK_IDS.has(id))
62
+ .sort();
63
+ /** Fixtures permitted to declare a REDUCED applicable_check_ids. A reduced set
64
+ * shrinks a fixture's core floor, so the deterministic validator — not just the
65
+ * honest harness — must gate it: only a designated clean-target fixture may
66
+ * reduce, or a material-bearing fixture could drop its recall spine and certify
67
+ * silently. Identity is by fixture_id; binding the id to the actual clean blob
68
+ * via content_sha256 is Phase B (design §D2/§D5). */
69
+ const REDUCED_APPLICABLE_FIXTURE_IDS = new Set([
70
+ "clean-target-v1",
71
+ ]);
72
+ /** The applicable_check_ids a fixture's manifest entry MUST declare — the single
73
+ * authority shared by the cert-record PRODUCER (scripts/review-cert-run.mts) and
74
+ * this validator, so the two cannot drift. A designated clean-target fixture
75
+ * declares the reduced set; every other fixture declares nothing (undefined =
76
+ * absent = full universe, byte-identical v2 recompute). The producer emits
77
+ * exactly this; the validator below recomputes and rejects any mismatch. */
78
+ export function fixtureApplicableCheckIds(fixtureId) {
79
+ return REDUCED_APPLICABLE_FIXTURE_IDS.has(fixtureId)
80
+ ? CLEAN_TARGET_APPLICABLE_CHECK_IDS
81
+ : undefined;
82
+ }
54
83
  const FixtureManifestEntrySchema = z
55
84
  .object({
56
85
  fixture_id: IdSchema,
57
86
  target_anchor: z.string().min(1),
58
87
  content_sha256: Sha256Schema,
88
+ /** v3 (design 20260712 §D2): the check subset this fixture's ok runs emit
89
+ * and that aggregates iterate. ABSENT = the full gate universe — so v2
90
+ * records and the existing code fixtures recompute byte-for-byte. A
91
+ * clean-target fixture declares its reduced applicable set here (recall/
92
+ * grounding/actionability are N/A with no material defect). Additive-
93
+ * optional: the wire contract stays review-cert/v2, no G7 bump. */
94
+ applicable_check_ids: z.array(CheckIdSchema).min(1).optional(),
59
95
  })
60
96
  .strict();
61
- const CheckIdSchema = z.enum(SEMANTIC_QUALITY_GATE_CHECK_IDS);
62
97
  const RunCheckSchema = z
63
98
  .object({
64
99
  check_id: CheckIdSchema,
@@ -174,26 +209,40 @@ function passRate(runs, arm, fixtureId, checkId) {
174
209
  const passed = completed.filter((run) => run.checks.some((check) => check.check_id === checkId && check.status === "passed")).length;
175
210
  return passed / completed.length;
176
211
  }
212
+ /** The check set a fixture's ok runs emit and that aggregates iterate: its
213
+ * declared applicable_check_ids, or the full gate universe when absent (v2 /
214
+ * existing fixtures — byte-identical recompute). */
215
+ function applicableChecks(fixture) {
216
+ return fixture.applicable_check_ids ?? SEMANTIC_QUALITY_GATE_CHECK_IDS;
217
+ }
177
218
  /**
178
219
  * The ONE aggregate computation both the validator (recompute/compare) and the
179
220
  * harness assembler (declare) consume — a second rate implementation could
180
- * silently disagree with the gate. Fixture×check pairs where either arm has no
181
- * completed run are OMITTED (rep_floor reports that state separately).
182
- * `quality_pass` = every recall-first core check meets the absolute floor.
183
- * Candidate<baseline regressions outside that decisive spine are retained in
184
- * the aggregate rows and projected by {@link reviewCertQualityDisclosures}.
221
+ * silently disagree with the gate. Iterates each fixture's APPLICABLE check set
222
+ * only (v3 §D2): a clean-target fixture omits recall/grounding/actionability, so
223
+ * they never enter the core-floor judgment as vacuous 0-rate rows. Fixture×check
224
+ * pairs where either arm has no completed run are OMITTED (rep_floor reports
225
+ * that state separately). `quality_pass` = every recall-first core check that IS
226
+ * applicable meets the absolute floor. Candidate<baseline regressions outside
227
+ * that decisive spine are retained in the aggregate rows and projected by
228
+ * {@link reviewCertQualityDisclosures}. Duplicate fixture_ids are collapsed
229
+ * (first wins; duplicate_manifest_input is reported separately).
185
230
  */
186
- export function computeReviewCertAggregates(runs, fixtureIds) {
231
+ export function computeReviewCertAggregates(runs, fixtures) {
187
232
  const rows = [];
188
233
  let qualityPass = true;
189
- for (const fixtureId of fixtureIds) {
190
- for (const checkId of SEMANTIC_QUALITY_GATE_CHECK_IDS) {
191
- const baselineRate = passRate(runs, "baseline", fixtureId, checkId);
192
- const candidateRate = passRate(runs, "candidate", fixtureId, checkId);
234
+ const seen = new Set();
235
+ for (const fixture of fixtures) {
236
+ if (seen.has(fixture.fixture_id))
237
+ continue;
238
+ seen.add(fixture.fixture_id);
239
+ for (const checkId of applicableChecks(fixture)) {
240
+ const baselineRate = passRate(runs, "baseline", fixture.fixture_id, checkId);
241
+ const candidateRate = passRate(runs, "candidate", fixture.fixture_id, checkId);
193
242
  if (baselineRate === null || candidateRate === null)
194
243
  continue;
195
244
  rows.push({
196
- fixture_id: fixtureId,
245
+ fixture_id: fixture.fixture_id,
197
246
  check_id: checkId,
198
247
  baseline_pass_rate: baselineRate,
199
248
  candidate_pass_rate: candidateRate,
@@ -236,8 +285,7 @@ export function reviewCertResubmitDisclosure(record) {
236
285
  });
237
286
  }
238
287
  export function reviewCertQualityDisclosures(record) {
239
- const fixtureIds = [...new Set(record.fixtures.map((fixture) => fixture.fixture_id))];
240
- return computeReviewCertAggregates(record.runs, fixtureIds).per_fixture_check
288
+ return computeReviewCertAggregates(record.runs, record.fixtures).per_fixture_check
241
289
  .filter((row) => row.candidate_pass_rate < row.baseline_pass_rate &&
242
290
  !ratesEqual(row.candidate_pass_rate, row.baseline_pass_rate))
243
291
  .map((row) => {
@@ -296,8 +344,35 @@ export function validateReviewCertRecord(record) {
296
344
  if (!record.gate_pin.issue_artifacts_provided) {
297
345
  push("check_universe_mismatch", "gate_pin.issue_artifacts_provided must be true — without issue artifacts the gate emits a subset universe");
298
346
  }
347
+ // v3 §D2: applicable_check_ids is a per-fixture DECLARATION that can shrink a
348
+ // fixture's core floor, so the deterministic validator must constrain it — an
349
+ // unconstrained reduced set lets a MATERIAL-bearing fixture drop its recall
350
+ // spine and certify silently. Only a designated clean-target fixture may
351
+ // reduce, and only to the single legal clean-target reduction.
352
+ for (const fixture of record.fixtures) {
353
+ if (fixture.applicable_check_ids === undefined)
354
+ continue;
355
+ if (!REDUCED_APPLICABLE_FIXTURE_IDS.has(fixture.fixture_id)) {
356
+ push("applicable_check_ids_invalid", `fixture ${fixture.fixture_id} declares a reduced applicable_check_ids but is not a designated clean-target fixture — a material-bearing fixture must emit the full check universe`, fixture.fixture_id);
357
+ continue;
358
+ }
359
+ const declared = [...new Set(fixture.applicable_check_ids)].sort();
360
+ if (declared.length !== CLEAN_TARGET_APPLICABLE_CHECK_IDS.length ||
361
+ declared.some((id, index) => id !== CLEAN_TARGET_APPLICABLE_CHECK_IDS[index])) {
362
+ push("applicable_check_ids_invalid", `fixture ${fixture.fixture_id} applicable_check_ids must equal the clean-target reduction (${CLEAN_TARGET_APPLICABLE_CHECK_IDS.length} checks: ${CLEAN_TARGET_APPLICABLE_CHECK_IDS.join(", ")})`, fixture.fixture_id);
363
+ }
364
+ }
299
365
  // Run rows: unique coordinates, manifest membership, completion honesty,
300
- // full universe emission on every completed run.
366
+ // applicable-set emission on every completed run. Each fixture's ok runs must
367
+ // emit EXACTLY its applicable set (the full universe when it declares none —
368
+ // v2 / existing fixtures); a clean-target fixture emits its reduced set. The
369
+ // check_universe pin above stays the full vocabulary regardless (§D2).
370
+ const expectedEmissionByFixture = new Map();
371
+ for (const fixture of record.fixtures) {
372
+ if (expectedEmissionByFixture.has(fixture.fixture_id))
373
+ continue;
374
+ expectedEmissionByFixture.set(fixture.fixture_id, [...new Set(applicableChecks(fixture))].sort());
375
+ }
301
376
  const seen = new Set();
302
377
  const fixtureSet = new Set(fixtureIds);
303
378
  for (const run of record.runs) {
@@ -313,11 +388,12 @@ export function validateReviewCertRecord(record) {
313
388
  if (run.units_completed !== run.units_total) {
314
389
  push("units_incomplete", `run ${coordinate} claims completion=ok with ${run.units_completed}/${run.units_total} units — a partial run must be not_run`, coordinate);
315
390
  }
391
+ const expected = expectedEmissionByFixture.get(run.fixture_id) ?? canonical;
316
392
  const emitted = [...new Set(run.checks.map((check) => check.check_id))].sort();
317
- if (run.checks.length !== canonical.length ||
318
- emitted.length !== canonical.length ||
319
- emitted.some((id, index) => id !== canonical[index])) {
320
- push("check_emission_incomplete", `run ${coordinate} must emit the full check universe exactly once (${canonical.length} checks); got ${run.checks.length}`, coordinate);
393
+ if (run.checks.length !== expected.length ||
394
+ emitted.length !== expected.length ||
395
+ emitted.some((id, index) => id !== expected[index])) {
396
+ push("check_emission_incomplete", `run ${coordinate} must emit its applicable check set exactly once (${expected.length} checks); got ${run.checks.length}`, coordinate);
321
397
  }
322
398
  }
323
399
  else if (run.checks.length > 0) {
@@ -344,7 +420,7 @@ export function validateReviewCertRecord(record) {
344
420
  if (!ratesEqual(record.declared_aggregates.core_check_floor, REVIEW_CERT_CORE_CHECK_FLOOR)) {
345
421
  push("aggregate_mismatch", `declared core_check_floor=${record.declared_aggregates.core_check_floor} does not match the contract floor ${REVIEW_CERT_CORE_CHECK_FLOOR}`);
346
422
  }
347
- const computed = computeReviewCertAggregates(record.runs, [...fixtureSet]);
423
+ const computed = computeReviewCertAggregates(record.runs, record.fixtures);
348
424
  for (const row of computed.per_fixture_check) {
349
425
  const subject = `${row.fixture_id}/${row.check_id}`;
350
426
  const declared = declaredByKey.get(`${row.fixture_id}\u0000${row.check_id}`);
@@ -359,6 +435,28 @@ export function validateReviewCertRecord(record) {
359
435
  push("core_check_floor", `${subject}: candidate pass-rate ${row.candidate_pass_rate.toFixed(4)} is below the absolute core-check floor ${REVIEW_CERT_CORE_CHECK_FLOOR.toFixed(4)}`, subject);
360
436
  }
361
437
  }
438
+ // Declared-aggregate set integrity (v3 A-2): the loop above proves every
439
+ // COMPUTED row is declared and recomputes; this reverse pass proves the declared
440
+ // side carries no EXTRA and no DUPLICATE rows. A declared aggregate for a
441
+ // fixture×check pair that is not applicable (a clean-target's excluded recall/
442
+ // grounding checks) or has no completed run never appears in `computed`, so its
443
+ // asserted rate cannot be recomputed; a repeated pair would let a spurious rate
444
+ // ride alongside the honest one. Together the passes pin the declared aggregate
445
+ // set to EXACTLY the computed set — one rate per applicable, computed pair.
446
+ const computedKeys = new Set(computed.per_fixture_check.map((row) => `${row.fixture_id}\u0000${row.check_id}`));
447
+ const declaredSeen = new Set();
448
+ for (const row of record.declared_aggregates.per_fixture_check) {
449
+ const key = `${row.fixture_id}\u0000${row.check_id}`;
450
+ const subject = `${row.fixture_id}/${row.check_id}`;
451
+ if (declaredSeen.has(key)) {
452
+ push("aggregate_mismatch", `declared aggregate for ${subject} appears more than once — a fixture×check pair must declare exactly one rate`, subject);
453
+ continue;
454
+ }
455
+ declaredSeen.add(key);
456
+ if (computedKeys.has(key))
457
+ continue;
458
+ push("aggregate_mismatch", `declared aggregate for ${subject} does not recompute — the pair is not applicable or has no completed run (over-declaration)`, subject);
459
+ }
362
460
  if (record.declared_aggregates.quality_pass !== computed.quality_pass) {
363
461
  push("aggregate_mismatch", `declared quality_pass=${record.declared_aggregates.quality_pass} does not recompute (recomputed ${computed.quality_pass})`);
364
462
  }
@@ -55,6 +55,21 @@ const ReviewActorLlmSettingsSchema = z
55
55
  timeout_ms: z.number().int().min(1).optional(),
56
56
  })
57
57
  .strict();
58
+ // Per-call LLM override (MCP tool surface): an ephemeral settings-`llm` overlay
59
+ // applied to the resolved settings for one review/reconstruct invocation, after
60
+ // which the existing pipeline runs unchanged. Single-sourced from
61
+ // LlmSettingsSchema minus the transport/credential fields (base_url, api_key_env,
62
+ // timeout_ms) — those stay settings-owned so a per-call override cannot select an
63
+ // arbitrary endpoint or credential env. The chosen provider's transport resolves
64
+ // from settings exactly as a settings edit would. .strict() rejects the excluded
65
+ // fields. See development-records/design/per-call-llm-override-design-v4.md.
66
+ export const PerCallLlmOverrideSchema = LlmSettingsSchema.pick({
67
+ provider: true,
68
+ auth: true,
69
+ model: true,
70
+ effort: true,
71
+ service_tier: true,
72
+ }).strict();
58
73
  const LlmRefSchema = LlmSettingsSchema;
59
74
  const ReviewWorkerSeatSchema = z.enum(["main", "worker"]);
60
75
  const ReviewExecutionModeSchema = z.enum(["main-workers", "nested-workers"]);
@@ -187,8 +202,10 @@ const DEFAULT_REVIEW_EXECUTION = {
187
202
  deliberation: "controlled-lens-deliberation",
188
203
  };
189
204
  export const DEFAULT_DISPATCH_BREAKER_SETTINGS = {
190
- // opt-in: OFF = 현행 동작 보존 활성화는 settings로만.
191
- enabled: false,
205
+ // DEFAULT ON (2026-07-15 owner-directed promotion): the fan-out dispatch
206
+ // breaker (trip/poison/recover) is on unless a setting disables it. Disable
207
+ // with `dispatch_breaker.enabled=false`.
208
+ enabled: true,
192
209
  systemic_threshold: 3,
193
210
  per_call_max_attempts: 3,
194
211
  backoff_initial_ms: 3000,
@@ -212,11 +229,15 @@ const DEFAULT_REVIEW_RETRY_SETTINGS = {
212
229
  deliberation_max_retries: 2,
213
230
  synthesis_max_retries: 2,
214
231
  retry_initial_delay_ms: 3000,
215
- // opt-in: 벤치마크 재현성 보호 활성화는 settings로만.
216
- salvage: { enabled: false, delta_completion: "unit_llm" },
217
- // opt-in: OFF = 현행 halt 동작 보존 활성화는 settings로만.
218
- resubmit: { enabled: false },
219
- // opt-in: OFF = 현행 halt/배리어 동작 보존 — 활성화는 settings로만.
232
+ // DEFAULT ON (2026-07-15 owner-directed promotion): submit-salvage recovery is
233
+ // on in real usage unless disabled with `salvage.enabled=false`. The review-cert
234
+ // harness pins it OFF explicitly (benchmark --no-salvage) for raw, reproducible
235
+ // measurement that pin is cert-only, not the product default.
236
+ salvage: { enabled: true, delta_completion: "unit_llm" },
237
+ // DEFAULT ON (2026-07-15 owner-directed promotion): bounded unit resubmit on
238
+ // validation rejection is on unless disabled with `resubmit.enabled=false`.
239
+ resubmit: { enabled: true },
240
+ // DEFAULT ON via DEFAULT_DISPATCH_BREAKER_SETTINGS (2026-07-15 promotion).
220
241
  dispatch_breaker: DEFAULT_DISPATCH_BREAKER_SETTINGS,
221
242
  };
222
243
  const DEFAULT_REVIEW_UNIT_TIMEOUT_MS = 240000;
@@ -564,11 +585,12 @@ export function resolveOptionalReconstructActorLlmSettings(settings, actorName)
564
585
  normalizeLlmModelSwitcher(actor.llm);
565
586
  return actor.llm;
566
587
  }
567
- /** Production opt-in for the semantic-map authoring stage (design §5.5).
568
- * Absent/false = off: the capability pair is not attached AND the synthesize
569
- * seat is dormant (excluded from the gate walk U6, salvage precedent). */
588
+ /** Semantic-map authoring stage (design §5.5). DEFAULT ON (2026-07-15
589
+ * owner-directed promotion): absent = on; only an explicit `false` disables it,
590
+ * which detaches the capability pair AND leaves the synthesize seat dormant
591
+ * (excluded from the gate walk — U6, salvage precedent). */
570
592
  export function isReconstructSemanticMapAuthoringEnabled(settings) {
571
- return settings.reconstruct?.execution?.semantic_map_authoring === true;
593
+ return settings.reconstruct?.execution?.semantic_map_authoring !== false;
572
594
  }
573
595
  export const SETTINGS_FILENAME = "settings.json";
574
596
  export const RETIRED_CONFIG_FILENAMES = [
@@ -6,7 +6,13 @@ export function isDirectModelCallSelection(selection) {
6
6
  }
7
7
  export const DEFAULT_GROK_BASE_URL = "https://api.x.ai/v1";
8
8
  export const DEFAULT_LMSTUDIO_BASE_URL = "http://localhost:1234/v1";
9
- function defaultAuthForProvider(provider) {
9
+ /**
10
+ * The auth a block resolves to when it omits `auth`. Exported because callers
11
+ * that reason about ROUTE IDENTITY (e.g. the per-call llmOverride overlay) must
12
+ * compare the DEFAULTED auth, not the raw field — a block without `auth` still
13
+ * dispatches on this route, so an override restating it is not a route change.
14
+ */
15
+ export function defaultAuthForProvider(provider) {
10
16
  if (provider === "openai")
11
17
  return "oauth";
12
18
  if (provider === "lmstudio")
@@ -7200,7 +7200,7 @@ export function createDirectCallReconstructDirectiveAuthor(args = {}) {
7200
7200
  // of the EFFECTIVE config (model/adapter/base_url/effort — always folded,
7201
7201
  // no equality judgment); else ⑤a arg present → legacy string, byte-identical
7202
7202
  // to the pre-seat format (existing reuse keys never rotate); else absent
7203
- // (seat 부재·인자 부재 = 현행 byte-parity — a request llmEffort pin alone
7203
+ // (seat 부재·인자 부재 = 현행 byte-parity — a per-call override effort alone
7204
7204
  // reaches synthesize through the base config on BOTH sides, so no fold).
7205
7205
  ...(args.semanticMapSynthesizeLlmConfig !== undefined
7206
7206
  ? {
@@ -594,6 +594,10 @@ export async function bootstrapInvocationBindingArtifacts(params) {
594
594
  // the A-path profile carries). Host advances read it for auditable
595
595
  // execution-result/ReviewRecord retry reporting.
596
596
  retry_policy: completeReviewRetrySettings(ontoConfig?.review?.execution?.retry),
597
+ // Durable stamp of the per-call override (design v4 §7): continuation
598
+ // re-overlays the freshly resolved project profile with it so an overridden
599
+ // session's units cannot revert to the non-overlaid project models.
600
+ ...(params.llmOverride ? { llm_override: params.llmOverride } : {}),
597
601
  interpretation_artifact_path: interpretationArtifactPath,
598
602
  binding_output_path: bindingOutputPath,
599
603
  session_metadata_path: sessionMetadataPath,