onto-mcp 0.4.12 → 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 (128) hide show
  1. package/.onto/authority/core-lexicon.yaml +1 -1
  2. package/.onto/authority/supported-models.yaml +137 -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 +169 -8
  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/productized-live-path.md +9 -5
  11. package/.onto/processes/review/prompt-execution-runner-contract.md +32 -8
  12. package/.onto/processes/review/review-target-profile-contract.md +47 -4
  13. package/.onto/processes/shared/pipeline-execution-ledger-contract.md +11 -0
  14. package/.onto/processes/shared/target-material-kind-contract.md +1 -1
  15. package/README.md +64 -1
  16. package/dist/cli.js +11 -1
  17. package/dist/core-api/reconstruct-api.js +575 -24
  18. package/dist/core-api/review-api.js +70 -5
  19. package/dist/core-api/review-progress.js +7 -0
  20. package/dist/core-api/runtime-observation.js +7 -0
  21. package/dist/core-runtime/artifact-io.js +75 -3
  22. package/dist/core-runtime/cli/claude-code-review-unit-executor.js +19 -8
  23. package/dist/core-runtime/cli/materialize-review-prompt-packets.js +68 -14
  24. package/dist/core-runtime/cli/prepare-review-session.js +34 -1
  25. package/dist/core-runtime/cli/render-review-final-output.js +2 -1
  26. package/dist/core-runtime/cli/run-review-prompt-execution.js +758 -48
  27. package/dist/core-runtime/cli/run-seats.js +18 -0
  28. package/dist/core-runtime/cli/runtime-submit-context.js +10 -2
  29. package/dist/core-runtime/cli/unit-resubmit.js +269 -0
  30. package/dist/core-runtime/cli/worker-structured-output.js +80 -2
  31. package/dist/core-runtime/discovery/host-detection.js +71 -22
  32. package/dist/core-runtime/discovery/review-cert-assemble.js +208 -0
  33. package/dist/core-runtime/discovery/review-cert-record.js +442 -0
  34. package/dist/core-runtime/discovery/review-execution-units.js +22 -0
  35. package/dist/core-runtime/discovery/seat-inventory.js +167 -0
  36. package/dist/core-runtime/discovery/settings-chain.js +311 -52
  37. package/dist/core-runtime/discovery/supported-models.js +282 -29
  38. package/dist/core-runtime/discovery/synthesize-cert-assemble.js +197 -0
  39. package/dist/core-runtime/discovery/synthesize-cert-capsule.js +399 -0
  40. package/dist/core-runtime/discovery/synthesize-cert-judge.js +340 -0
  41. package/dist/core-runtime/discovery/synthesize-cert-loop.js +234 -0
  42. package/dist/core-runtime/discovery/synthesize-cert-mutation.js +117 -0
  43. package/dist/core-runtime/discovery/synthesize-cert-packet.js +182 -0
  44. package/dist/core-runtime/discovery/synthesize-cert-record.js +831 -0
  45. package/dist/core-runtime/discovery/synthesize-cert-sampler.js +336 -0
  46. package/dist/core-runtime/effort-calibration-ingest.js +135 -0
  47. package/dist/core-runtime/llm/dispatch-breaker.js +412 -0
  48. package/dist/core-runtime/llm/dispatch-fallback-adapter-capabilities.js +33 -0
  49. package/dist/core-runtime/llm/llm-caller.js +188 -47
  50. package/dist/core-runtime/llm/mock-llm-realization.js +35 -4
  51. package/dist/core-runtime/llm/model-switcher.js +1 -1
  52. package/dist/core-runtime/llm/openai-responses-incomplete-error.js +46 -0
  53. package/dist/core-runtime/llm/sealed-dispatch-capability.js +359 -0
  54. package/dist/core-runtime/llm/structured-dispatch-error.js +15 -0
  55. package/dist/core-runtime/onboard/claude-profile-scan.js +65 -0
  56. package/dist/core-runtime/onboard/cli-host.js +5 -2
  57. package/dist/core-runtime/onboard/configure-provider.js +33 -1
  58. package/dist/core-runtime/onboard/host-target.js +28 -3
  59. package/dist/core-runtime/onboard/register.js +54 -12
  60. package/dist/core-runtime/pipeline-execution-ledger.js +10 -0
  61. package/dist/core-runtime/reconstruct/artifact-types.js +35 -0
  62. package/dist/core-runtime/reconstruct/claim-projection-validation.js +17 -0
  63. package/dist/core-runtime/reconstruct/competency-projection-contract.js +63 -0
  64. package/dist/core-runtime/reconstruct/comprehension-artifact.js +321 -0
  65. package/dist/core-runtime/reconstruct/comprehension-reduce.js +367 -0
  66. package/dist/core-runtime/reconstruct/comprehension-semantic-map.js +771 -0
  67. package/dist/core-runtime/reconstruct/contract-registry.js +20 -0
  68. package/dist/core-runtime/reconstruct/dispatch-fallback-artifacts.js +640 -0
  69. package/dist/core-runtime/reconstruct/execution-telemetry.js +83 -9
  70. package/dist/core-runtime/reconstruct/final-output-sections.js +89 -0
  71. package/dist/core-runtime/reconstruct/governing-snapshot.js +36 -1
  72. package/dist/core-runtime/reconstruct/leaf-reader.js +305 -0
  73. package/dist/core-runtime/reconstruct/llm-dispatch-failure.js +270 -0
  74. package/dist/core-runtime/reconstruct/llm-touch-fingerprint.js +94 -0
  75. package/dist/core-runtime/reconstruct/material-admission-validation.js +18 -0
  76. package/dist/core-runtime/reconstruct/material-profile-validation.js +14 -0
  77. package/dist/core-runtime/reconstruct/materialize-preparation.js +304 -18
  78. package/dist/core-runtime/reconstruct/maturation-validation.js +1231 -53
  79. package/dist/core-runtime/reconstruct/mock-llm-realization.js +107 -3
  80. package/dist/core-runtime/reconstruct/obligation-assertion.js +18 -0
  81. package/dist/core-runtime/reconstruct/ontology-seed-validation.js +27 -0
  82. package/dist/core-runtime/reconstruct/output-budget.js +6 -0
  83. package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +87 -3
  84. package/dist/core-runtime/reconstruct/post-seed-validation.js +118 -5
  85. package/dist/core-runtime/reconstruct/purpose-authority-validation.js +117 -2
  86. package/dist/core-runtime/reconstruct/record.js +42 -0
  87. package/dist/core-runtime/reconstruct/registry-verification-validation.js +16 -0
  88. package/dist/core-runtime/reconstruct/run-control-validation.js +1011 -34
  89. package/dist/core-runtime/reconstruct/run.js +5198 -536
  90. package/dist/core-runtime/reconstruct/seed-authoring-readiness-validation.js +54 -3
  91. package/dist/core-runtime/reconstruct/semantic-quality-gate.js +105 -2
  92. package/dist/core-runtime/reconstruct/source-observation-delta-validation.js +41 -0
  93. package/dist/core-runtime/reconstruct/source-observations.js +166 -0
  94. package/dist/core-runtime/reconstruct/source-safety-validation.js +71 -183
  95. package/dist/core-runtime/reconstruct/source-scout-pack-validation.js +37 -1
  96. package/dist/core-runtime/reconstruct/terminal-validation.js +132 -2
  97. package/dist/core-runtime/review/continuation-plan.js +12 -5
  98. package/dist/core-runtime/review/issue-artifact-runtime.js +4 -1
  99. package/dist/core-runtime/review/materializers.js +100 -11
  100. package/dist/core-runtime/review/nesting-batch.js +6 -2
  101. package/dist/core-runtime/review/obligation-shardability.js +138 -0
  102. package/dist/core-runtime/review/pipeline-execution-ledger.js +42 -2
  103. package/dist/core-runtime/review/review-artifact-utils.js +217 -5
  104. package/dist/core-runtime/review/review-execution-steps.js +32 -8
  105. package/dist/core-runtime/review/review-progress-contract.js +8 -0
  106. package/dist/core-runtime/review/review-prompt-budget.js +97 -0
  107. package/dist/core-runtime/review/semantic-quality-gate.js +33 -7
  108. package/dist/core-runtime/review/spreadsheet-review-disposition.js +94 -0
  109. package/dist/core-runtime/route-identity.js +182 -0
  110. package/dist/core-runtime/spreadsheet-header-escalation.js +204 -0
  111. package/dist/core-runtime/spreadsheet-structure-observer.js +2689 -0
  112. package/dist/core-runtime/target-material-kind.js +58 -0
  113. package/dist/mcp/server.js +12 -5
  114. package/dist/tui/app.js +167 -0
  115. package/dist/tui/data/event-follower.js +116 -0
  116. package/dist/tui/data/node-detail.js +42 -0
  117. package/dist/tui/data/projection-poll.js +26 -0
  118. package/dist/tui/data/session-discovery.js +65 -0
  119. package/dist/tui/index.js +108 -0
  120. package/dist/tui/screens/log.js +25 -0
  121. package/dist/tui/screens/node-detail.js +17 -0
  122. package/dist/tui/screens/session-selector.js +26 -0
  123. package/dist/tui/screens/workflow-tree.js +90 -0
  124. package/dist/tui/view-model/reconstruct-adapter.js +162 -0
  125. package/dist/tui/view-model/review-adapter.js +270 -0
  126. package/dist/tui/view-model/tree-view-model.js +19 -0
  127. package/package.json +18 -3
  128. package/settings.example.json +77 -16
