onto-mcp 0.4.11 → 0.4.13

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.
Files changed (139) hide show
  1. package/.onto/authority/core-lexicon.yaml +1 -1
  2. package/.onto/authority/supported-models.yaml +153 -0
  3. package/.onto/domains/accounting-kr/competency_qs.md +153 -153
  4. package/.onto/processes/reconstruct/obligation-coverage-ledger.yaml +842 -0
  5. package/.onto/processes/reconstruct/obligation-coverage-recorded.yaml +225 -0
  6. package/.onto/processes/reconstruct/ontology-seeding-and-maturation-design.md +93 -54
  7. package/.onto/processes/reconstruct/reconstruct-contract-registry.yaml +202 -49
  8. package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +72 -1
  9. package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +35 -0
  10. package/.onto/processes/review/nesting-batch-worker-contract.md +1 -1
  11. package/.onto/processes/review/productized-live-path.md +9 -5
  12. package/.onto/processes/review/prompt-execution-runner-contract.md +32 -8
  13. package/.onto/processes/review/review-target-profile-contract.md +47 -4
  14. package/.onto/processes/shared/pipeline-execution-ledger-contract.md +11 -0
  15. package/.onto/processes/shared/target-material-kind-contract.md +1 -1
  16. package/README.md +64 -1
  17. package/dist/cli.js +11 -1
  18. package/dist/core-api/reconstruct-api.js +679 -18
  19. package/dist/core-api/review-api.js +70 -5
  20. package/dist/core-api/review-progress.js +7 -0
  21. package/dist/core-api/runtime-observation.js +7 -0
  22. package/dist/core-runtime/artifact-io.js +131 -0
  23. package/dist/core-runtime/cli/claude-code-review-unit-executor.js +21 -9
  24. package/dist/core-runtime/cli/claude-nesting-batch-worker.js +4 -2
  25. package/dist/core-runtime/cli/materialize-review-prompt-packets.js +68 -14
  26. package/dist/core-runtime/cli/prepare-review-session.js +34 -1
  27. package/dist/core-runtime/cli/render-review-final-output.js +2 -1
  28. package/dist/core-runtime/cli/run-review-prompt-execution.js +758 -48
  29. package/dist/core-runtime/cli/run-seats.js +18 -0
  30. package/dist/core-runtime/cli/runtime-submit-context.js +10 -2
  31. package/dist/core-runtime/cli/unit-resubmit.js +269 -0
  32. package/dist/core-runtime/cli/worker-structured-output.js +80 -2
  33. package/dist/core-runtime/discovery/host-detection.js +71 -22
  34. package/dist/core-runtime/discovery/review-cert-assemble.js +208 -0
  35. package/dist/core-runtime/discovery/review-cert-record.js +442 -0
  36. package/dist/core-runtime/discovery/review-execution-units.js +22 -0
  37. package/dist/core-runtime/discovery/seat-inventory.js +167 -0
  38. package/dist/core-runtime/discovery/settings-chain.js +311 -52
  39. package/dist/core-runtime/discovery/supported-models.js +289 -25
  40. package/dist/core-runtime/discovery/synthesize-cert-assemble.js +197 -0
  41. package/dist/core-runtime/discovery/synthesize-cert-capsule.js +399 -0
  42. package/dist/core-runtime/discovery/synthesize-cert-judge.js +340 -0
  43. package/dist/core-runtime/discovery/synthesize-cert-loop.js +234 -0
  44. package/dist/core-runtime/discovery/synthesize-cert-mutation.js +117 -0
  45. package/dist/core-runtime/discovery/synthesize-cert-packet.js +182 -0
  46. package/dist/core-runtime/discovery/synthesize-cert-record.js +831 -0
  47. package/dist/core-runtime/discovery/synthesize-cert-sampler.js +336 -0
  48. package/dist/core-runtime/effort-calibration-ingest.js +326 -0
  49. package/dist/core-runtime/effort-calibration-reconstruct.js +47 -0
  50. package/dist/core-runtime/effort-calibration-review.js +22 -0
  51. package/dist/core-runtime/effort-calibration-sweep.js +110 -0
  52. package/dist/core-runtime/effort-frontier.js +134 -0
  53. package/dist/core-runtime/llm/claude-bin.js +77 -0
  54. package/dist/core-runtime/llm/dispatch-breaker.js +412 -0
  55. package/dist/core-runtime/llm/dispatch-fallback-adapter-capabilities.js +33 -0
  56. package/dist/core-runtime/llm/llm-caller.js +316 -35
  57. package/dist/core-runtime/llm/mock-llm-realization.js +35 -4
  58. package/dist/core-runtime/llm/model-switcher.js +1 -1
  59. package/dist/core-runtime/llm/openai-responses-incomplete-error.js +46 -0
  60. package/dist/core-runtime/llm/sealed-dispatch-capability.js +359 -0
  61. package/dist/core-runtime/llm/structured-dispatch-error.js +15 -0
  62. package/dist/core-runtime/onboard/claude-profile-scan.js +65 -0
  63. package/dist/core-runtime/onboard/cli-host.js +5 -2
  64. package/dist/core-runtime/onboard/configure-provider.js +33 -1
  65. package/dist/core-runtime/onboard/host-target.js +28 -3
  66. package/dist/core-runtime/onboard/register.js +54 -12
  67. package/dist/core-runtime/pipeline-execution-ledger.js +10 -0
  68. package/dist/core-runtime/reconstruct/artifact-types.js +37 -0
  69. package/dist/core-runtime/reconstruct/benchmark-evidence.js +16 -0
  70. package/dist/core-runtime/reconstruct/claim-projection-validation.js +19 -5
  71. package/dist/core-runtime/reconstruct/competency-projection-contract.js +63 -0
  72. package/dist/core-runtime/reconstruct/comprehension-artifact.js +321 -0
  73. package/dist/core-runtime/reconstruct/comprehension-reduce.js +367 -0
  74. package/dist/core-runtime/reconstruct/comprehension-semantic-map.js +771 -0
  75. package/dist/core-runtime/reconstruct/contract-registry.js +20 -0
  76. package/dist/core-runtime/reconstruct/directive-validation.js +4 -3
  77. package/dist/core-runtime/reconstruct/dispatch-fallback-artifacts.js +640 -0
  78. package/dist/core-runtime/reconstruct/execution-telemetry.js +84 -9
  79. package/dist/core-runtime/reconstruct/final-output-sections.js +89 -0
  80. package/dist/core-runtime/reconstruct/governing-snapshot.js +36 -1
  81. package/dist/core-runtime/reconstruct/leaf-reader.js +305 -0
  82. package/dist/core-runtime/reconstruct/llm-dispatch-failure.js +270 -0
  83. package/dist/core-runtime/reconstruct/llm-touch-fingerprint.js +94 -0
  84. package/dist/core-runtime/reconstruct/material-admission-validation.js +20 -5
  85. package/dist/core-runtime/reconstruct/material-profile-validation.js +24 -6
  86. package/dist/core-runtime/reconstruct/materialize-preparation.js +380 -23
  87. package/dist/core-runtime/reconstruct/maturation-validation.js +1522 -67
  88. package/dist/core-runtime/reconstruct/mock-llm-realization.js +120 -1
  89. package/dist/core-runtime/reconstruct/obligation-assertion.js +18 -0
  90. package/dist/core-runtime/reconstruct/ontology-seed-validation.js +39 -7
  91. package/dist/core-runtime/reconstruct/output-budget.js +6 -0
  92. package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +112 -5
  93. package/dist/core-runtime/reconstruct/post-seed-validation.js +122 -11
  94. package/dist/core-runtime/reconstruct/proof-authority-validation.js +2 -6
  95. package/dist/core-runtime/reconstruct/purpose-authority-validation.js +119 -7
  96. package/dist/core-runtime/reconstruct/record.js +49 -3
  97. package/dist/core-runtime/reconstruct/registry-verification-validation.js +41 -5
  98. package/dist/core-runtime/reconstruct/run-control-validation.js +1019 -37
  99. package/dist/core-runtime/reconstruct/run.js +5409 -517
  100. package/dist/core-runtime/reconstruct/seed-authoring-readiness-validation.js +56 -8
  101. package/dist/core-runtime/reconstruct/semantic-quality-gate.js +112 -7
  102. package/dist/core-runtime/reconstruct/source-observation-delta-validation.js +53 -5
  103. package/dist/core-runtime/reconstruct/source-observations.js +166 -0
  104. package/dist/core-runtime/reconstruct/source-safety-validation.js +77 -188
  105. package/dist/core-runtime/reconstruct/source-scout-pack-validation.js +50 -6
  106. package/dist/core-runtime/reconstruct/terminal-validation.js +145 -13
  107. package/dist/core-runtime/review/continuation-plan.js +12 -5
  108. package/dist/core-runtime/review/issue-artifact-runtime.js +4 -1
  109. package/dist/core-runtime/review/materializers.js +100 -11
  110. package/dist/core-runtime/review/nesting-batch.js +6 -2
  111. package/dist/core-runtime/review/obligation-shardability.js +138 -0
  112. package/dist/core-runtime/review/pipeline-execution-ledger.js +42 -2
  113. package/dist/core-runtime/review/review-artifact-utils.js +219 -7
  114. package/dist/core-runtime/review/review-execution-steps.js +32 -8
  115. package/dist/core-runtime/review/review-progress-contract.js +8 -0
  116. package/dist/core-runtime/review/review-prompt-budget.js +97 -0
  117. package/dist/core-runtime/review/semantic-quality-gate.js +33 -7
  118. package/dist/core-runtime/review/spreadsheet-review-disposition.js +94 -0
  119. package/dist/core-runtime/route-identity.js +182 -0
  120. package/dist/core-runtime/spreadsheet-header-escalation.js +204 -0
  121. package/dist/core-runtime/spreadsheet-structure-observer.js +2689 -0
  122. package/dist/core-runtime/target-material-kind.js +58 -0
  123. package/dist/mcp/server.js +27 -5
  124. package/dist/mcp/tool-schemas.js +8 -0
  125. package/dist/tui/app.js +167 -0
  126. package/dist/tui/data/event-follower.js +116 -0
  127. package/dist/tui/data/node-detail.js +42 -0
  128. package/dist/tui/data/projection-poll.js +26 -0
  129. package/dist/tui/data/session-discovery.js +65 -0
  130. package/dist/tui/index.js +108 -0
  131. package/dist/tui/screens/log.js +25 -0
  132. package/dist/tui/screens/node-detail.js +17 -0
  133. package/dist/tui/screens/session-selector.js +26 -0
  134. package/dist/tui/screens/workflow-tree.js +90 -0
  135. package/dist/tui/view-model/reconstruct-adapter.js +162 -0
  136. package/dist/tui/view-model/review-adapter.js +270 -0
  137. package/dist/tui/view-model/tree-view-model.js +19 -0
  138. package/package.json +20 -3
  139. package/settings.example.json +77 -16
