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
@@ -1,10 +1,12 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
5
- import { RECONSTRUCT_STAGE_IDS } from "./artifact-types.js";
4
+ import { parse as parseYaml } from "yaml";
5
+ import { assertArrayField, atomicWriteYamlDocument as writeYamlDocument } from "../artifact-io.js";
6
+ import { RECONSTRUCT_STAGE_IDS, WITNESS_LESS_CONDITIONAL_STAGE_IDS } from "./artifact-types.js";
6
7
  import { loadReconstructContractRegistry, } from "./contract-registry.js";
7
8
  import { validateReconstructRunGoverningSnapshot } from "./governing-snapshot.js";
9
+ import { assertObligation } from "./obligation-assertion.js";
8
10
  function isoNow() {
9
11
  return new Date().toISOString();
10
12
  }
@@ -24,10 +26,6 @@ async function exists(ref) {
24
26
  return false;
25
27
  }
26
28
  }
27
- async function writeYamlDocument(filePath, value) {
28
- await fs.mkdir(path.dirname(filePath), { recursive: true });
29
- await fs.writeFile(filePath, stringifyYaml(value), "utf8");
30
- }
31
29
  async function readYamlDocument(filePath) {
32
30
  return parseYaml(await fs.readFile(filePath, "utf8"));
33
31
  }
@@ -48,8 +46,13 @@ const SELF_VALIDATION_OUTPUT_REFS = new Set([
48
46
  "pre_handoff_run_manifest_validation",
49
47
  "post_publication_run_manifest_validation",
50
48
  ]);
49
+ // The witness-less conditional stages permitted `skip_kind: "legit_conditional"` on a
50
+ // graceful-terminal manifest (canonical set in artifact-types.ts, shared with the builder).
51
+ const WITNESS_LESS_CONDITIONAL_STAGES = new Set(WITNESS_LESS_CONDITIONAL_STAGE_IDS);
51
52
  export async function validateReconstructRunManifest(args) {
53
+ assertArrayField(args.manifest.steps, "run-manifest", "steps");
52
54
  const violations = [];
55
+ const assertedObligationIds = [];
53
56
  const stepById = new Map(args.manifest.steps.map((step) => [step.step_id, step]));
54
57
  for (const stageId of RECONSTRUCT_STAGE_IDS) {
55
58
  if (!stepById.has(stageId)) {
@@ -60,6 +63,27 @@ export async function validateReconstructRunManifest(args) {
60
63
  }));
61
64
  }
62
65
  }
66
+ // W3 review W3-004: missing-only checking was fail-OPEN for extra/duplicate steps — a
67
+ // misregistered or duplicated step id validated green. Exact-set both directions.
68
+ const knownStageIds = new Set(RECONSTRUCT_STAGE_IDS);
69
+ const seenStepIds = new Set();
70
+ for (const step of args.manifest.steps) {
71
+ if (!knownStageIds.has(step.step_id)) {
72
+ violations.push(violation({
73
+ code: "manifest_step_unknown",
74
+ message: `manifest carries unknown stage ${step.step_id}`,
75
+ subjectId: step.step_id,
76
+ }));
77
+ }
78
+ if (seenStepIds.has(step.step_id)) {
79
+ violations.push(violation({
80
+ code: "manifest_step_duplicate",
81
+ message: `manifest carries duplicate stage ${step.step_id}`,
82
+ subjectId: step.step_id,
83
+ }));
84
+ }
85
+ seenStepIds.add(step.step_id);
86
+ }
63
87
  for (const step of args.manifest.steps) {
64
88
  if (step.status !== "completed")
65
89
  continue;
@@ -75,21 +99,33 @@ export async function validateReconstructRunManifest(args) {
75
99
  }
76
100
  if (SELF_VALIDATION_OUTPUT_REFS.has(step.step_id))
77
101
  continue;
78
- for (const ref of step.artifact_refs) {
79
- if (!(await exists(ref))) {
102
+ const refExistence = await Promise.all(step.artifact_refs.map((ref) => exists(ref)));
103
+ step.artifact_refs.forEach((ref, index) => {
104
+ if (!refExistence[index]) {
80
105
  violations.push(violation({
81
106
  code: "manifest_artifact_missing",
82
107
  message: `manifest step ${step.step_id} references a missing artifact: ${ref}`,
83
108
  subjectId: step.step_id,
84
109
  }));
85
110
  }
86
- }
111
+ });
87
112
  }
88
113
  if (args.projectRoot &&
89
114
  args.registryPath &&
90
115
  args.contractRegistry &&
91
116
  args.selectedSourceProfiles &&
92
117
  args.lensIds) {
118
+ // AUTHORITY-GATED record (slice 28): the governing-snapshot drift check rebuilds the expected
119
+ // snapshot from the live registry/profile/lens authority and compares each recorded field by exact
120
+ // value, so it only runs when that authority is supplied. The four recorded snapshot-freeze
121
+ // obligations (selected reference-standard / pattern-catalog ids resolve to the registry projection;
122
+ // their version/snapshot maps carry an entry per selected id) are stamped INSIDE the callee, at the
123
+ // per-field `checks` loop — past the missing-snapshot and registry-hash early-returns — so they
124
+ // record only when the comparisons actually run. The remaining 23 obligations stay PARKED (see
125
+ // obligation-coverage-ledger.yaml): shared-field obligations cannot bind a single one, the canonical
126
+ // URI obligation names a registry policy the rebuild never reads, "allowed/supported/contains" are
127
+ // enforced at snapshot-build time, the p2/p3 non-promotion policy and governed-seed/previous-id
128
+ // closures are not compared fields, and the registry ref+hash presence overlaps the hash-match path.
93
129
  violations.push(...await validateReconstructRunGoverningSnapshot({
94
130
  projectRoot: args.projectRoot,
95
131
  registryPath: args.registryPath,
@@ -98,6 +134,7 @@ export async function validateReconstructRunManifest(args) {
98
134
  lensIds: args.lensIds,
99
135
  admittedDomainIds: args.admittedDomainIds ?? [],
100
136
  snapshot: args.manifest.governing_snapshot,
137
+ assertedObligationIds,
101
138
  }));
102
139
  }
103
140
  else if (!args.manifest.governing_snapshot) {
@@ -107,6 +144,75 @@ export async function validateReconstructRunManifest(args) {
107
144
  subjectId: "governing_snapshot",
108
145
  }));
109
146
  }