@@ -2,6 +2,8 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { parse as parseYaml } from "yaml";
4
4
  import { atomicWriteYamlDocument as writeYamlDocument } from "../artifact-io.js";
5
+ import { assertObligation } from "./obligation-assertion.js";
6
+ import { TARGET_MATERIAL_KINDS } from "../target-material-kind.js";
5
7
  const PURPOSE_EVIDENCE_KINDS = [
6
8
  "P1",
7
9
  "P2",
@@ -9,6 +11,12 @@ const PURPOSE_EVIDENCE_KINDS = [
9
11
  "P4",
10
12
  "P5",
11
13
  ];
14
+ const PURPOSE_FRAME_STATUSES = [
15
+ "source_declared",
16
+ "evidence_inferred",
17
+ "limitation_backed",
18
+ "unresolved",
19
+ ];
12
20
  function isoNow() {
13
21
  return new Date().toISOString();
14
22
  }
@@ -93,6 +101,22 @@ function hasNonEmptyString(value) {
93
101
  export function validateSourcePurposeCandidates(args) {
94
102
  const artifact = args.sourcePurposeCandidates;
95
103
  const violations = [];
104
+ // G(a) slice 24 — record the obligations this validator FULLY enforces with a distinct violation
105
+ // (RECORD 6/10). The other 4 stay parked (see obligation-coverage-ledger.yaml notes): derive_
106
+ // confirmation_required and expose_selected_..._id are PROJECTION-ONLY (computed into the return
107
+ // object with no violation → no breaching binding); require_purpose_candidate_status_field_to_be_
108
+ // purpose_source_status is PARTIAL (only the artifact-level alias-field rejection is enforced, no
109
+ // per-candidate purpose_source_status enum check); and require_exactly_one_primary_..._when_selected
110
+ // is enforced UNCONDITIONALLY (blind to the "when selected" discriminator the contract scopes it to —
111
+ // slice-20 superset-blind park). Stamped before any per-candidate guard so the recorder fires on
112
+ // zero-candidate input.
113
+ const assertedObligationIds = [];
114
+ assertObligation(assertedObligationIds, "preserve_contradictions_in_candidate_status_or_limitations");
115
+ assertObligation(assertedObligationIds, "reject_p5_only_primary_purpose_candidate");
116
+ assertObligation(assertedObligationIds, "require_mixed_purpose_frame_elements_to_carry_member_scope_refs_member_target_material_kind_member_source_refs_and_cross_material_ref_refs_or_validated_exclusion");
117
+ assertObligation(assertedObligationIds, "require_non_p1_primary_to_have_two_evidence_kinds_and_one_p2_p3_or_p4");
118
+ assertObligation(assertedObligationIds, "require_purpose_adequacy_frame_elements_to_have_surface_dimension_facet_and_evidence_or_limitation_state");
119
+ assertObligation(assertedObligationIds, "validate_purpose_candidate_evidence_refs_against_source_observations");
96
120
  const evidenceIndex = observationEvidenceIndex(args.sourceObservations);
97
121
  const rawArtifact = artifact;
98
122
  if ("source_purpose_status" in rawArtifact || "inference_status" in rawArtifact) {
@@ -217,12 +241,74 @@ export function validateSourcePurposeCandidates(args) {
217
241
  subjectId: candidate.purpose_candidate_id,
218
242
  }));
219
243
  }
220
- if (!Array.isArray(frame.required_elements) || frame.required_elements.length === 0) {
244
+ // The frame header (frame_kind, frame_status, material_kind_requirements) is required for
245
+ // every candidate regardless of rank. The direct-call mapper enforces it on the authoring
246
+ // path; this validator must enforce it on the disk/reuse path (source-purpose-candidates.yaml
247
+ // is parsed without schema validation) so a malformed frame is never blessed as valid. Only
248
+ // required_elements is rank-dependent — a rejected candidate may leave it empty (below).
249
+ if (!hasNonEmptyString(frame.frame_kind)) {
250
+ violations.push(violation({
251
+ code: "required_element_missing",
252
+ message: `${candidatePath}.adequacy_frame.frame_kind must be non-empty`,
253
+ subjectId: candidate.purpose_candidate_id,
254
+ }));
255
+ }
256
+ if (!PURPOSE_FRAME_STATUSES.includes(frame.frame_status)) {
257
+ violations.push(violation({
258
+ code: "invalid_enum",
259
+ message: `${candidatePath}.adequacy_frame.frame_status is not a valid frame status`,
260
+ subjectId: candidate.purpose_candidate_id,
261
+ }));
262
+ }
263
+ const requirements = frame.material_kind_requirements;
264
+ if (typeof requirements !== "object" || requirements === null) {
221
265
  violations.push(violation({
222
266
  code: "required_element_missing",
223
- message: `${candidatePath}.adequacy_frame.required_elements must not be empty`,
267
+ message: `${candidatePath}.adequacy_frame.material_kind_requirements is required`,
224
268
  subjectId: candidate.purpose_candidate_id,
225
269
  }));
270
+ }
271
+ else {
272
+ if (!TARGET_MATERIAL_KINDS.includes(requirements.target_material_kind)) {
273
+ violations.push(violation({
274
+ code: "invalid_enum",
275
+ message: `${candidatePath}.adequacy_frame.material_kind_requirements.target_material_kind is invalid`,
276
+ subjectId: candidate.purpose_candidate_id,
277
+ }));
278
+ }
279
+ if (!Array.isArray(requirements.required_facets)) {
280
+ violations.push(violation({
281
+ code: "required_element_missing",
282
+ message: `${candidatePath}.adequacy_frame.material_kind_requirements.required_facets must be an array`,
283
+ subjectId: candidate.purpose_candidate_id,
284
+ }));
285
+ }
286
+ if (!hasNonEmptyString(requirements.rationale)) {
287
+ violations.push(violation({
288
+ code: "required_element_missing",
289
+ message: `${candidatePath}.adequacy_frame.material_kind_requirements.rationale must be non-empty`,
290
+ subjectId: candidate.purpose_candidate_id,
291
+ }));
292
+ }
293
+ }
294
+ if (!Array.isArray(frame.required_elements) || frame.required_elements.length === 0) {
295
+ // Rejected candidates record a considered-and-excluded alternative for provenance,
296
+ // not an active adequacy frame, so they may leave required_elements as an empty array
297
+ // (the full frame header above is still required for every rank). Mirrors the rejected
298
+ // exemption for supporting evidence. Only the empty-array case is exempt: a non-array
299
+ // required_elements still violates the artifact contract for every rank, and
300
+ // non-rejected candidates must still carry frame elements. When a rejected candidate
301
+ // does provide elements, the element-format checks below still apply.
302
+ const rejectedEmptyArray = candidate.rank === "rejected" &&
303
+ Array.isArray(frame.required_elements) &&
304
+ frame.required_elements.length === 0;
305
+ if (!rejectedEmptyArray) {
306
+ violations.push(violation({
307
+ code: "required_element_missing",
308
+ message: `${candidatePath}.adequacy_frame.required_elements must not be empty`,
309
+ subjectId: candidate.purpose_candidate_id,
310
+ }));
311
+ }
226
312
  continue;
227
313
  }
228
314
  for (const [elementIndex, element] of frame.required_elements.entries()) {
@@ -289,6 +375,7 @@ export function validateSourcePurposeCandidates(args) {
289
375
  validation_results: violations.length === 0
290
376
  ? ["source_purpose_candidates_valid"]
291
377
  : ["source_purpose_candidates_invalid"],
378
+ asserted_obligation_ids: assertedObligationIds,
292
379
  violations,
293
380
  };
294
381
  }
@@ -296,6 +383,33 @@ export function validatePurposeConfirmation(args) {
296
383
  const confirmation = args.purposeConfirmation;
297
384
  const sourceValidation = args.sourcePurposeCandidatesValidation;
298
385
  const violations = [];
386
+ // G(a) slice 11: record the three obligations whose enforcement matches the authoritative contract
387
+ // (ontology-seeding-and-maturation-design.md §purpose-confirmation; confirmation_required is the
388
+ // derived inferred/limitation_backed predicate and "the only source for the purpose-confirmation
389
+ // gate"). All three reach a distinct, name-matching enforcement site below; stamped unconditionally
390
+ // up front so they fire on any input:
391
+ // - validate_confirmation_status_against_selected_purpose_candidate (selected_primary_mismatch
392
+ // when purpose_candidate_id != selected_purpose_candidate_id — distinct site).
393
+ // - require_confirmation_for_inferred_or_limitation_backed_purpose (when confirmation_required is
394
+ // true a non-confirmed status — e.g. not_required — blocks: conflicting_state). Per the contract
395
+ // the validator consumes confirmation_required rather than re-deriving inferred/limitation_backed.
396
+ // - block_seed_readiness_when_confirmation_is_pending_rejected_unavailable_or_evidence_check_pending
397
+ // (each of those statuses blocks seed readiness even when confirmation is not required).
398
+ // The two former obligations are DISTINCT facets with non-overlapping breaching inputs: ob "require"
399
+ // is bound by confirmation_required=true + status=not_required (a status not in the "block" list);
400
+ // ob "block" is bound by confirmation_required=false + status=pending (no confirmation required, yet
401
+ // it still blocks). PARKED (not recorded), name broader than code — honest declared≠wired, NOT
402
+ // laundered (see obligation-coverage-ledger.yaml notes):
403
+ // - require_revised_confirmation_to_preserve_source_conflict_or_trigger_purpose_discovery_rerun:
404
+ // only the rerun arm is enforced (revised_pending_evidence_check → must_rerun_purpose_discovery);
405
+ // the "preserve source conflict" arm is never checked — source_conflict_policy is never read.
406
+ // - validate_confirmation_status_against_source_purpose_candidate_status_and_validation_confirmation_required:
407
+ // the validator reads only sourceValidation.validation_status and confirmation_required, never the
408
+ // candidate's own purpose_source_status the name references.
409
+ const assertedObligationIds = [];
410
+ assertObligation(assertedObligationIds, "validate_confirmation_status_against_selected_purpose_candidate");
411
+ assertObligation(assertedObligationIds, "require_confirmation_for_inferred_or_limitation_backed_purpose");
412
+ assertObligation(assertedObligationIds, "block_seed_readiness_when_confirmation_is_pending_rejected_unavailable_or_evidence_check_pending");
299
413
  if (confirmation.session_id !== sourceValidation.session_id) {
300
414
  violations.push(violation({
301
415
  code: "session_id_mismatch",
@@ -384,6 +498,7 @@ export function validatePurposeConfirmation(args) {
384
498
  validation_results: violations.length === 0
385
499
  ? ["purpose_confirmation_valid"]
386
500
  : ["purpose_confirmation_invalid"],
501
+ asserted_obligation_ids: assertedObligationIds,
387
502
  violations,
388
503
  };
389
504
  }
@@ -15,11 +15,21 @@ const RECORD_ARTIFACT_KEYS = [
15
15
  "source_inventory",
16
16
  "initial_source_frontier",
17
17
  "source_observations",
18
+ "seed_stage_prompt_source_observations",
18
19
  "source_observation_delta",
19
20
  "source_observation_delta_validation",
20
21
  "source_observation_reentry_validation",
21
22
  "source_observation_lineage_index",
22
23
  "source_observation_lineage_index_validation",
24
+ // W3 code review W3-001: normalizeRefs silently DROPS any typed ref key missing from this list —
25
+ // leaf_read_census was typed on ReconstructRecordArtifactRefs but dropped here (pre-existing
26
+ // latent type↔behavior mismatch); registered together with the semantic_map refs so the record
27
+ // (the durable primary evidence artifact) carries what the type declares.
28
+ "leaf_read_census",
29
+ "dispatch_incomplete",
30
+ "semantic_map_census",
31
+ "semantic_map_sidecar",
32
+ "semantic_map_resume_validation",
23
33
  "source_safety_ledger",
24
34
  "source_safety_ledger_validation",
25
35
  "source_scout_pack",
@@ -306,6 +316,30 @@ function buildWarnings(args) {
306
316
  }
307
317
  return warnings;
308
318
  }
319
+ /**
320
+ * The single canonical terminal-status projection over a reconstruct record (design §16.7).
321
+ *
322
+ * "Is this run done, and how?" is judged here and nowhere else: `getRunStatus` (the `status`
323
+ * field), `deriveReconstructProgress` (liveness / poll interval) and the TUI `deriveWorkflowStatus`
324
+ * all derive from this one function, so a graceful terminal is treated uniformly as terminal
325
+ * without each consumer re-checking `record_stage === "completed"` (the removed "diamond").
326
+ * `terminal_disposition` present ⇒ blocked/limited (terminal, stop polling, NOT "completed");
327
+ * else the raw `record_stage` ("completed" when the pipeline finished, otherwise in-progress).
328
+ */
329
+ export function reconstructTerminalStatus(record) {
330
+ return record.terminal_disposition ?? record.record_stage;
331
+ }
332
+ /**
333
+ * Record-validator invariant (design §16.7): a graceful terminal never reached the terminal stage,
334
+ * so `terminal_disposition` and `record_stage === "completed"` are mutually exclusive. Enforced at
335
+ * the write authority ({@link assembleReconstructRecord}) so no consumer can observe an incoherent
336
+ * pairing that {@link reconstructTerminalStatus} could not sensibly project.
337
+ */
338
+ export function assertReconstructTerminalDispositionCoherent(recordStage, terminalDisposition) {
339
+ if (terminalDisposition && recordStage === "completed") {
340
+ throw new Error(`reconstruct record invariant: terminal_disposition=${terminalDisposition} cannot pair with record_stage="completed"`);
341
+ }
342
+ }
309
343
  export async function assembleReconstructRecord(params) {
310
344
  const sessionRoot = path.resolve(params.sessionRoot);
311
345
  const sessionId = path.basename(sessionRoot);
@@ -380,17 +414,25 @@ export async function assembleReconstructRecord(params) {
380
414
  stopDecisionPresent: await exists(artifactRefs.stop_decision),
381
415
  finalOutputPresent: await exists(artifactRefs.final_output),
382
416
  });
417
+ assertReconstructTerminalDispositionCoherent(recordStage, params.terminalDisposition);
383
418
  const record = {
384
419
  schema_version: "1",
385
420
  reconstruct_record_id: `reconstruct-record:${sessionId}`,
386
421
  session_id: sessionId,
387
422
  entrypoint: "reconstruct",
388
423
  record_stage: recordStage,
424
+ // Present only on a graceful terminal; conditional spread keeps a normal record byte-identical.
425
+ ...(params.terminalDisposition
426
+ ? { terminal_disposition: params.terminalDisposition }
427
+ : {}),
389
428
  created_at: now,
390
429
  updated_at: now,
391
430
  target_material_kind: targetMaterialProfile?.target_material_kind ?? null,
392
431
  support_status: targetMaterialProfile?.support_status ?? null,
393
432
  artifact_refs: artifactRefs,
433
+ ...(params.dispatchFallback && recordStage === "completed"
434
+ ? { dispatch_fallback: structuredClone(params.dispatchFallback) }
435
+ : {}),
394
436
  artifact_integrity: artifactIntegrity,
395
437
  validation_summary: {
396
438
  target_material_profile_status: targetMaterialProfileStatus,
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { parse as parseYaml } from "yaml";
5
5
  import { assertArrayField, atomicWriteYamlDocument as writeYamlDocument } from "../artifact-io.js";
6
+ import { assertObligation } from "./obligation-assertion.js";
6
7
  import { loadReconstructContractRegistry, } from "./contract-registry.js";
7
8
  function isoNow() {
8
9
  return new Date().toISOString();
@@ -157,6 +158,20 @@ export function validateRegistryVerificationEvidence(args) {
157
158
  assertArrayField(args.contractRegistry.validation_gate_catalog, "contract-registry", "validation_gate_catalog");
158
159
  assertArrayField(args.contractRegistry.required_when_predicate_catalog, "contract-registry", "required_when_predicate_catalog");
159
160
  const violations = [];
161
+ // G(a) obligation recorder (INV-OBLIGATION-COVERAGE-1): record that control
162
+ // reached each enforcer block below. Unconditional, before any per-row/per-loop
163
+ // guard so a zero-row clean input still stamps every recorded obligation.
164
+ // (validate_registry_snapshot_hash_matches_current_registry_file is PARKED: the
165
+ // match check is gated on the caller-supplied optional expectedRegistrySha256,
166
+ // not enforced internally — see obligation-coverage-ledger.yaml.)
167
+ const assertedObligationIds = [];
168
+ assertObligation(assertedObligationIds, "validate_registry_snapshot_hash_is_recorded");
169
+ assertObligation(assertedObligationIds, "validate_active_artifact_gate_validator_predicate_and_source_profile_ids_are_unique");
170
+ assertObligation(assertedObligationIds, "validate_active_registry_subject_lists_match_current_registry_catalogs");
171
+ assertObligation(assertedObligationIds, "validate_every_active_gate_has_a_validator_record");
172
+ assertObligation(assertedObligationIds, "validate_every_validator_gate_ref_resolves_to_an_active_gate");
173
+ assertObligation(assertedObligationIds, "validate_every_active_gate_required_when_predicate_resolves");
174
+ assertObligation(assertedObligationIds, "require_evidence_row_for_each_current_registry_subject_id");
160
175
  const expectedRegistryRef = args.expectedRegistryRef
161
176
  ? path.resolve(args.expectedRegistryRef)
162
177
  : null;
@@ -353,6 +368,7 @@ export function validateRegistryVerificationEvidence(args) {
353
368
  validation_results: violations.length === 0
354
369
  ? ["registry_verification_evidence_valid"]
355
370
  : ["registry_verification_evidence_invalid"],
371
+ asserted_obligation_ids: assertedObligationIds,
356
372
  violations,
357
373
  };
358
374
  }