@@ -3,24 +3,206 @@ import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { parse as parseYaml } from "yaml";
5
5
  import { RECONSTRUCT_STAGE_IDS, } from "../core-runtime/reconstruct/artifact-types.js";
6
- import { materializeReconstructPreparationArtifacts, } from "../core-runtime/reconstruct/materialize-preparation.js";
6
+ import { deriveDocumentExcerptProjectionBudget, DOCUMENT_EXCERPT_PROJECTION_FLOOR, materializeReconstructPreparationArtifacts, } from "../core-runtime/reconstruct/materialize-preparation.js";
7
7
  import { writeTargetMaterialProfileValidationArtifact, } from "../core-runtime/reconstruct/material-profile-validation.js";
8
- import { assembleReconstructRecord, } from "../core-runtime/reconstruct/record.js";
8
+ import { assembleReconstructRecord, reconstructTerminalStatus, } from "../core-runtime/reconstruct/record.js";
9
+ export { reconstructTerminalStatus, } from "../core-runtime/reconstruct/record.js";
9
10
  import { createDirectCallReconstructConfirmationProvider, createDirectCallReconstructDirectiveAuthor, runReconstruct, } from "../core-runtime/reconstruct/run.js";
11
+ import { RECONSTRUCT_SEMANTIC_AUTHOR_MAX_BASE_OUTPUT_TOKENS, } from "../core-runtime/reconstruct/output-budget.js";
10
12
  // Explicit reconstruct mock realization switch point (INV-MOCK-1 boundary;
11
13
  // allowlisted in scripts/check-import-boundary.ts). Active only when
12
14
  // ONTO_LLM_MOCK=1; mock runs record mock actor ids in the run manifest.
13
15
  import { RECONSTRUCT_MOCK_AUTHOR_ID, RECONSTRUCT_MOCK_CONFIRMATION_PROVIDER_ID, callReconstructMockLlm, isReconstructMockLlmRealizationEnabled, } from "../core-runtime/reconstruct/mock-llm-realization.js";
14
- import { assertSettingsModelsSupported, resolveSettingsChain, resolveReconstructActorLlmSettings, } from "../core-runtime/discovery/settings-chain.js";
16
+ import { assertSettingsModelsSupported, completeDispatchBreakerSettings, isReconstructSemanticMapAuthoringEnabled, resolveOptionalReconstructActorLlmSettings, resolveSettingsChain, resolveReconstructActorLlmSettings, resolveReconstructSemanticAuthorLlmRuntimeSettings, } from "../core-runtime/discovery/settings-chain.js";
17
+ import { assertDispatchFallbackSessionAdmission } from "../core-runtime/reconstruct/dispatch-fallback-artifacts.js";
18
+ import { normalizeLlmModelSwitcher, } from "../core-runtime/llm/model-switcher.js";
15
19
  import { resolveOntoHome, } from "../core-runtime/discovery/onto-home.js";
16
- import { resolveLlmProviderConfig, } from "../core-runtime/llm/llm-caller.js";
20
+ import { isSupportedModelRoute, loadSupportedModelRegistry, supportedModelMaxOutputTokens, } from "../core-runtime/discovery/supported-models.js";
21
+ import { applyOpenAIResponsesOutputHeadroom, resolveLlmProviderConfig, } from "../core-runtime/llm/llm-caller.js";
22
+ import { createSealedDispatchCapability, SemanticMapDispatchAccounting, } from "../core-runtime/llm/sealed-dispatch-capability.js";
23
+ import { createReconstructExecutionTelemetryCollector } from "../core-runtime/reconstruct/execution-telemetry.js";
17
24
  import { writeOntologySeedValidationArtifact, writeCandidateDispositionValidationArtifact, } from "../core-runtime/reconstruct/ontology-seed-validation.js";