147
+ // Graceful-terminal reachability rules (design v2 §4). Enforced ONLY when the manifest is
148
+ // an explicit graceful terminal — a completed run has no graceful_terminal field, so this
149
+ // whole block is skipped and completed-path validation stays byte-identical. Authority for
150
+ // "did a witness-less conditional stage run and legitimately produce nothing" is an
151
+ // INDEPENDENT reachability witness (census) read here; the manifest builder cannot
152
+ // self-declare a legit no-op the witness does not confirm (closes the v1 membership-only hole).
153
+ if (args.manifest.graceful_terminal) {
154
+ // legitNoOpByStage: stage -> legit_no_op flag, for witness-less stages that RAN and produced
155
+ // nothing. ranStages: every witness-less stage the census shows ran (produced or not).
156
+ const legitNoOpByStage = new Map();
157
+ const ranStages = new Set();
158
+ const witnessRef = args.manifest.graceful_terminal.reachability_witness_ref;
159
+ if (witnessRef) {
160
+ const census = await readYamlDocumentIfPresent(witnessRef);
161
+ if (!census) {
162
+ violations.push(violation({
163
+ code: "manifest_reachability_witness_missing",
164
+ message: `graceful manifest references a reachability witness that does not exist: ${witnessRef}`,
165
+ subjectId: "reachability_witness_ref",
166
+ }));
167
+ }
168
+ else {
169
+ for (const w of census.stage_witnesses) {
170
+ ranStages.add(w.step_id); // present in the census only because the stage ran
171
+ if (!w.produced)
172
+ legitNoOpByStage.set(w.step_id, w.legit_no_op);
173
+ }
174
+ }
175
+ }
176
+ for (const step of args.manifest.steps) {
177
+ if (step.status !== "skipped")
178
+ continue; // completed already ref-checked; failed out of scope
179
+ if (step.step_id === "invocation_binding")
180
+ continue;
181
+ if (step.skip_kind === undefined) {
182
+ // M5: a bare skipped step under a graceful terminal is a masking surface (a not-reached
183
+ // bug hiding as a healthy pre-handoff-style skip). Require the typed discriminant.
184
+ violations.push(violation({
185
+ code: "manifest_untyped_graceful_skip",
186
+ message: `graceful manifest skipped step lacks skip_kind: ${step.step_id}`,
187
+ subjectId: step.step_id,
188
+ }));
189
+ continue;
190
+ }
191
+ if (step.skip_kind === "legit_conditional") {
192
+ // M2: authorized by the WITNESS (ran-and-legit-no-op), not by allowlist membership. Only
193
+ // the witness-less lineage stages carry this witness; any other stage claiming it, or one
194
+ // the witness does not confirm ran-and-legit-no-op, is a masking attempt.
195
+ if (!WITNESS_LESS_CONDITIONAL_STAGES.has(step.step_id) ||
196
+ legitNoOpByStage.get(step.step_id) !== true) {
197
+ violations.push(violation({
198
+ code: "manifest_unwitnessed_conditional_skip",
199
+ message: `legit_conditional skip for ${step.step_id} is not confirmed by the reachability witness (must be a witness-less lineage stage that ran and legitimately produced nothing)`,
200
+ subjectId: step.step_id,
201
+ }));
202
+ }
203
+ }
204
+ else if (step.skip_kind === "not_reached") {
205
+ // A witness proving the stage RAN contradicts not_reached = masking attempt.
206
+ if (ranStages.has(step.step_id)) {
207
+ violations.push(violation({
208
+ code: "manifest_reached_stage_masked",
209
+ message: `${step.step_id} is marked not_reached but the reachability witness shows it ran`,
210
+ subjectId: step.step_id,
211
+ }));
212
+ }
213
+ }
214
+ }
215
+ }
110
216
  const completedStepCount = args.manifest.steps.filter((step) => step.status === "completed").length;
