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.
- package/.onto/authority/core-lexicon.yaml +1 -1
- package/.onto/authority/supported-models.yaml +137 -0
- package/.onto/domains/accounting-kr/competency_qs.md +153 -153
- package/.onto/processes/reconstruct/obligation-coverage-ledger.yaml +842 -0
- package/.onto/processes/reconstruct/obligation-coverage-recorded.yaml +225 -0
- package/.onto/processes/reconstruct/ontology-seeding-and-maturation-design.md +93 -54
- package/.onto/processes/reconstruct/reconstruct-contract-registry.yaml +169 -8
- package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +72 -1
- package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +35 -0
- package/.onto/processes/review/productized-live-path.md +9 -5
- package/.onto/processes/review/prompt-execution-runner-contract.md +32 -8
- package/.onto/processes/review/review-target-profile-contract.md +47 -4
- package/.onto/processes/shared/pipeline-execution-ledger-contract.md +11 -0
- package/.onto/processes/shared/target-material-kind-contract.md +1 -1
- package/README.md +64 -1
- package/dist/cli.js +11 -1
- package/dist/core-api/reconstruct-api.js +575 -24
- package/dist/core-api/review-api.js +70 -5
- package/dist/core-api/review-progress.js +7 -0
- package/dist/core-api/runtime-observation.js +7 -0
- package/dist/core-runtime/artifact-io.js +75 -3
- package/dist/core-runtime/cli/claude-code-review-unit-executor.js +19 -8
- package/dist/core-runtime/cli/materialize-review-prompt-packets.js +68 -14
- package/dist/core-runtime/cli/prepare-review-session.js +34 -1
- package/dist/core-runtime/cli/render-review-final-output.js +2 -1
- package/dist/core-runtime/cli/run-review-prompt-execution.js +758 -48
- package/dist/core-runtime/cli/run-seats.js +18 -0
- package/dist/core-runtime/cli/runtime-submit-context.js +10 -2
- package/dist/core-runtime/cli/unit-resubmit.js +269 -0
- package/dist/core-runtime/cli/worker-structured-output.js +80 -2
- package/dist/core-runtime/discovery/host-detection.js +71 -22
- package/dist/core-runtime/discovery/review-cert-assemble.js +208 -0
- package/dist/core-runtime/discovery/review-cert-record.js +442 -0
- package/dist/core-runtime/discovery/review-execution-units.js +22 -0
- package/dist/core-runtime/discovery/seat-inventory.js +167 -0
- package/dist/core-runtime/discovery/settings-chain.js +311 -52
- package/dist/core-runtime/discovery/supported-models.js +282 -29
- package/dist/core-runtime/discovery/synthesize-cert-assemble.js +197 -0
- package/dist/core-runtime/discovery/synthesize-cert-capsule.js +399 -0
- package/dist/core-runtime/discovery/synthesize-cert-judge.js +340 -0
- package/dist/core-runtime/discovery/synthesize-cert-loop.js +234 -0
- package/dist/core-runtime/discovery/synthesize-cert-mutation.js +117 -0
- package/dist/core-runtime/discovery/synthesize-cert-packet.js +182 -0
- package/dist/core-runtime/discovery/synthesize-cert-record.js +831 -0
- package/dist/core-runtime/discovery/synthesize-cert-sampler.js +336 -0
- package/dist/core-runtime/effort-calibration-ingest.js +135 -0
- package/dist/core-runtime/llm/dispatch-breaker.js +412 -0
- package/dist/core-runtime/llm/dispatch-fallback-adapter-capabilities.js +33 -0
- package/dist/core-runtime/llm/llm-caller.js +188 -47
- package/dist/core-runtime/llm/mock-llm-realization.js +35 -4
- package/dist/core-runtime/llm/model-switcher.js +1 -1
- package/dist/core-runtime/llm/openai-responses-incomplete-error.js +46 -0
- package/dist/core-runtime/llm/sealed-dispatch-capability.js +359 -0
- package/dist/core-runtime/llm/structured-dispatch-error.js +15 -0
- package/dist/core-runtime/onboard/claude-profile-scan.js +65 -0
- package/dist/core-runtime/onboard/cli-host.js +5 -2
- package/dist/core-runtime/onboard/configure-provider.js +33 -1
- package/dist/core-runtime/onboard/host-target.js +28 -3
- package/dist/core-runtime/onboard/register.js +54 -12
- package/dist/core-runtime/pipeline-execution-ledger.js +10 -0
- package/dist/core-runtime/reconstruct/artifact-types.js +35 -0
- package/dist/core-runtime/reconstruct/claim-projection-validation.js +17 -0
- package/dist/core-runtime/reconstruct/competency-projection-contract.js +63 -0
- package/dist/core-runtime/reconstruct/comprehension-artifact.js +321 -0
- package/dist/core-runtime/reconstruct/comprehension-reduce.js +367 -0
- package/dist/core-runtime/reconstruct/comprehension-semantic-map.js +771 -0
- package/dist/core-runtime/reconstruct/contract-registry.js +20 -0
- package/dist/core-runtime/reconstruct/dispatch-fallback-artifacts.js +640 -0
- package/dist/core-runtime/reconstruct/execution-telemetry.js +83 -9
- package/dist/core-runtime/reconstruct/final-output-sections.js +89 -0
- package/dist/core-runtime/reconstruct/governing-snapshot.js +36 -1
- package/dist/core-runtime/reconstruct/leaf-reader.js +305 -0
- package/dist/core-runtime/reconstruct/llm-dispatch-failure.js +270 -0
- package/dist/core-runtime/reconstruct/llm-touch-fingerprint.js +94 -0
- package/dist/core-runtime/reconstruct/material-admission-validation.js +18 -0
- package/dist/core-runtime/reconstruct/material-profile-validation.js +14 -0
- package/dist/core-runtime/reconstruct/materialize-preparation.js +304 -18
- package/dist/core-runtime/reconstruct/maturation-validation.js +1231 -53
- package/dist/core-runtime/reconstruct/mock-llm-realization.js +107 -3
- package/dist/core-runtime/reconstruct/obligation-assertion.js +18 -0
- package/dist/core-runtime/reconstruct/ontology-seed-validation.js +27 -0
- package/dist/core-runtime/reconstruct/output-budget.js +6 -0
- package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +87 -3
- package/dist/core-runtime/reconstruct/post-seed-validation.js +118 -5
- package/dist/core-runtime/reconstruct/purpose-authority-validation.js +117 -2
- package/dist/core-runtime/reconstruct/record.js +42 -0
- package/dist/core-runtime/reconstruct/registry-verification-validation.js +16 -0
- package/dist/core-runtime/reconstruct/run-control-validation.js +1011 -34
- package/dist/core-runtime/reconstruct/run.js +5198 -536
- package/dist/core-runtime/reconstruct/seed-authoring-readiness-validation.js +54 -3
- package/dist/core-runtime/reconstruct/semantic-quality-gate.js +105 -2
- package/dist/core-runtime/reconstruct/source-observation-delta-validation.js +41 -0
- package/dist/core-runtime/reconstruct/source-observations.js +166 -0
- package/dist/core-runtime/reconstruct/source-safety-validation.js +71 -183
- package/dist/core-runtime/reconstruct/source-scout-pack-validation.js +37 -1
- package/dist/core-runtime/reconstruct/terminal-validation.js +132 -2
- package/dist/core-runtime/review/continuation-plan.js +12 -5
- package/dist/core-runtime/review/issue-artifact-runtime.js +4 -1
- package/dist/core-runtime/review/materializers.js +100 -11
- package/dist/core-runtime/review/nesting-batch.js +6 -2
- package/dist/core-runtime/review/obligation-shardability.js +138 -0
- package/dist/core-runtime/review/pipeline-execution-ledger.js +42 -2
- package/dist/core-runtime/review/review-artifact-utils.js +217 -5
- package/dist/core-runtime/review/review-execution-steps.js +32 -8
- package/dist/core-runtime/review/review-progress-contract.js +8 -0
- package/dist/core-runtime/review/review-prompt-budget.js +97 -0
- package/dist/core-runtime/review/semantic-quality-gate.js +33 -7
- package/dist/core-runtime/review/spreadsheet-review-disposition.js +94 -0
- package/dist/core-runtime/route-identity.js +182 -0
- package/dist/core-runtime/spreadsheet-header-escalation.js +204 -0
- package/dist/core-runtime/spreadsheet-structure-observer.js +2689 -0
- package/dist/core-runtime/target-material-kind.js +58 -0
- package/dist/mcp/server.js +12 -5
- package/dist/tui/app.js +167 -0
- package/dist/tui/data/event-follower.js +116 -0
- package/dist/tui/data/node-detail.js +42 -0
- package/dist/tui/data/projection-poll.js +26 -0
- package/dist/tui/data/session-discovery.js +65 -0
- package/dist/tui/index.js +108 -0
- package/dist/tui/screens/log.js +25 -0
- package/dist/tui/screens/node-detail.js +17 -0
- package/dist/tui/screens/session-selector.js +26 -0
- package/dist/tui/screens/workflow-tree.js +90 -0
- package/dist/tui/view-model/reconstruct-adapter.js +162 -0
- package/dist/tui/view-model/review-adapter.js +270 -0
- package/dist/tui/view-model/tree-view-model.js +19 -0
- package/package.json +18 -3
- 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
|
-
|
|
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.
|
|
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
|
}
|