18
25
  import { writeSourceObservationDirectiveValidationArtifact, } from "../core-runtime/reconstruct/directive-validation.js";
19
26
  import { loadReconstructSourceProfiles, } from "../core-runtime/reconstruct/source-profiles.js";
20
27
  import { assertReconstructDomainId, } from "../core-runtime/reconstruct/domain-id.js";
21
- import { buildReconstructPipelineExecutionLedger, } from "../core-runtime/reconstruct/pipeline-execution-ledger.js";
28
+ import { buildReconstructPipelineExecutionLedger, reconstructStageIdForArtifactRef, reconstructStageOwner, } from "../core-runtime/reconstruct/pipeline-execution-ledger.js";
29
+ import { reconcileReconstructLlmDispatchFailures, } from "../core-runtime/reconstruct/run-control-validation.js";
30
+ import { isReconstructLlmDispatchFailureRef, projectReconstructLlmDispatchFailureSummary, readReconstructLlmDispatchFailureArtifactWithHash, readReconstructLlmDispatchFailureError, } from "../core-runtime/reconstruct/llm-dispatch-failure.js";
22
31
  import { spawnRuntimeWatcherPane, } from "../core-runtime/cli/spawn-watcher.js";
32
+ import { assertPathInsideRoot } from "../core-runtime/path-boundary.js";
23
33
  import { appendRuntimeStatusEventSync, runWithRuntimeObservationContext, } from "../core-runtime/observability/runtime-stream-observation.js";