111
217
  const skippedStepCount = args.manifest.steps.filter((step) => step.status === "skipped").length;
112
218
  return {
@@ -120,6 +226,7 @@ export async function validateReconstructRunManifest(args) {
120
226
  validation_results: violations.length === 0
121
227
  ? ["reconstruct_run_manifest_valid"]
122
228
  : ["reconstruct_run_manifest_invalid"],
229
+ asserted_obligation_ids: assertedObligationIds,
123
230
  violations,
124
231
  };
125
232
  }
@@ -223,6 +330,10 @@ function validationArtifactStatuses(args) {
223
330
  "source-scout-pack-validation.post-maturation.yaml",
224
331
  statusOf(args.sourceScoutPackPostMaturationValidation),
225
332
  ],
333
+ [
334
+ "semantic-map-resume-validation.yaml",
335
+ statusOf(args.semanticMapResumeValidation),
336
+ ],
226
337
  [
227
338
  "material-admission-ledger-validation.yaml",
228
339
  statusOf(args.materialAdmissionLedgerValidation),
@@ -797,6 +908,15 @@ export function validateHandoffDecision(args) {
797
908
  gate.validation_status,
798
909
  ]));
799
910
  const violations = [];
911
+ // G(a) obligation recorder (INV-OBLIGATION-COVERAGE-1): record that control reached the
912
+ // active-gate consumption loop below. Unconditional, before the per-gate guard so a zero-gate
913
+ // input still stamps. Only the two ACTIVE-gate obligations are recorded — every PLANNED-gate
914
+ // obligation is parked because planned_validation_gate_catalog is never loaded into the runtime
915
+ // registry (projectGateStatusesOnce iterates validation_gate_catalog only); see
916
+ // obligation-coverage-ledger.yaml.
917
+ const assertedObligationIds = [];
918
+ assertObligation(assertedObligationIds, "consume_all_active_validation_gate_statuses_emitted_by_runtime");
919
+ assertObligation(assertedObligationIds, "project_missing_active_validation_artifact_as_blocked");
800
920
  for (const gate of gateProjection) {
801
921
  if (gate.applicability === "not_applicable" || gate.applicability === "self_validation_output") {
802
922
  continue;
@@ -858,13 +978,15 @@ export function validateHandoffDecision(args) {
858
978
  validation_results: violations.length === 0
859
979
  ? ["handoff_decision_valid"]
860
980
  : ["handoff_decision_invalid"],
981
+ asserted_obligation_ids: assertedObligationIds,
861
982
  violations,
862
983
  };
863
984
  }
864
985
  export async function writeReconstructRunManifestValidationArtifact(args) {
865
986
  const manifest = await readYamlDocument(args.manifestPath);
866
987
  const [contractRegistry, targetMaterialProfile] = await Promise.all([
867
- loadReconstructContractRegistry({ registryPath: args.registryPath }),
988
+ args.contractRegistry ??
989
+ loadReconstructContractRegistry({ registryPath: args.registryPath }),
868
990
  readYamlDocument(args.targetMaterialProfilePath),
869
991
  ]);
870
992
  const validation = await validateReconstructRunManifest({
@@ -882,7 +1004,8 @@ export async function writeReconstructRunManifestValidationArtifact(args) {
882
1004
  }
883
1005
  export async function writePostMaturationGateProjectionValidationArtifact(args) {
884
1006
  const [contractRegistry, sourceScoutPackPostMaturationValidation] = await Promise.all([
885
- loadReconstructContractRegistry({ registryPath: args.registryPath }),
1007
+ args.contractRegistry ??
1008
+ loadReconstructContractRegistry({ registryPath: args.registryPath }),
886
1009
  readYamlDocumentIfPresent(args.sourceScoutPackPostMaturationValidationPath),
887
1010
  ]);
888
1011
  const validation = validatePostMaturationGateProjection({
@@ -900,10 +1023,11 @@ export async function writeHandoffDecisionValidationArtifact(args) {
900
1023
  readYamlDocument(args.stopDecisionPath),
901
1024
  readYamlDocument(args.manifestValidationPath),
902
1025
  readYamlDocument(args.metricsPath),
903
- loadReconstructContractRegistry({ registryPath: args.registryPath }),
1026
+ args.contractRegistry ??
1027
+ loadReconstructContractRegistry({ registryPath: args.registryPath }),
904
1028
  ]);
905
1029
  const manifest = await readYamlDocumentIfPresent(manifestValidation.reconstruct_run_manifest_ref);
906
- const [runControlValidation, registryVerificationEvidenceValidation, targetMaterialProfileValidation, sourceObservationDirectiveValidation, sourceObservationLineageIndexValidation, sourceSafetyLedgerValidation, sourceScoutPackValidation, sourceScoutPackPreSeedValidation, sourceScoutPackPostMaturationValidation, materialAdmissionLedgerValidation, seedAuthoringReadinessValidation, sourceFrontierValidation, sourcePurposeCandidatesValidation, purposeConfirmationValidation, candidateDispositionValidation, ontologySeed, ontologySeedValidation, claimRealizationMapValidation, competencyQuestionsValidation, competencyQuestionAssessment, competencyQuestionAssessmentValidation, seedConfirmationValidation, failureClassificationValidation, revisionProposalValidation,] = await Promise.all([
1030
+ const [runControlValidation, registryVerificationEvidenceValidation, targetMaterialProfileValidation, sourceObservationDirectiveValidation, sourceObservationLineageIndexValidation, sourceSafetyLedgerValidation, sourceScoutPackValidation, sourceScoutPackPreSeedValidation, sourceScoutPackPostMaturationValidation, semanticMapResumeValidation, materialAdmissionLedgerValidation, seedAuthoringReadinessValidation, sourceFrontierValidation, sourcePurposeCandidatesValidation, purposeConfirmationValidation, candidateDispositionValidation, ontologySeed, ontologySeedValidation, claimRealizationMapValidation, competencyQuestionsValidation, competencyQuestionAssessment, competencyQuestionAssessmentValidation, seedConfirmationValidation, failureClassificationValidation, revisionProposalValidation,] = await Promise.all([
907
1031
  args.runControlValidationPath
908
1032
  ? readYamlDocumentIfPresent(args.runControlValidationPath)
909
1033
  : Promise.resolve(null),
@@ -927,6 +1051,9 @@ export async function writeHandoffDecisionValidationArtifact(args) {
927
1051
  args.sourceScoutPackPostMaturationValidationPath
928
1052
  ? readYamlDocumentIfPresent(args.sourceScoutPackPostMaturationValidationPath)
929
1053
  : Promise.resolve(null),
1054
+ args.semanticMapResumeValidationPath
1055
+ ? readYamlDocumentIfPresent(args.semanticMapResumeValidationPath)
1056
+ : Promise.resolve(null),
930
1057
  args.materialAdmissionLedgerValidationPath
931
1058
  ? readYamlDocumentIfPresent(args.materialAdmissionLedgerValidationPath)
932
1059
  : Promise.resolve(null),
@@ -985,6 +1112,7 @@ export async function writeHandoffDecisionValidationArtifact(args) {
985
1112
  sourceScoutPackPostMaturationValidation
986
1113
  ? args.sourceScoutPackPostMaturationValidationPath
987
1114
  : null,
1115
+ semanticMapResumeValidation ? args.semanticMapResumeValidationPath : null,
988
1116
  materialAdmissionLedgerValidation
989
1117
  ? args.materialAdmissionLedgerValidationPath
990
1118
  : null,
@@ -1041,6 +1169,9 @@ export async function writeHandoffDecisionValidationArtifact(args) {
1041
1169
  "source-scout-pack-validation.post-maturation.yaml": sourceScoutPackPostMaturationValidation
1042
1170
  ? args.sourceScoutPackPostMaturationValidationPath
1043
1171
  : null,
1172
+ "semantic-map-resume-validation.yaml": semanticMapResumeValidation
1173
+ ? args.semanticMapResumeValidationPath
1174
+ : null,
1044
1175
  "material-admission-ledger-validation.yaml": materialAdmissionLedgerValidation
1045
1176
  ? args.materialAdmissionLedgerValidationPath
1046
1177
  : null,
@@ -1093,6 +1224,7 @@ export async function writeHandoffDecisionValidationArtifact(args) {
1093
1224
  sourceScoutPackValidation,
1094
1225
  sourceScoutPackPreSeedValidation,
1095
1226
  sourceScoutPackPostMaturationValidation,
1227
+ semanticMapResumeValidation,
1096
1228
  materialAdmissionLedgerValidation,
1097
1229
  seedAuthoringReadinessValidation,
1098
1230
  sourceFrontierValidation,
@@ -1,4 +1,4 @@
1
- import { isTrustedLedgerUnit } from "../pipeline-execution-ledger.js";
1
+ import { isResolvedLedgerUnit, isTrustedLedgerUnit, } from "../pipeline-execution-ledger.js";
2
2
  function lensIdFromUnit(unit) {
3
3
  if (unit.unitKind === "lens")
4
4
  return unit.unitId;
@@ -21,13 +21,15 @@ function toContinuationUnit(unit, dispatchDecision, reason) {
21
21
  reason,
22
22
  };
23
23
  }
24
+ /** Upstream-satisfied set: trusted outputs plus terminally resolved units
25
+ * (a demoted unit's downstream consumed the disclosed gap, not its output). */
24
26
  function trustedUnitIds(ledger) {
25
27
  return new Set(ledger.units
26
- .filter((unit) => isTrustedLedgerUnit(unit))
28
+ .filter((unit) => isResolvedLedgerUnit(unit))
27
29
  .map((unit) => unit.unitId));
28
30
  }
29
31
  function isFrontierUnit(unit, trustedIds) {
30
- if (isTrustedLedgerUnit(unit))
32
+ if (isResolvedLedgerUnit(unit))
31
33
  return false;
32
34
  return unit.upstreamUnitIds.every((unitId) => trustedIds.has(unitId));
33
35
  }
@@ -76,6 +78,11 @@ function targetRejectedUnits(args) {
76
78
  toContinuationUnit(unit, "reject", "Target unit is already trusted and completed; completed outputs are reuse-only."),
77
79
  ];
78
80
  }
81
+ if (isResolvedLedgerUnit(unit)) {
82
+ return [
83
+ toContinuationUnit(unit, "reject", "Target unit is terminally resolved (demoted complete-with-failure); its gap is disclosed downstream and it owes no further dispatch."),
84
+ ];
85
+ }
79
86
  if (!args.naturalFrontierUnitIds.has(unitId)) {
80
87
  return [
81
88
  toContinuationUnit(unit, "reject", "Target unit is not the current continuation frontier; continue from the earliest untrusted unit first."),
@@ -102,7 +109,7 @@ export function buildReviewContinuationPlan(params) {
102
109
  const allRejectedTargets = [...rejectedTargets, ...rejectedMissingFrontierTargets];
103
110
  const targetFrontier = requestedTargetSet.size === 0
104
111
  ? naturalFrontier
105
- : params.ledger.units.filter((unit) => requestedTargetSet.has(unit.unitId) && !isTrustedLedgerUnit(unit));
112
+ : params.ledger.units.filter((unit) => requestedTargetSet.has(unit.unitId) && !isResolvedLedgerUnit(unit));
106
113
  const frontierUnits = allRejectedTargets.length > 0
107
114
  ? allRejectedTargets
108
115
  : targetFrontier.map((unit) => toContinuationUnit(unit, "run", requestedTargetSet.size === 0
@@ -116,7 +123,7 @@ export function buildReviewContinuationPlan(params) {
116
123
  frontierUnitIds,
117
124
  });
118
125
  const downstreamUnits = params.ledger.units
119
- .filter((unit) => downstreamIds.has(unit.unitId) && !isTrustedLedgerUnit(unit))
126
+ .filter((unit) => downstreamIds.has(unit.unitId) && !isResolvedLedgerUnit(unit))
120
127
  .map((unit) => toContinuationUnit(unit, "run", "Downstream unit must be recomputed after the continuation frontier."));
121
128
  const preservedArtifactRefs = params.ledger.units
122
129
  .filter((unit) => isTrustedLedgerUnit(unit))
@@ -3072,7 +3072,10 @@ export async function writeIssueStanceMatrixFromResponses(args) {
3072
3072
  session_id: args.executionPlan.session_id,
3073
3073
  issues,
3074
3074
  validation: {
3075
- missing_stances: [],
3075
+ missing_stances: (args.demotedLensIds ?? []).map((lensId) => ({
3076
+ lens_id: lensId,
3077
+ reason: "stance_validation_failed",
3078
+ })),
3076
3079
  },
3077
3080
  });
3078
3081
  await validateIssueArtifactOnDisk({
@@ -7,7 +7,9 @@ import { ensureDirectory, isoNow, normalizeDomainValue, renderReviewTargetMateri
7
7
  import { ISSUE_ARTIFACT_IDS, issueArtifactConsumerId, issueArtifactSpec, issueStanceConsumerId, } from "./issue-artifact-runtime.js";
8
8
  import { lensSidecarArtifactPath } from "./lens-sidecar-artifact.js";
9
9
  import { deliberationResolutionPath } from "./controlled-lens-deliberation.js";
10
- import { detectTargetMaterialKind, reviewMaterialSupportStatus, } from "../target-material-kind.js";
10
+ import { detectTargetMaterialKind, isSpreadsheetRef, reviewMaterialGoals, reviewMaterialSupportStatus, } from "../target-material-kind.js";
11
+ import { observeSpreadsheetSource, } from "../spreadsheet-structure-observer.js";
12
+ import { computeSpreadsheetDisposition, } from "./spreadsheet-review-disposition.js";
11
13
  import { semanticQualityEvidenceForArtifactGeneration } from "./artifact-generation-realization.js";
12
14
  /**
13
15
  * Load {projectRoot}/.onto/settings.json into the narrow subset used by
@@ -79,7 +81,7 @@ function derivePlanTimeLlmResolution(config) {
79
81
  plan.base_url = baseUrl;
80
82
  return Object.keys(plan).length > 0 ? plan : undefined;
81
83
  }
82
- function resolveReviewExecutionSettingsForArtifacts(config) {
84
+ export function resolveReviewExecutionSettingsForArtifacts(config) {
83
85
  const defaults = defaultReviewExecution();
84
86
  const execution = config.review?.execution;
85
87
  if (!execution)
@@ -655,6 +657,12 @@ export async function bootstrapInvocationBindingArtifacts(params) {
655
657
  effective_boundary_state: effectiveBoundaryState,
656
658
  max_concurrent_lenses: maxConcurrentLenses,
657
659
  minimum_participating_lenses: params.resolvedLensIds.length,
660
+ // Stage 1: persist the window-proportional embed budget so the packet stage
661
+ // reads it without re-resolving. Omitted when unresolved (model-unaware run)
662
+ // → packet stage falls back to the DEFAULT budget (no regression).
663
+ ...(params.maxEmbedLines !== undefined
664
+ ? { max_embed_lines: params.maxEmbedLines }
665
+ : {}),
658
666
  };
659
667
  const actorInvocationProfiles = {
660
668
  schema_version: "1",
@@ -944,7 +952,7 @@ function requireExecutionPreparationSessionDomain(value) {
944
952
  }
945
953
  return normalizeDomainValue(value);
946
954
  }
947
- async function buildReviewTargetProfileArtifact(params) {
955
+ async function buildReviewTargetProfileArtifact(params, inventoryByRef, materializedRefs) {
948
956
  const sessionRoot = path.resolve(params.sessionRoot);
949
957
  const sessionId = path.basename(sessionRoot);
950
958
  const resolvedRefs = params.resolvedTargetRefs.map((ref) => path.resolve(ref));
@@ -966,19 +974,82 @@ async function buildReviewTargetProfileArtifact(params) {
966
974
  inputKind,
967
975
  primaryRole: roles.primary,
968
976
  });
977
+ // SINGLE SOURCE OF TRUTH: one disposition per rendered spreadsheet ref, computed once
978
+ // over the UNION of resolved + materialized refs from the shared observation. Every
979
+ // honesty surface below (support_status, target_refs[].inspectable/.sha256, review_goal)
980
+ // PROJECTS from this map instead of re-deriving "is this workbook backed?" from a proxy.
981
+ const unionSpreadsheetRefs = uniqueStrings([...resolvedRefs, ...(materializedRefs ?? [])].filter(isSpreadsheetRef));
982
+ const dispositionByRef = new Map();
983
+ for (const ref of unionSpreadsheetRefs) {
984
+ dispositionByRef.set(ref, computeSpreadsheetDisposition(inventoryByRef?.get(ref), ref));
985
+ }
969
986
  const targetRefs = [];
970
987
  for (const [index, ref] of resolvedRefs.entries()) {
971
988
  const kind = await targetRefKind(ref, sessionRoot);
989
+ // Per-ref inspectability + sha256 PROJECT from the ref's disposition (spreadsheet-only).
990
+ // `inspectable` is the renderable-structure axis — a clean data CSV is inspectable even
991
+ // though it backs no structural obligation. sha256 reuses the single observation's
992
+ // content hash (no raw re-read; null when the observer declined to read the file).
993
+ const isSpreadsheet = isSpreadsheetRef(ref);
994
+ const disposition = isSpreadsheet ? dispositionByRef.get(ref) : undefined;
972
995
  targetRefs.push({
973
996
  ref,
974
997
  role: index === 0 ? "primary" : "supporting",
975
998
  kind: kind.kind,
976
999
  exists: kind.exists,
977
1000
  sha256: kind.exists
978
- ? await targetRefSha256(ref, kind.kind, params.directoryListingOptions)
1001
+ ? isSpreadsheet
1002
+ ? (disposition?.sha256 ?? null)
1003
+ : await targetRefSha256(ref, kind.kind, params.directoryListingOptions)
979
1004
  : null,
1005
+ ...(isSpreadsheet
1006
+ ? { inspectable: disposition?.inspectable ?? false }
1007
+ : {}),
980
1008
  });
981
1009
  }
1010
+ // SUPPORT-STATUS GATE — projects from the disposition set over the UNION of rendered
1011
+ // spreadsheet refs. It runs UNCONDITIONALLY (not only when the resolved kind is
1012
+ // spreadsheet) because materialized-input renders materializedRefs regardless of the
1013
+ // resolved kind (F1): a code-resolved target carrying a materialized workbook the
1014
+ // observer could not read must not stay `supported`. reviewMaterialSupportStatus is the
1015
+ // KIND-level baseline; any rendered workbook ref that is not inspectable downgrades it to
1016
+ // partial, naming each ref's honest cause (full path, deduped on path+cause).
1017
+ let supportStatus = materialSupport.status;
1018
+ let supportReason = materialSupport.reason;
1019
+ let materialGoals = reviewMaterialGoals(materialDetection.target_material_kind);
1020
+ const unionDispositions = unionSpreadsheetRefs.map((ref) => dispositionByRef.get(ref));
1021
+ const uninspected = unionDispositions.filter((d) => !d.inspectable);
1022
+ if (uninspected.length > 0) {
1023
+ supportStatus = "partial";
1024
+ // Each uninspected disposition carries a non-null `reason` by construction (disposition
1025
+ // invariant: !inspectable ⇒ reason is set); `?? d.ref` only satisfies the string|null
1026
+ // type and is never actually taken.
1027
+ supportReason = `spreadsheet review inspects structure only; ${uninspected.length} of ${unionDispositions.length} workbook ref(s) were not inspected (${uniqueStrings(uninspected.map((d) => d.reason ?? d.ref)).join("; ")})`;
1028
+ }
1029
+ // review_goal spreadsheet obligations = union of backed_goals across the RESOLVED
1030
+ // spreadsheet refs (kind stays the trigger per the approved F1 scope: only support_status
1031
+ // reflects the materialized union). backed_goals is the POSITIVE rule — a goal is carried
1032
+ // only when its specific evidence was rendered for that ref, so an obligation never
1033
+ // outruns its backing (a plain data CSV carries none; a macro-only workbook carries only
1034
+ // access_and_protection_hygiene).
1035
+ if (materialDetection.target_material_kind === "spreadsheet") {
1036
+ materialGoals = uniqueStrings(resolvedRefs
1037
+ .filter(isSpreadsheetRef)
1038
+ .flatMap((ref) => dispositionByRef.get(ref)?.backed_goals ?? []));
1039
+ // No directly-inspectable workbook ref at all — e.g. a directory aggregated as
1040
+ // spreadsheet with no workbook ref, or every ref uninspectable. Nothing backs an
1041
+ // obligation and the render shows only a listing/unsupported note, so a kind-level
1042
+ // `supported` would be dishonest.
1043
+ const anyInspectable = unionDispositions.some((d) => d.inspectable);
1044
+ if (!anyInspectable) {
1045
+ materialGoals = [];
1046
+ if (supportStatus === "supported") {
1047
+ supportStatus = "partial";
1048
+ supportReason =
1049
+ "spreadsheet target has no directly-inspectable workbook ref (no inventory-backed structural detail rendered)";
1050
+ }
1051
+ }
1052
+ }
982
1053
  return {
983
1054
  schema_version: "1",
984
1055
  session_id: sessionId,
@@ -1000,6 +1071,7 @@ async function buildReviewTargetProfileArtifact(params) {
1000
1071
  ...goalsForRole(roles.primary),
1001
1072
  ...roles.secondary.flatMap(goalsForRole),
1002
1073
  ...domainGoalAdditions(sessionDomain),
1074
+ ...materialGoals,
1003
1075
  ]),
1004
1076
  closure_obligation_policy: [
1005
1077
  "must_close_in_target",
@@ -1012,8 +1084,8 @@ async function buildReviewTargetProfileArtifact(params) {
1012
1084
  material_profile: {
1013
1085
  target_material_kind: materialDetection.target_material_kind,
1014
1086
  target_material_kind_candidates: materialDetection.target_material_kind_candidates,
1015
- support_status: materialSupport.status,
1016
- unsupported_reason: materialSupport.reason,
1087
+ support_status: supportStatus,
1088
+ unsupported_reason: supportReason,
1017
1089
  detection: {
1018
1090
  owner: "runtime_heuristic",
1019
1091
  confidence: materialDetection.confidence,
@@ -1045,17 +1117,34 @@ export async function materializeReviewExecutionPreparationArtifacts(params) {
1045
1117
  const reviewTargetProfilePath = path.join(executionPreparationRoot, "review-target-profile.yaml");
1046
1118
  const contextCandidateAssemblyPath = path.join(executionPreparationRoot, "context-candidate-assembly.yaml");
1047
1119
  await ensureDirectory(executionPreparationRoot);
1120
+ const resolvedTargetRefs = params.resolvedTargetRefs.map((ref) => path.resolve(ref));
1048
1121
  const materializedRefs = params.materializedRefs && params.materializedRefs.length > 0
1049
1122
  ? params.materializedRefs.map((ref) => path.resolve(ref))
1050
- : params.resolvedTargetRefs.map((ref) => path.resolve(ref));
1123
+ : resolvedTargetRefs;
1124
+ // Observe each spreadsheet ref ONCE and share the inventory across the profile and
1125
+ // both renders (design §3.2 "single observation"): a workbook would otherwise be
1126
+ // parsed up to three times (target-snapshot + materialized-input + profile
1127
+ // inspectability). The map feeds per-target inspectability and the rendered detail.
1128
+ const spreadsheetRefs = [
1129
+ ...new Set([...resolvedTargetRefs, ...materializedRefs].filter(isSpreadsheetRef)),
1130
+ ];
1131
+ // Observe SEQUENTIALLY (not Promise.all): observeSpreadsheetSource reads each source
1132
+ // fully into a Buffer (up to the 1 GiB pre-read limit) before streaming-unzip, so
1133
+ // observing every ref of a multi-workbook bundle at once could exhaust host memory
1134
+ // before any artifact is written. Sequential keeps peak memory at one workbook while
1135
+ // still observing — and sharing — each inventory exactly once.
1136
+ const inventoryByRef = new Map();
1137
+ for (const ref of spreadsheetRefs) {
1138
+ inventoryByRef.set(ref, await observeSpreadsheetSource(ref));
1139
+ }
1051
1140
  const targetSnapshotManifest = {
1052
1141
  review_target_scope_kind: params.scopeKind,
1053
- resolved_target_refs: params.resolvedTargetRefs.map((ref) => path.resolve(ref)),
1142
+ resolved_target_refs: resolvedTargetRefs,
1054
1143
  review_target_profile_ref: reviewTargetProfilePath,
1055
1144
  captured_at: isoNow(),
1056
1145
  capture_reason: "prompt-backed review execution",
1057
1146
  };
1058
- const reviewTargetProfile = await buildReviewTargetProfileArtifact(params);
1147
+ const reviewTargetProfile = await buildReviewTargetProfileArtifact(params, inventoryByRef, materializedRefs);
1059
1148
  const contextCandidateAssembly = {
1060
1149
  system_purpose_refs: params.systemPurposeRefs ?? [],
1061
1150
  domain_context_refs: params.domainContextRefs ?? [],
@@ -1063,10 +1152,10 @@ export async function materializeReviewExecutionPreparationArtifacts(params) {
1063
1152
  execution_rule_refs: params.executionRuleRefs ?? [],
1064
1153
  };
1065
1154
  await Promise.all([
1066
- fs.writeFile(targetSnapshotPath, await renderTargetSnapshot(params.resolvedTargetRefs.map((ref) => path.resolve(ref)), params.directoryListingOptions), "utf8"),
1155
+ fs.writeFile(targetSnapshotPath, await renderTargetSnapshot(resolvedTargetRefs, params.directoryListingOptions, inventoryByRef, params.inventoryPromptCaps), "utf8"),
1067
1156
  writeYamlDocument(targetSnapshotManifestPath, targetSnapshotManifest),
1068
1157
  writeYamlDocument(reviewTargetProfilePath, reviewTargetProfile),
1069
- fs.writeFile(materializedInputPath, await renderReviewTargetMaterializedInput(params.materializedKind, materializedRefs, params.directoryListingOptions), "utf8"),
1158
+ fs.writeFile(materializedInputPath, await renderReviewTargetMaterializedInput(params.materializedKind, materializedRefs, params.directoryListingOptions, inventoryByRef, params.inventoryPromptCaps), "utf8"),
1070
1159
  writeYamlDocument(contextCandidateAssemblyPath, contextCandidateAssembly),
1071
1160
  ]);
1072
1161
  return executionPreparationRoot;
@@ -104,7 +104,7 @@ export function buildNestingBatchScript(descriptor) {
104
104
  const auditPath = path.join(outputDir, `.${safeId}.nested-stderr.log`);
105
105
  const statRef = `"$TMPDIR/u${index}.status"`;
106
106
  const okJson = `{"unit_id":"${jsonEscape(unit.unit_id)}","status":"ok"}`;
107
- const failFmt = `{"unit_id":"${jsonEscape(unit.unit_id)}","status":"fail","error":"exit=%s size=%s"}`;
107
+ const failFmt = `{"unit_id":"${jsonEscape(unit.unit_id)}","status":"fail","error":"exit=%s size=%s log_tail=%s"}`;
108
108
  const fallbackJson = `{"unit_id":"${jsonEscape(unit.unit_id)}","status":"fail","error":"no status reported"}`;
109
109
  const unitArgs = [
110
110
  "--unit-id",
@@ -136,7 +136,11 @@ export function buildNestingBatchScript(descriptor) {
136
136
  ' if [ "$EC" = "0" ] && [ "$SIZE" -gt 0 ]; then',
137
137
  ` printf '%s' ${shellQuote(okJson)} > ${statRef}`,
138
138
  " else",
139
- ` printf ${shellQuote(failFmt)} "$EC" "$SIZE" > ${statRef}`,
139
+ ' ERROR_HINT=""',
140
+ ' if [ -f "$LOG" ]; then',
141
+ ` ERROR_HINT=$(tail -n 20 "$LOG" 2>/dev/null | tr '\\n\\r\\t' ' ' | tr '\\000-\\010\\013\\014\\016-\\037\\177' ' ' | cut -c1-500 | sed 's/\\\\/\\\\\\\\/g; s/"/\\\\"/g')`,
142
+ " fi",
143
+ ` printf ${shellQuote(failFmt)} "$EC" "$SIZE" "$ERROR_HINT" > ${statRef}`,
140
144
  " fi",
141
145
  ") &",
142
146
  ].join("\n"));