34
+ function failedReconstructProgress(failure, completedStageRefs) {
35
+ return {
36
+ executionProfile: null,
37
+ currentStageId: failure.unit_id,
38
+ stageCount: RECONSTRUCT_STAGE_IDS.length,
39
+ liveness: {
40
+ state: "halted_or_partial",
41
+ recommendedPollIntervalMs: null,
42
+ },
43
+ countSummary: {
44
+ sourceObservationCount: null,
45
+ selectedObservationCount: null,
46
+ semanticClaimCount: null,
47
+ confirmedClaimCount: null,
48
+ partialClaimCount: null,
49
+ deferredClaimCount: null,
50
+ competencyQuestionCount: null,
51
+ assessmentCount: null,
52
+ failureCount: null,
53
+ revisionProposalCount: null,
54
+ unresolvedCount: null,
55
+ passRate: null,
56
+ },
57
+ answerabilitySummary: null,
58
+ stages: RECONSTRUCT_STAGE_IDS.map((stageId) => ({
59
+ stageId,
60
+ state: stageId === failure.unit_id
61
+ ? "halted"
62
+ : completedStageRefs.has(stageId)
63
+ ? "completed"
64
+ : "pending",
65
+ owner: stageId === failure.unit_id
66
+ ? reconstructStageOwner(stageId)
67
+ : null,
68
+ artifactRefs: stageId === failure.unit_id
69
+ ? [failure.failure_artifact_ref]
70
+ : [...(completedStageRefs.get(stageId) ?? [])],
71
+ reason: stageId === failure.unit_id ? failure.failure_code : null,
72
+ authorityImpact: stageId === failure.unit_id
73
+ ? "The provider output ceiling stopped the owning actor before a canonical stage artifact was accepted."
74
+ : null,
75
+ })),
76
+ };
77
+ }
78
+ async function isRegularRefInsideSession(sessionRoot, artifactRef) {
79
+ const relative = path.relative(sessionRoot, path.resolve(artifactRef));
80
+ if (relative.startsWith("..") || path.isAbsolute(relative))
81
+ return false;
82
+ try {
83
+ const stat = await fs.lstat(artifactRef);
84
+ if (!stat.isFile() || stat.isSymbolicLink())
85
+ return false;
86
+ const [realSessionRoot, realArtifactRef] = await Promise.all([
87
+ fs.realpath(sessionRoot),
88
+ fs.realpath(artifactRef),
89
+ ]);
90
+ const realRelative = path.relative(realSessionRoot, realArtifactRef);
91
+ return !realRelative.startsWith("..") && !path.isAbsolute(realRelative);
92
+ }
93
+ catch {
94
+ return false;
95
+ }
96
+ }
97
+ async function collectFailedStatusArtifactEvidence(args) {
98
+ const reusable = new Set();
99
+ const completedStageRefs = new Map();
100
+ const addStageRef = (stageId, ref) => {
101
+ const refs = completedStageRefs.get(stageId) ?? [];
102
+ if (!refs.includes(ref))
103
+ refs.push(ref);
104
+ completedStageRefs.set(stageId, refs);
105
+ };
106
+ for (const transaction of args.runControl.write_transactions) {
107
+ if (transaction.owner_attempt_id !== args.ownerAttemptId ||
108
+ transaction.transaction_status !== "committed" ||
109
+ !transaction.committed_hash ||
110
+ isReconstructLlmDispatchFailureRef(args.sessionRoot, transaction.artifact_ref) ||
111
+ !(await isRegularRefInsideSession(args.sessionRoot, transaction.artifact_ref))) {
112
+ continue;
113
+ }
114
+ const bytes = await fs.readFile(transaction.artifact_ref);
115
+ const observedHash = crypto.createHash("sha256").update(bytes).digest("hex");
116
+ if (observedHash !== transaction.committed_hash)
117
+ continue;
118
+ reusable.add(transaction.artifact_ref);
119
+ const stageId = reconstructStageIdForArtifactRef(transaction.artifact_ref);
120
+ if (stageId)
121
+ addStageRef(stageId, transaction.artifact_ref);
122
+ }
123
+ return {
124
+ reusableArtifactRefs: [...reusable].sort(),
125
+ completedStageRefs,
126
+ };
127
+ }
128
+ async function trustedFailedSessionStatus(args) {
129
+ if (!args.reconciliation)
130
+ return null;
131
+ const runControl = args.reconciliation.runControl;
132
+ const validation = args.reconciliation.validation ??
133
+ await readYamlArtifactIfPresent(args.runControlValidationRef);
134
+ const latestAttempt = runControl.attempt_rows.at(-1) ?? null;
135
+ if (latestAttempt?.attempt_status !== "failed")
136
+ return null;
137
+ const failureTransactions = runControl.write_transactions.filter((row) => row.owner_attempt_id === latestAttempt.attempt_id &&
138
+ row.transaction_status === "committed" &&
139
+ isReconstructLlmDispatchFailureRef(args.sessionRoot, row.artifact_ref));
140
+ const failureTransaction = failureTransactions.length === 1
141
+ ? failureTransactions[0]
142
+ : null;
143
+ if (!validation ||
144
+ validation.validation_status !== "valid" ||
145
+ validation.current_attempt_id !== latestAttempt.attempt_id ||
146
+ !failureTransaction) {
147
+ throw new Error(`reconstruct session has no trusted failed terminal: ${args.sessionRoot}`);
148
+ }
149
+ const failureRead = await readReconstructLlmDispatchFailureArtifactWithHash({
150
+ sessionRoot: args.sessionRoot,
151
+ artifactRef: failureTransaction.artifact_ref,
152
+ });
153
+ if (failureTransaction.prepared_content_hash !==
154
+ failureTransaction.committed_hash ||
155
+ failureRead.sha256 !== failureTransaction.committed_hash ||
156
+ failureRead.artifact.session_id !== runControl.session_id ||
157
+ failureRead.artifact.owner_attempt_id !== latestAttempt.attempt_id) {
158
+ throw new Error(`trusted failed terminal changed after validation: ${failureTransaction.artifact_ref}`);
159
+ }
160
+ const failure = projectReconstructLlmDispatchFailureSummary(failureRead.artifact, failureTransaction.artifact_ref);
161
+ const artifactEvidence = await collectFailedStatusArtifactEvidence({
162
+ sessionRoot: args.sessionRoot,
163
+ runControl,
164
+ ownerAttemptId: latestAttempt.attempt_id,
165
+ });
166
+ artifactEvidence.completedStageRefs.set("run_control", [args.runControlRef]);
167
+ artifactEvidence.completedStageRefs.set("run_control_validation", [
168
+ args.runControlValidationRef,
169
+ ]);
170
+ return {
171
+ sessionId: runControl.session_id,
172
+ sessionRoot: args.sessionRoot,
173
+ status: "failed",
174
+ artifactRefs: {
175
+ reconstruct_run_control: args.runControlRef,
176
+ reconstruct_run_control_validation: args.runControlValidationRef,
177
+ },
178
+ claimProjection: null,
179
+ claimProjectionValidation: null,
180
+ progress: failedReconstructProgress(failure, artifactEvidence.completedStageRefs),
181
+ reconstructRecord: null,
182
+ runControlRef: args.runControlRef,
183
+ runControlValidationRef: args.runControlValidationRef,
184
+ failure,
185
+ reusableArtifactRefs: artifactEvidence.reusableArtifactRefs,
186
+ };
187
+ }
188
+ export async function recoverReconstructFailedRunStatus(args) {
189
+ const typedFailure = readReconstructLlmDispatchFailureError(args.error);
190
+ if (!typedFailure)
191
+ return null;
192
+ const runControlRef = path.join(args.sessionRoot, "reconstruct-run-control.yaml");
193
+ const runControlValidationRef = path.join(args.sessionRoot, "reconstruct-run-control-validation.yaml");
194
+ const reconciliation = await reconcileReconstructLlmDispatchFailures({
195
+ sessionRoot: args.sessionRoot,
196
+ runControlPath: runControlRef,
197
+ validationOutputPath: runControlValidationRef,
198
+ });
199
+ return trustedFailedSessionStatus({
200
+ sessionRoot: args.sessionRoot,
201
+ reconciliation,
202
+ runControlRef,
203
+ runControlValidationRef,
204
+ });
205
+ }
24
206
  async function directoryExists(directoryPath) {
25
207
  try {
26
208
  const stat = await fs.stat(directoryPath);
@@ -35,6 +217,128 @@ function resolveFromBase(basePath, maybeRelativePath) {
35
217
  ? path.resolve(maybeRelativePath)
36
218
  : path.resolve(basePath, maybeRelativePath);
37
219
  }
220
+ /**
221
+ * Pure adopt-vs-degrade decision for the opt-in answer-support judge config.
222
+ * The judge keeps the semantic author's config except for the requested
223
+ * overrides. A judgeModelCandidate (already resolved on the author's provider,
224
+ * so its credentials/adapter match the author) is adopted only when it is a
225
+ * benchmark-verified route (INV-MODEL-1) AND keeps the author's provider; any
226
+ * other case degrades to the author model with a recorded note. Effort always
227
+ * INHERITS the author's effective effort (e.g. a pinned `--effort`) unless
228
+ * judgeLlmEffort explicitly overrides it — never the model candidate's raw
229
+ * settings effort, which could otherwise silently run the judge weaker than the
230
+ * author. Returns `undefined` config when nothing was requested (caller inherits
231
+ * the author config — zero change).
232
+ */
233
+ export function resolveJudgeLlmConfig(args) {
234
+ if (!args.judgeLlmEffort && !args.judgeModelCandidate) {
235
+ return { judgeLlmConfig: undefined, note: null };
236
+ }
237
+ const judge = { ...args.authorLlmConfig };
238
+ const authorEffort = args.authorLlmConfig.reasoning_effort;
239
+ let note = null;
240
+ const candidate = args.judgeModelCandidate;
241
+ if (candidate) {
242
+ // INV-MODEL-1 is keyed by MODEL provider (e.g. openai/gpt-5.5), not the
243
+ // runtime adapter provider (OpenAI OAuth normalizes to codex). Check the
244
+ // model provider so a supported judge model is not spuriously degraded.
245
+ const supported = isSupportedModelRoute(args.judgeModelProvider, candidate.model_id, args.registry,
246
+ // Judge adoption is a NAMED non-settings dispatch (design §2.3): a
247
+ // role-restricted entry (e.g. synthesize-only) must not be adoptable
248
+ // as the answer-support judge.
249
+ { kind: "request_judge" });
250
+ // Credential safety: the candidate resolves on the author's provider, so its
251
+ // runtime provider must match the author's (guarantees api_key_env/adapter
252
+ // never cross providers). Uses the runtime provider, not the model provider.
253
+ const sameProvider = candidate.provider === args.authorLlmConfig.provider;
254
+ if (supported && sameProvider) {
255
+ Object.assign(judge, candidate);
256
+ }
257
+ else {
258
+ note = `answer-support judge model override (${args.judgeModelProvider ?? "(unresolved provider)"}/${candidate.model_id ?? "(unresolved model)"}) ${supported
259
+ ? "requires a different provider than the semantic author"
260
+ : "is not a benchmark-verified route"}; degraded to the semantic-author model`;
261
+ }
262
+ }
263
+ // Effort = explicit judge override, else the author's effective effort. This
264
+ // wins over any reasoning_effort Object.assign copied from the model candidate
265
+ // (the candidate is resolved without the author's effort pin, so its raw
266
+ // settings effort can diverge from the author's pinned effort).
267
+ if (args.judgeLlmEffort)
268
+ judge.reasoning_effort = args.judgeLlmEffort;
269
+ else if (authorEffort !== undefined)
270
+ judge.reasoning_effort = authorEffort;
271
+ else
272
+ delete judge.reasoning_effort;
273
+ const judgeLlmConfig = args.outputHeadroom
274
+ ? applyOpenAIResponsesOutputHeadroom({
275
+ config: judge,
276
+ selection: args.outputHeadroom.selection,
277
+ headroomTokens: args.outputHeadroom.headroomTokens,
278
+ maxBaseOutputTokens: RECONSTRUCT_SEMANTIC_AUTHOR_MAX_BASE_OUTPUT_TOKENS,
279
+ modelMaxOutputTokens: supportedModelMaxOutputTokens(args.registry, args.judgeModelProvider ?? "", judge.model_id ?? ""),
280
+ })
281
+ : judge;
282
+ return { judgeLlmConfig, note };
283
+ }
284
+ /**
285
+ * Mock-safe IDENTITY projection of the optional synthesize seat (design §5.4):
286
+ * carries only the dispatch-identity axes the reuse fold and census read
287
+ * (provider/model/effort/base_url) — deliberately NOT resolveLlmProviderConfig,
288
+ * which validates provider/auth combinations and resolves credentials that a
289
+ * mock run must not require. The identity is deterministic per seat edit, so
290
+ * P3's fold-rotation assertions hold under mock without any live material.
291
+ * (No execution_adapter under mock — the canonical fold serializes it as
292
+ * "default"; live runs fold the real adapter.)
293
+ */
294
+ export function synthesizeSeatIdentityProjection(selection) {
295
+ return {
296
+ ...(selection.provider !== undefined
297
+ ? { provider: selection.provider }
298
+ : {}),
299
+ ...(selection.model !== undefined ? { model_id: selection.model } : {}),
300
+ ...(selection.effort !== undefined
301
+ ? { reasoning_effort: selection.effort }
302
+ : {}),
303
+ ...(selection.base_url !== undefined
304
+ ? { base_url: selection.base_url }
305
+ : {}),
306
+ };
307
+ }
308
+ /**
309
+ * The ONE deterministic seam from post-chain settings to the factory's
310
+ * semantic-map wiring (design §5.4/§5.5): opt-in read, single seat read
311
+ * (resolveOptionalReconstructActorLlmSettings), live provider completion vs
312
+ * mock identity projection, and the dormant-seat honesty note. Coverage is
313
+ * two-layered: unit tests exercise this function over resolved settings, and
314
+ * the seam→factory CONSUMPTION edge is bound by the mock E2Es — the dormant
315
+ * branch via the N11 note, the active branch via the P3 census
316
+ * synthesize_model_identity assertion (dropping either factory spread at the
317
+ * runReconstruct call site fails P3).
318
+ */
319
+ export function resolveSemanticMapSynthesizeWiring(args) {
320
+ const enabled = isReconstructSemanticMapAuthoringEnabled(args.settings);
321
+ const seat = resolveOptionalReconstructActorLlmSettings(args.settings, "semantic_map_synthesize");
322
+ if (seat === undefined)
323
+ return { enableSemanticMapAuthoring: enabled };
324
+ if (!enabled) {
325
+ return {
326
+ enableSemanticMapAuthoring: false,
327
+ dormantSeatNote: "semantic_map_synthesize seat is configured but reconstruct.execution.semantic_map_authoring is off — the seat is dormant (no synthesize dispatch, not gate-validated) until the opt-in is enabled",
328
+ };
329
+ }
330
+ return {
331
+ enableSemanticMapAuthoring: true,
332
+ semanticMapSynthesizeLlmConfig: args.mockRealizationEnabled
333
+ ? synthesizeSeatIdentityProjection(seat)
334
+ : resolveLlmProviderConfig({
335
+ config: { llm: seat },
336
+ ...(args.llmEffortOverride
337
+ ? { cliOverrides: args.llmEffortOverride }
338
+ : {}),
339
+ }),
340
+ };
341
+ }
38
342
  function dateStamp() {
39
343
  return new Date().toISOString().slice(0, 10).replace(/-/g, "");
40
344
  }
@@ -118,15 +422,20 @@ function deriveReconstructProgress(args) {
118
422
  });
119
423
  const lastReachedStage = [...stages].reverse().find((stage) => stage.state !== "pending") ??
120
424
  stages[0];
425
+ // Terminality (completed / graceful-blocked / graceful-limited) is judged once, via the single
426
+ // projection (design §16.7). A graceful terminal is terminal for polling (stop) but not
427
+ // "completed" — its liveness state stays "halted_or_partial" while the poll interval goes null.
428
+ const terminalStatus = reconstructTerminalStatus(args.record);
429
+ const isCompleted = terminalStatus === "completed";
430
+ const isGracefulTerminal = terminalStatus === "blocked" || terminalStatus === "limited";
431
+ const isTerminal = isCompleted || isGracefulTerminal;
121
432
  return {
122
433
  executionProfile: args.runManifest?.execution_profile ?? null,
123
434
  currentStageId: lastReachedStage.stageId,
124
435
  stageCount: RECONSTRUCT_STAGE_IDS.length,
125
436
  liveness: {
126
- state: args.record.record_stage === "completed"
127
- ? "completed"
128
- : "halted_or_partial",
129
- recommendedPollIntervalMs: args.record.record_stage === "completed" ? null : 1000,
437
+ state: isCompleted ? "completed" : "halted_or_partial",
438
+ recommendedPollIntervalMs: isTerminal ? null : 1000,
130
439
  },
131
440
  countSummary: {
132
441
  sourceObservationCount: args.metrics?.source_observation_count ?? null,
@@ -171,6 +480,23 @@ function recordArtifactRefsFromPreparation(refs) {
171
480
  source_observations: refs.source_observations,
172
481
  };
173
482
  }
483
+ export async function tryCreateEligiblePrimarySealedDispatchCapability(args) {
484
+ const selection = normalizeLlmModelSwitcher(args.llm);
485
+ if (!selection ||
486
+ selection.auth !== "api_key" ||
487
+ selection.execution_route !== "direct_model_call" ||
488
+ (selection.execution_adapter !== "openai_sdk" &&
489
+ selection.execution_adapter !== "anthropic_sdk") ||
490
+ selection.base_url !== undefined) {
491
+ return undefined;
492
+ }
493
+ try {
494
+ return await createSealedDispatchCapability(args);
495
+ }
496
+ catch {
497
+ return undefined;
498
+ }
499
+ }
174
500
  export function createOntoReconstructCoreApi(options = {}) {
175
501
  const ontoHome = resolveOntoHome(options.ontoHome);
176
502
  return {
@@ -187,6 +513,11 @@ export function createOntoReconstructCoreApi(options = {}) {
187
513
  const sessionRoot = request.sessionRoot
188
514
  ? resolveFromBase(projectRoot, request.sessionRoot)
189
515
  : createDefaultSessionRoot(projectRoot);
516
+ await assertPathInsideRoot({
517
+ root: projectRoot,
518
+ candidate: sessionRoot,
519
+ label: "reconstruct sessionRoot",
520
+ });
190
521
  const profilesRoot = await resolveProfilesRoot({
191
522
  projectRoot,
192
523
  ...(request.profilesRoot
@@ -233,6 +564,11 @@ export function createOntoReconstructCoreApi(options = {}) {
233
564
  const sessionRoot = request.sessionRoot
234
565
  ? resolveFromBase(projectRoot, request.sessionRoot)
235
566
  : createDefaultSessionRoot(projectRoot);
567
+ await assertPathInsideRoot({
568
+ root: projectRoot,
569
+ candidate: sessionRoot,
570
+ label: "reconstruct sessionRoot",
571
+ });
236
572
  const profilesRoot = await resolveProfilesRoot({
237
573
  projectRoot,
238
574
  ...(request.profilesRoot
@@ -242,6 +578,10 @@ export function createOntoReconstructCoreApi(options = {}) {
242
578
  });
243
579
  const targetRefs = request.targetRefs.map((targetRef) => resolveFromBase(projectRoot, targetRef));
244
580
  const settings = await resolveSettingsChain(ontoHome ?? projectRoot, projectRoot);
581
+ await assertDispatchFallbackSessionAdmission({
582
+ sessionRoot,
583
+ enabled: settings.reconstruct?.execution?.dispatch_fallback?.enabled === true,
584
+ });
245
585
  const semanticAuthorRealization = request.semanticAuthorRealization ?? "direct_call";
246
586
  const confirmationProviderRealization = request.confirmationProviderRealization ?? "direct_call";
247
587
  if (request.domain) {
@@ -261,14 +601,20 @@ export function createOntoReconstructCoreApi(options = {}) {
261
601
  const llmEffortOverride = request.llmEffort
262
602
  ? { reasoning_effort: request.llmEffort }
263
603
  : undefined;
264
- const semanticAuthorLlmConfig = mockRealizationEnabled
265
- ? {}
266
- : resolveLlmProviderConfig({
267
- config: {
268
- llm: resolveReconstructActorLlmSettings(settings, "semantic_author"),
269
- },
604
+ // The semantic_author actor settings (non-mock). The MODEL provider here
605
+ // (e.g. "openai") is the supported-models registry key, DISTINCT from the
606
+ // resolved RUNTIME provider (openai OAuth resolves to "codex"). The document
607
+ // projection-budget lookup keys on the model provider so the default
608
+ // gpt-5.5 OAuth seat resolves (see deriveDocumentExcerptProjectionBudget).
609
+ const semanticAuthorActorLlm = mockRealizationEnabled
610
+ ? null
611
+ : resolveReconstructActorLlmSettings(settings, "semantic_author");
612
+ let semanticAuthorLlmConfig = semanticAuthorActorLlm
613
+ ? resolveLlmProviderConfig({
614
+ config: { llm: semanticAuthorActorLlm },
270
615
  ...(llmEffortOverride ? { cliOverrides: llmEffortOverride } : {}),
271
- });
616
+ })
617
+ : {};
272
618
  const confirmationProviderLlmConfig = mockRealizationEnabled
273
619
  ? {}
274
620
  : resolveLlmProviderConfig({
@@ -277,8 +623,240 @@ export function createOntoReconstructCoreApi(options = {}) {
277
623
  },
278
624
  ...(llmEffortOverride ? { cliOverrides: llmEffortOverride } : {}),
279
625
  });
626
+ // The supported-models registry (non-mock only): loaded once and shared by
627
+ // the judge-override support check and the document projection-budget
628
+ // lookup. Mock realization makes no real provider calls, so it stays
629
+ // decoupled from the install authority file (C6).
630
+ const supportedModelRegistry = mockRealizationEnabled
631
+ ? null
632
+ : loadSupportedModelRegistry();
633
+ const semanticAuthorLlmRuntime = mockRealizationEnabled
634
+ ? undefined
635
+ : resolveReconstructSemanticAuthorLlmRuntimeSettings(settings);
636
+ if (semanticAuthorLlmRuntime && semanticAuthorActorLlm) {
637
+ const selection = normalizeLlmModelSwitcher(semanticAuthorActorLlm);
638
+ if (!selection) {
639
+ throw new Error("semantic_author output headroom requires a resolved LLM selection.");
640
+ }
641
+ semanticAuthorLlmConfig = applyOpenAIResponsesOutputHeadroom({
642
+ config: semanticAuthorLlmConfig,
643
+ selection,
644
+ headroomTokens: semanticAuthorLlmRuntime.openai_responses_output_headroom_tokens,
645
+ maxBaseOutputTokens: RECONSTRUCT_SEMANTIC_AUTHOR_MAX_BASE_OUTPUT_TOKENS,
646
+ modelMaxOutputTokens: supportedModelMaxOutputTokens(supportedModelRegistry, selection.model_provider, semanticAuthorLlmConfig.model_id ?? selection.model_id ?? ""),
647
+ });
648
+ }
649
+ // Semantic-map authoring opt-in + optional synthesize seat (INV-MODEL-1
650
+ // role-aware design §5.4/§5.5): ONE deterministic seam owns the wiring
651
+ // (resolveSemanticMapSynthesizeWiring) — live completes the seat into a
652
+ // provider config (own auth/adapter, cross-provider; the request
653
+ // llmEffort pin applies as cliOverrides, so pin > seat effort), mock
654
+ // takes an identity-only projection (no auth material required).
655
+ const semanticMapWiring = resolveSemanticMapSynthesizeWiring({
656
+ settings,
657
+ mockRealizationEnabled,
658
+ llmEffortOverride,
659
+ });
660
+ // Single seed-stage document projection budget (chars), derived once from
661
+ // the semantic author's MODEL window.
662
+ const documentExcerptProjectionBudget = supportedModelRegistry
663
+ ? deriveDocumentExcerptProjectionBudget({
664
+ ...(semanticAuthorActorLlm?.provider
665
+ ? { provider: semanticAuthorActorLlm.provider }
666
+ : {}),
667
+ ...(semanticAuthorLlmConfig.model_id
668
+ ? { modelId: semanticAuthorLlmConfig.model_id }
669
+ : {}),
670
+ }, supportedModelRegistry)
671
+ : DOCUMENT_EXCERPT_PROJECTION_FLOOR;
672
+ const dispatchFallbackSettings = settings.reconstruct?.execution?.dispatch_fallback;
673
+ const dispatchBreakerSettings = completeDispatchBreakerSettings(settings.reconstruct?.execution?.dispatch_breaker);
674
+ if (semanticAuthorLlmRuntime &&
675
+ dispatchFallbackSettings?.enabled === true) {
676
+ throw new Error("reconstruct output headroom cannot be combined with dispatch_fallback until the sealed semantic-map route preserves the same output-budget and typed-incomplete contract.");
677
+ }
678
+ let dispatchFallbackRuntime;
679
+ let dispatchFallbackTelemetry;
680
+ if (dispatchFallbackSettings?.enabled === true) {
681
+ if (!dispatchBreakerSettings.enabled) {
682
+ throw new Error("reconstruct dispatch_fallback requires reconstruct.execution.dispatch_breaker.enabled=true.");
683
+ }
684
+ if (mockRealizationEnabled) {
685
+ throw new Error("reconstruct dispatch_fallback requires the live sealed SDK path; mock realization is not product evidence.");
686
+ }
687
+ if (!semanticMapWiring.enableSemanticMapAuthoring) {
688
+ throw new Error("reconstruct dispatch_fallback requires reconstruct.execution.semantic_map_authoring=true.");
689
+ }
690
+ const effectiveEffort = request.llmEffort;
691
+ const primarySynthesizeLlm = {
692
+ ...(resolveOptionalReconstructActorLlmSettings(settings, "semantic_map_synthesize") ?? resolveReconstructActorLlmSettings(settings, "semantic_author")),
693
+ ...(effectiveEffort ? { effort: effectiveEffort } : {}),
694
+ };
695
+ const primaryVerifyLlm = {
696
+ ...resolveReconstructActorLlmSettings(settings, "semantic_author"),
697
+ ...(effectiveEffort ? { effort: effectiveEffort } : {}),
698
+ };
699
+ const fallbackLlm = {
700
+ ...dispatchFallbackSettings.llm,
701
+ ...(effectiveEffort ? { effort: effectiveEffort } : {}),
702
+ };
703
+ const [primarySynthesize, primaryVerify, fallbackSynthesize, fallbackVerify] = await Promise.all([
704
+ tryCreateEligiblePrimarySealedDispatchCapability({
705
+ llm: primarySynthesizeLlm,
706
+ operation: "semantic_map_synthesize",
707
+ }),
708
+ tryCreateEligiblePrimarySealedDispatchCapability({
709
+ llm: primaryVerifyLlm,
710
+ operation: "semantic_map_verify",
711
+ }),
712
+ createSealedDispatchCapability({
713
+ llm: fallbackLlm,
714
+ operation: "semantic_map_synthesize",
715
+ }),
716
+ createSealedDispatchCapability({
717
+ llm: fallbackLlm,
718
+ operation: "semantic_map_verify",
719
+ }),
720
+ ]);
721
+ const eligiblePrimaryCapabilities = [
722
+ primarySynthesize,
723
+ primaryVerify,
724
+ ].filter((capability) => capability !== undefined);
725
+ if (eligiblePrimaryCapabilities.length === 0) {
726
+ throw new Error("reconstruct dispatch_fallback requires at least one eligible sealed primary semantic-map operation.");
727
+ }
728
+ const eligiblePrimaryProviders = new Set(eligiblePrimaryCapabilities.map((capability) => capability.public_descriptor.model_provider));
729
+ if ([...eligiblePrimaryProviders].every((provider) => provider === fallbackSynthesize.public_descriptor.model_provider) ||
730
+ fallbackSynthesize.public_descriptor.model_provider !==
731
+ fallbackVerify.public_descriptor.model_provider) {
732
+ throw new Error("reconstruct dispatch_fallback requires one alternate provider for the complete fallback pair.");
733
+ }
734
+ const accounting = new SemanticMapDispatchAccounting();
735
+ dispatchFallbackTelemetry =
736
+ createReconstructExecutionTelemetryCollector({
737
+ nullMixedRouteProjection: true,
738
+ });
739
+ const fallbackLlmConfig = resolveLlmProviderConfig({
740
+ config: { llm: fallbackLlm },
741
+ });
742
+ const fallbackDirectiveAuthor = createDirectCallReconstructDirectiveAuthor({
743
+ llmConfig: fallbackLlmConfig,
744
+ semanticMapSynthesizeLlmConfig: fallbackLlmConfig,
745
+ enableSemanticMapAuthoring: true,
746
+ semanticMapDispatchCapabilities: {
747
+ synthesize: fallbackSynthesize,
748
+ verify: fallbackVerify,
749
+ accounting,
750
+ executionSource: "fallback",
751
+ allowParseRepair: false,
752
+ maxTransportAttempts: 1,
753
+ },
754
+ documentExcerptProjectionBudget,
755
+ executionTelemetry: dispatchFallbackTelemetry,
756
+ });
757
+ dispatchFallbackRuntime = {
758
+ accounting,
759
+ primary: {
760
+ ...(primarySynthesize ? { synthesize: primarySynthesize } : {}),
761
+ ...(primaryVerify ? { verify: primaryVerify } : {}),
762
+ },
763
+ fallback: {
764
+ synthesize: fallbackSynthesize,
765
+ verify: fallbackVerify,
766
+ directiveAuthor: fallbackDirectiveAuthor,
767
+ },
768
+ };
769
+ }
770
+ // Opt-in per-stage JUDGE config (semantic-independence lever). Default =
771
+ // inherit the semantic-author config (judgeLlmConfig undefined → no change,
772
+ // zero regression). A judgeModel override resolves ON THE AUTHOR'S PROVIDER
773
+ // (same credentials/route), so it is adopted only when the resulting
774
+ // (author provider, judgeModel) pair is benchmark-verified, otherwise it
775
+ // degrades. resolveJudgeLlmConfig owns the adopt-vs-degrade decision.
776
+ const judgeOverrideRequested = Boolean(request.judgeLlmEffort || request.judgeModel);
777
+ let judgeConfigNote = null;
778
+ if (judgeOverrideRequested && mockRealizationEnabled) {
779
+ judgeConfigNote =
780
+ "answer-support judge override ignored under mock realization (no real provider calls)";
781
+ }
782
+ // A judgeModel candidate is resolved on the SAME actor settings as the
783
+ // author (no provider override), so api_key_env / execution_adapter /
784
+ // base_url stay the author provider's — consistent, never cross-provider.
785
+ const judgeAuthorActorLlm = !mockRealizationEnabled && request.judgeModel
786
+ ? resolveReconstructActorLlmSettings(settings, "semantic_author")
787
+ : null;
788
+ const judgeModelCandidateBase = judgeAuthorActorLlm
789
+ ? resolveLlmProviderConfig({
790
+ config: { llm: judgeAuthorActorLlm },
791
+ cliOverrides: { model: request.judgeModel },
792
+ })
793
+ : null;
794
+ const judgeResolution = mockRealizationEnabled
795
+ ? { judgeLlmConfig: undefined, note: judgeConfigNote }
796
+ : resolveJudgeLlmConfig({
797
+ authorLlmConfig: semanticAuthorLlmConfig,
798
+ ...(request.judgeLlmEffort
799
+ ? { judgeLlmEffort: request.judgeLlmEffort }
800
+ : {}),
801
+ judgeModelCandidate: judgeModelCandidateBase,
802
+ // Registry key is the MODEL provider (e.g. openai), not the runtime
803
+ // adapter (openai OAuth → codex). The judge uses the author's provider.
804
+ ...(judgeAuthorActorLlm?.provider
805
+ ? { judgeModelProvider: judgeAuthorActorLlm.provider }
806
+ : {}),
807
+ ...(judgeAuthorActorLlm && semanticAuthorLlmRuntime
808
+ ? {
809
+ outputHeadroom: {
810
+ selection: normalizeLlmModelSwitcher(judgeAuthorActorLlm),
811
+ headroomTokens: semanticAuthorLlmRuntime
812
+ .openai_responses_output_headroom_tokens,
813
+ },
814
+ }
815
+ : {}),
816
+ // Non-null in this branch: both this and supportedModelRegistry gate on
817
+ // the same mockRealizationEnabled check.
818
+ registry: supportedModelRegistry,
819
+ });
820
+ const judgeLlmConfig = judgeResolution.judgeLlmConfig;
821
+ if (!mockRealizationEnabled)
822
+ judgeConfigNote = judgeResolution.note;
280
823
  const directiveAuthor = createDirectCallReconstructDirectiveAuthor({
281
824
  llmConfig: semanticAuthorLlmConfig,
825
+ ...(judgeLlmConfig ? { judgeLlmConfig } : {}),
826
+ // Production opt-in + per-role synthesize override (design §5.5/§5.2)
827
+ // from the single wiring seam. Opt-in absent/false = pair not
828
+ // attached, stage skips — byte-parity with today. A dormant seat
829
+ // (configured, opt-in off) is surfaced via the honest note below.
830
+ ...(semanticMapWiring.enableSemanticMapAuthoring
831
+ ? { enableSemanticMapAuthoring: true }
832
+ : {}),
833
+ ...(semanticMapWiring.semanticMapSynthesizeLlmConfig
834
+ ? {
835
+ semanticMapSynthesizeLlmConfig: semanticMapWiring.semanticMapSynthesizeLlmConfig,
836
+ }
837
+ : {}),
838
+ documentExcerptProjectionBudget,
839
+ ...(dispatchFallbackRuntime
840
+ ? {
841
+ semanticMapDispatchCapabilities: {
842
+ ...(dispatchFallbackRuntime.primary.synthesize
843
+ ? {
844
+ synthesize: dispatchFallbackRuntime.primary.synthesize,
845
+ }
846
+ : {}),
847
+ ...(dispatchFallbackRuntime.primary.verify
848
+ ? { verify: dispatchFallbackRuntime.primary.verify }
849
+ : {}),
850
+ accounting: dispatchFallbackRuntime.accounting,
851
+ executionSource: "primary",
852
+ allowParseRepair: true,
853
+ maxTransportAttempts: 3,
854
+ },
855
+ }
856
+ : {}),
857
+ ...(dispatchFallbackTelemetry
858
+ ? { executionTelemetry: dispatchFallbackTelemetry }
859
+ : {}),
282
860
  ...(mockRealizationEnabled
283
861
  ? {
284
862
  llmCall: callReconstructMockLlm,
@@ -304,6 +882,37 @@ export function createOntoReconstructCoreApi(options = {}) {
304
882
  : "reconstruct session starting",
305
883
  stageId: "start",
306
884
  });
885
+ if (judgeConfigNote) {
886
+ // Honest accounting: the operator opted into a judge override that was
887
+ // not used (unsupported model degraded to the author model, or ignored
888
+ // under mock), so the rubber-stamp mitigation did NOT take effect. The
889
+ // judge's actual model/effort is independently recorded in the judge
890
+ // step execution telemetry. Emitted BEFORE the run so it survives a
891
+ // run failure — the degrade decision is independent of the run outcome.
892
+ appendRuntimeStatusEventSync({
893
+ pipeline: "reconstruct",
894
+ sessionRoot,
895
+ sourceLabel: "onto_reconstruct",
896
+ message: judgeConfigNote,
897
+ stageId: "answer_support_judgment",
898
+ });
899
+ }
900
+ if (semanticMapWiring.dormantSeatNote) {
901
+ // Honest accounting (design §5.4, N11 / judge-note precedent): the
902
+ // operator configured a synthesize seat but the semantic-map authoring
903
+ // opt-in is off, so the seat is DORMANT — no synthesize dispatch will
904
+ // use it and the gate deliberately excludes it (U6). Emitted BEFORE the
905
+ // run so the inert config is never a silent no-op. This emission binds
906
+ // the seam's DORMANT branch to the live path (N11); the ACTIVE branch
907
+ // is bound by the P3 census-identity assertion on the factory spreads.
908
+ appendRuntimeStatusEventSync({
909
+ pipeline: "reconstruct",
910
+ sessionRoot,
911
+ sourceLabel: "onto_reconstruct",
912
+ message: semanticMapWiring.dormantSeatNote,
913
+ stageId: "start",
914
+ });
915
+ }
307
916
  const watcherResult = spawnRuntimeWatcherPane(projectRoot, sessionRoot, ontoHome);
308
917
  appendRuntimeStatusEventSync({
309
918
  pipeline: "reconstruct",
@@ -334,6 +943,17 @@ export function createOntoReconstructCoreApi(options = {}) {
334
943
  confirmationProviderRealization,
335
944
  directiveAuthor,
336
945
  confirmationProvider,
946
+ // 설계 B: settings가 유일 권위(INV-CFG-1) — 기본 OFF, 완성값은
947
+ // settings chain이 채운다.
948
+ dispatchBreaker: dispatchBreakerSettings,
949
+ ...(settings.reconstruct?.execution?.dispatch_fallback
950
+ ? {
951
+ dispatchFallback: settings.reconstruct.execution.dispatch_fallback,
952
+ }
953
+ : {}),
954
+ ...(dispatchFallbackRuntime
955
+ ? { dispatchFallbackRuntime }
956
+ : {}),
337
957
  filesystemAllowedRoots: request.filesystemAllowedRoots?.map((root) => resolveFromBase(projectRoot, root)) ??
338
958
  [projectRoot],
339
959
  }));
@@ -354,6 +974,12 @@ export function createOntoReconstructCoreApi(options = {}) {
354
974
  message: `reconstruct session failed: ${error instanceof Error ? error.message : String(error)}`,
355
975
  stageId: "complete",
356
976
  });
977
+ const failedStatus = await recoverReconstructFailedRunStatus({
978
+ sessionRoot,
979
+ error,
980
+ });
981
+ if (failedStatus)
982
+ return failedStatus;
357
983
  throw error;
358
984
  }
359
985
  },
@@ -400,7 +1026,33 @@ export function createOntoReconstructCoreApi(options = {}) {
400
1026
  },
401
1027
  async getRunStatus(sessionRoot) {
402
1028
  const resolvedSessionRoot = path.resolve(sessionRoot);
403
- const reconstructRecord = await readYamlArtifact(path.join(resolvedSessionRoot, "reconstruct-record.yaml"));
1029
+ const runControlRef = path.join(resolvedSessionRoot, "reconstruct-run-control.yaml");
1030
+ const runControlValidationRef = path.join(resolvedSessionRoot, "reconstruct-run-control-validation.yaml");
1031
+ const reconciliation = await reconcileReconstructLlmDispatchFailures({
1032
+ sessionRoot: resolvedSessionRoot,
1033
+ runControlPath: runControlRef,
1034
+ validationOutputPath: runControlValidationRef,
1035
+ });
1036
+ const blockedPartialWrite = reconciliation
1037
+ ? [...reconciliation.runControl.resume_rows].reverse().find((row) => row.resume_decision === "blocked_partial_write")
1038
+ : undefined;
1039
+ if (blockedPartialWrite) {
1040
+ throw new Error(`reconstruct session is blocked by a partial failure write: ${blockedPartialWrite.stale_artifact_refs.join(",") || "unknown ref"}`);
1041
+ }
1042
+ const failedStatus = await trustedFailedSessionStatus({
1043
+ sessionRoot: resolvedSessionRoot,
1044
+ reconciliation,
1045
+ runControlRef,
1046
+ runControlValidationRef,
1047
+ });
1048
+ if (failedStatus)
1049
+ return failedStatus;
1050
+ const reconstructRecord = await readYamlArtifactIfPresent(path.join(resolvedSessionRoot, "reconstruct-record.yaml"));
1051
+ if (!reconstructRecord) {
1052
+ throw new Error(reconciliation
1053
+ ? `reconstruct session has no readable terminal record or trusted failed terminal: ${resolvedSessionRoot}`
1054
+ : `reconstruct session has neither a record nor run-control: ${resolvedSessionRoot}`);
1055
+ }
404
1056
  const reconstructRunManifest = await readYamlArtifactIfPresent(reconstructRecord.artifact_refs.reconstruct_run_manifest);
405
1057
  const reconstructMetrics = await readYamlArtifactIfPresent(reconstructRecord.artifact_refs.reconstruct_metrics);
406
1058
  const claimProjection = await readYamlArtifactIfPresent(reconstructRecord.artifact_refs.claim_projection);
@@ -416,7 +1068,7 @@ export function createOntoReconstructCoreApi(options = {}) {
416
1068
  return {
417
1069
  sessionId: path.basename(resolvedSessionRoot),
418
1070
  sessionRoot: resolvedSessionRoot,
419
- status: reconstructRecord.record_stage,
1071
+ status: reconstructTerminalStatus(reconstructRecord),
420
1072
  artifactRefs: reconstructRecord.artifact_refs,
421
1073
  claimProjection,
422
1074
  claimProjectionValidation,
@@ -431,6 +1083,15 @@ export function createOntoReconstructCoreApi(options = {}) {
431
1083
  },
432
1084
  async getRunResult(sessionRoot) {
433
1085
  const status = await this.getRunStatus(sessionRoot);
1086
+ if (status.status === "failed") {
1087
+ return {
1088
+ ...status,
1089
+ finalOutputPath: null,
1090
+ finalOutputText: null,
1091
+ reconstructRunManifestPath: null,
1092
+ reconstructRunManifest: null,
1093
+ };
1094
+ }
434
1095
  const finalOutputPath = status.reconstructRecord.artifact_refs.final_output;
435
1096
  const reconstructRunManifestPath = status.reconstructRecord.artifact_refs.reconstruct_run_manifest;
436
1097
  return {