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.
- package/.onto/authority/core-lexicon.yaml +1 -1
- package/.onto/authority/supported-models.yaml +153 -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 +202 -49
- 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/nesting-batch-worker-contract.md +1 -1
- 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 +679 -18
- 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 +131 -0
- package/dist/core-runtime/cli/claude-code-review-unit-executor.js +21 -9
- package/dist/core-runtime/cli/claude-nesting-batch-worker.js +4 -2
- 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 +289 -25
- 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 +326 -0
- package/dist/core-runtime/effort-calibration-reconstruct.js +47 -0
- package/dist/core-runtime/effort-calibration-review.js +22 -0
- package/dist/core-runtime/effort-calibration-sweep.js +110 -0
- package/dist/core-runtime/effort-frontier.js +134 -0
- package/dist/core-runtime/llm/claude-bin.js +77 -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 +316 -35
- 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 +37 -0
- package/dist/core-runtime/reconstruct/benchmark-evidence.js +16 -0
- package/dist/core-runtime/reconstruct/claim-projection-validation.js +19 -5
- 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/directive-validation.js +4 -3
- package/dist/core-runtime/reconstruct/dispatch-fallback-artifacts.js +640 -0
- package/dist/core-runtime/reconstruct/execution-telemetry.js +84 -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 +20 -5
- package/dist/core-runtime/reconstruct/material-profile-validation.js +24 -6
- package/dist/core-runtime/reconstruct/materialize-preparation.js +380 -23
- package/dist/core-runtime/reconstruct/maturation-validation.js +1522 -67
- package/dist/core-runtime/reconstruct/mock-llm-realization.js +120 -1
- package/dist/core-runtime/reconstruct/obligation-assertion.js +18 -0
- package/dist/core-runtime/reconstruct/ontology-seed-validation.js +39 -7
- package/dist/core-runtime/reconstruct/output-budget.js +6 -0
- package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +112 -5
- package/dist/core-runtime/reconstruct/post-seed-validation.js +122 -11
- package/dist/core-runtime/reconstruct/proof-authority-validation.js +2 -6
- package/dist/core-runtime/reconstruct/purpose-authority-validation.js +119 -7
- package/dist/core-runtime/reconstruct/record.js +49 -3
- package/dist/core-runtime/reconstruct/registry-verification-validation.js +41 -5
- package/dist/core-runtime/reconstruct/run-control-validation.js +1019 -37
- package/dist/core-runtime/reconstruct/run.js +5409 -517
- package/dist/core-runtime/reconstruct/seed-authoring-readiness-validation.js +56 -8
- package/dist/core-runtime/reconstruct/semantic-quality-gate.js +112 -7
- package/dist/core-runtime/reconstruct/source-observation-delta-validation.js +53 -5
- package/dist/core-runtime/reconstruct/source-observations.js +166 -0
- package/dist/core-runtime/reconstruct/source-safety-validation.js +77 -188
- package/dist/core-runtime/reconstruct/source-scout-pack-validation.js +50 -6
- package/dist/core-runtime/reconstruct/terminal-validation.js +145 -13
- 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 +219 -7
- 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 +27 -5
- package/dist/mcp/tool-schemas.js +8 -0
- 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 +20 -3
- package/settings.example.json +77 -16
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { parse as parseYaml
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
4
|
+
import { atomicWriteYamlDocument as writeYamlDocument } from "../artifact-io.js";
|
|
5
|
+
import { assertObligation } from "./obligation-assertion.js";
|
|
4
6
|
import { ontologySeedClaimProjections, ontologySeedExcludedClaimIds, } from "./seed-claim-projections.js";
|
|
5
7
|
import { collectOntologySeedRefs } from "./ontology-seed-validation.js";
|
|
6
8
|
import { loadReconstructContractRegistry, } from "./contract-registry.js";
|
|
@@ -77,6 +79,16 @@ const REVISION_ACTIONS = [
|
|
|
77
79
|
"reject",
|
|
78
80
|
"defer",
|
|
79
81
|
];
|
|
82
|
+
// Shared revision-proposal predicates (M4: one blocker predicate used identically at the
|
|
83
|
+
// stop gate (run.ts) and the continuation gate (maturation-validation.ts); both import from
|
|
84
|
+
// here, the revision-proposal concept owner, so the two gates cannot drift). reject|defer
|
|
85
|
+
// block continuation/stop; every non-reuse proposal is disclosed.
|
|
86
|
+
export function isRevisionBlocker(proposal) {
|
|
87
|
+
return proposal.action === "reject" || proposal.action === "defer";
|
|
88
|
+
}
|
|
89
|
+
export function isRevisionDisclosed(proposal) {
|
|
90
|
+
return proposal.action !== "reuse";
|
|
91
|
+
}
|
|
80
92
|
function isoNow() {
|
|
81
93
|
return new Date().toISOString();
|
|
82
94
|
}
|
|
@@ -162,7 +174,7 @@ function seedLimitationIds(seed) {
|
|
|
162
174
|
.map((record) => stringField(record, "limitation_id"))
|
|
163
175
|
.filter((id) => id !== null));
|
|
164
176
|
}
|
|
165
|
-
function knownSeedRefs(seed) {
|
|
177
|
+
export function knownSeedRefs(seed) {
|
|
166
178
|
if (!seed)
|
|
167
179
|
return new Set();
|
|
168
180
|
const refs = collectOntologySeedRefs(seed);
|
|
@@ -563,6 +575,14 @@ function validateClaimRealizationMapAgainstClaims(args) {
|
|
|
563
575
|
const seen = new Set();
|
|
564
576
|
const stanceCounts = initCountMap(CLAIM_REALIZATION_STANCES);
|
|
565
577
|
const observations = observationsById(args.sourceObservations);
|
|
578
|
+
// G(a) slice 7: record the three claim-realization obligations before the per-realization loop so
|
|
579
|
+
// they are proven wired on a zero-realization map. Each was audited to a distinct enforcement region
|
|
580
|
+
// (no laundering): require_exactly_one = duplicate_id (in-loop) + missing_required_coverage (the
|
|
581
|
+
// coverage loop below); claim_ids = unknown_id; evidence_refs = validateEvidenceRef vs source obs.
|
|
582
|
+
const assertedObligationIds = [];
|
|
583
|
+
assertObligation(assertedObligationIds, "require_exactly_one_realization_for_each_seed_claim");
|
|
584
|
+
assertObligation(assertedObligationIds, "validate_realization_claim_ids_against_ontology_seed_claim_ids");
|
|
585
|
+
assertObligation(assertedObligationIds, "validate_realization_evidence_refs_against_source_observations");
|
|
566
586
|
for (const realization of args.claimRealizationMap.claim_realizations) {
|
|
567
587
|
const subjectId = realization.claim_id;
|
|
568
588
|
if (seen.has(subjectId)) {
|
|
@@ -635,6 +655,7 @@ function validateClaimRealizationMapAgainstClaims(args) {
|
|
|
635
655
|
validation_results: violations.length === 0
|
|
636
656
|
? ["claim_realization_map_valid"]
|
|
637
657
|
: ["claim_realization_map_invalid"],
|
|
658
|
+
asserted_obligation_ids: assertedObligationIds,
|
|
638
659
|
violations,
|
|
639
660
|
};
|
|
640
661
|
}
|
|
@@ -890,6 +911,41 @@ export function validateCompetencyQuestionsForOntologySeed(args) {
|
|
|
890
911
|
}
|
|
891
912
|
function validateCompetencyQuestionsAgainstEligibleClaims(args) {
|
|
892
913
|
const violations = [];
|
|
914
|
+
// G(a) slice 27: record the 9 obligations this validator FULLY enforces with a distinct,
|
|
915
|
+
// name-matching site. These are the REF-RESOLUTION obligations ("validate X refs against/close-to
|
|
916
|
+
// authority Y" — every PRESENT ref must resolve to its registry/seed/manifest authority), plus
|
|
917
|
+
// question-id uniqueness and the exactly-one admitted-competency disposition rule. Authority-backed
|
|
918
|
+
// stamps are GATED on the authoritative input being supplied (codex #142 R1): the thin
|
|
919
|
+
// validateCompetencyQuestions path passes no registry/seed/governing-snapshot, so a zero-question
|
|
920
|
+
// artifact must NOT claim those obligations were enforced against an absent authority. Each stamp is
|
|
921
|
+
// before the per-question loop so a zero-question set still records once the authority is present; the
|
|
922
|
+
// enforcement code always runs, only the stamp is gated.
|
|
923
|
+
// Parked (see obligation-coverage-ledger.yaml notes): emit_required_evidence_scope (PROJECTION_ONLY);
|
|
924
|
+
// classification_level_axis / hidden_label facet coverage (NOT_FOUND — fields absent); p3-diagnostic
|
|
925
|
+
// (no code); query/visualization/graph refs (presence-only — codex #142 R1); and — codex #142 R2 — the
|
|
926
|
+
// 8 *_facet_coverage obligations (presence-only: resolve present facets but never require facets when
|
|
927
|
+
// the axis is applicable) and validate_required_ontology_handoff_axis_coverage_or_limitation_backed_
|
|
928
|
+
// disposition (counts inactive/unsupported questions as coverage; no active/limitation-backed filter).
|
|
929
|
+
const assertedObligationIds = [];
|
|
930
|
+
// Pure question-set check — enforceable with no external authority.
|
|
931
|
+
assertObligation(assertedObligationIds, "require_unique_question_id");
|
|
932
|
+
// Registry-backed ref resolution — only when the contract registry authority is supplied.
|
|
933
|
+
if (args.contractRegistry) {
|
|
934
|
+
assertObligation(assertedObligationIds, "validate_coverage_axes_against_coverage_axis_registry");
|
|
935
|
+
assertObligation(assertedObligationIds, "validate_ontology_handoff_axes_against_ontology_handoff_axis_registry");
|
|
936
|
+
assertObligation(assertedObligationIds, "validate_reference_standard_refs_against_reference_standard_registry");
|
|
937
|
+
assertObligation(assertedObligationIds, "validate_pattern_catalog_refs_against_reference_pattern_catalog_registry");
|
|
938
|
+
}
|
|
939
|
+
// Seed-backed closure — only when the ontology seed authority is supplied.
|
|
940
|
+
if (args.ontologySeed) {
|
|
941
|
+
assertObligation(assertedObligationIds, "validate_seed_ref_refs_close_against_known_seed_refs");
|
|
942
|
+
assertObligation(assertedObligationIds, "validate_limitation_refs_close_against_handoff_limitations");
|
|
943
|
+
}
|
|
944
|
+
// Run-manifest governing-snapshot authority — only when the snapshot is supplied.
|
|
945
|
+
if (args.governingSnapshot) {
|
|
946
|
+
assertObligation(assertedObligationIds, "validate_domain_competency_trace_refs_against_run_manifest_context_snapshot");
|
|
947
|
+
assertObligation(assertedObligationIds, "enforce_admitted_domain_competency_disposition_rule");
|
|
948
|
+
}
|
|
893
949
|
const normalizedCompetencyQuestions = normalizeCompetencyQuestionsAtBoundary(args.competencyQuestions);
|
|
894
950
|
violations.push(...normalizedCompetencyQuestions.violations);
|
|
895
951
|
const eligibleClaims = new Set(args.eligibleClaimIds);
|
|
@@ -1274,11 +1330,36 @@ function validateCompetencyQuestionsAgainstEligibleClaims(args) {
|
|
|
1274
1330
|
validation_results: violations.length === 0
|
|
1275
1331
|
? ["competency_questions_valid"]
|
|
1276
1332
|
: ["competency_questions_invalid"],
|
|
1333
|
+
asserted_obligation_ids: assertedObligationIds,
|
|
1277
1334
|
violations,
|
|
1278
1335
|
};
|
|
1279
1336
|
}
|
|
1280
1337
|
export function validateCompetencyQuestionAssessment(args) {
|
|
1281
1338
|
const violations = [];
|
|
1339
|
+
// G(a) slice 12: record the three obligations with a distinct, name-matching enforcement site,
|
|
1340
|
+
// stamped unconditionally before the per-assessment / per-question loops so they fire on a
|
|
1341
|
+
// zero-assessment artifact:
|
|
1342
|
+
// - require_exactly_one_assessment_per_authoritative_question (duplicate_id on a repeat +
|
|
1343
|
+
// missing_required_coverage when a question has no assessment → exactly one).
|
|
1344
|
+
// - validate_downstream_effect_consistent_with_answer_status (downstream_effect must equal
|
|
1345
|
+
// expectedDownstreamEffect(answer_status): invalid_enum).
|
|
1346
|
+
// - validate_required_seed_refs_close_against_question_seed_refs (bidirectional closure:
|
|
1347
|
+
// unknown_id for a ref outside the question, missing_required_coverage for a missing one).
|
|
1348
|
+
// PARKED (not recorded), name broader than code — honest declared≠wired, NOT laundered (see
|
|
1349
|
+
// obligation-coverage-ledger.yaml notes):
|
|
1350
|
+
// - validate_answer_status_against_active_answerability_contract: the validator checks enum
|
|
1351
|
+
// validity + per-status STRUCTURAL requirements (answerable→evidence_refs/required_seed_refs,
|
|
1352
|
+
// unsupported/deferred→missing_source_or_confirmation), but never verifies the answer_status
|
|
1353
|
+
// reflects actual answerability from evidence CONTENT — the answerability judgment is the
|
|
1354
|
+
// content-blind host-LLM step, not validated here.
|
|
1355
|
+
// - validate_answerability_trace_refs_close_against_seed_evidence_limitations_and_proofs: there
|
|
1356
|
+
// is no answerability_trace_refs / limitation_refs / proof_refs field; the validator closes
|
|
1357
|
+
// only evidence_refs and required_seed_refs (the latter already recorded above), never
|
|
1358
|
+
// limitations or proofs.
|
|
1359
|
+
const assertedObligationIds = [];
|
|
1360
|
+
assertObligation(assertedObligationIds, "require_exactly_one_assessment_per_authoritative_question");
|
|
1361
|
+
assertObligation(assertedObligationIds, "validate_downstream_effect_consistent_with_answer_status");
|
|
1362
|
+
assertObligation(assertedObligationIds, "validate_required_seed_refs_close_against_question_seed_refs");
|
|
1282
1363
|
const questionIds = new Set(args.competencyQuestions.questions.map((question) => question.question_id));
|
|
1283
1364
|
const questionById = new Map(args.competencyQuestions.questions.map((question) => [
|
|
1284
1365
|
question.question_id,
|
|
@@ -1453,6 +1534,7 @@ export function validateCompetencyQuestionAssessment(args) {
|
|
|
1453
1534
|
validation_results: violations.length === 0
|
|
1454
1535
|
? ["competency_question_assessment_valid"]
|
|
1455
1536
|
: ["competency_question_assessment_invalid"],
|
|
1537
|
+
asserted_obligation_ids: assertedObligationIds,
|
|
1456
1538
|
violations,
|
|
1457
1539
|
};
|
|
1458
1540
|
}
|
|
@@ -1530,7 +1612,25 @@ export function validateFailureClassification(args) {
|
|
|
1530
1612
|
}
|
|
1531
1613
|
export function validateRevisionProposal(args) {
|
|
1532
1614
|
const violations = [];
|
|
1615
|
+
// Every published proposal is carry-forward scope, so its target must resolve
|
|
1616
|
+
// to a real authority — not just the `failure` case. Claim/question targets
|
|
1617
|
+
// come from the failures' own claim_id/question_id; seed targets from the
|
|
1618
|
+
// ontology-seed's seed_refs (knownSeedRefs). An unresolved target is a
|
|
1619
|
+
// fabricated/stale carry-forward and is rejected as unknown_id.
|
|
1533
1620
|
const failureIds = new Set(args.failureClassification.failures.map((failure) => failure.failure_id));
|
|
1621
|
+
const failureClaimIds = new Set(args.failureClassification.failures
|
|
1622
|
+
.map((failure) => failure.claim_id)
|
|
1623
|
+
.filter((claimId) => Boolean(claimId)));
|
|
1624
|
+
const failureQuestionIds = new Set(args.failureClassification.failures
|
|
1625
|
+
.map((failure) => failure.question_id)
|
|
1626
|
+
.filter((questionId) => Boolean(questionId)));
|
|
1627
|
+
const seedRefs = knownSeedRefs(args.ontologySeed);
|
|
1628
|
+
const targetAuthorities = {
|
|
1629
|
+
failure: { ids: failureIds, authority: "failure" },
|
|
1630
|
+
claim: { ids: failureClaimIds, authority: "failure claim" },
|
|
1631
|
+
question: { ids: failureQuestionIds, authority: "failure question" },
|
|
1632
|
+
seed: { ids: seedRefs, authority: "ontology-seed seed_ref" },
|
|
1633
|
+
};
|
|
1534
1634
|
const seen = new Set();
|
|
1535
1635
|
const actionCounts = initCountMap(REVISION_ACTIONS);
|
|
1536
1636
|
for (const proposal of args.revisionProposal.proposals) {
|
|
@@ -1542,11 +1642,23 @@ export function validateRevisionProposal(args) {
|
|
|
1542
1642
|
}));
|
|
1543
1643
|
}
|
|
1544
1644
|
seen.add(proposal.proposal_id);
|
|
1545
|
-
|
|
1546
|
-
|
|
1645
|
+
// target_type is the typed union for fresh-authored proposals, but resume /
|
|
1646
|
+
// manual artifact validation loads it from YAML, so a stale value (e.g. the
|
|
1647
|
+
// removed domain_context) can reach here. An unknown type has no authority to
|
|
1648
|
+
// validate against, so reject it as invalid_enum instead of skipping the
|
|
1649
|
+
// authority check and disclosing an unbacked target as carry-forward.
|
|
1650
|
+
const targetAuthority = targetAuthorities[proposal.target_type];
|
|
1651
|
+
if (!targetAuthority) {
|
|
1652
|
+
violations.push(violation({
|
|
1653
|
+
code: "invalid_enum",
|
|
1654
|
+
message: `invalid proposal target_type: ${proposal.target_type}`,
|
|
1655
|
+
subjectId: proposal.proposal_id,
|
|
1656
|
+
}));
|
|
1657
|
+
}
|
|
1658
|
+
else if (!targetAuthority.ids.has(proposal.target_id)) {
|
|
1547
1659
|
violations.push(violation({
|
|
1548
1660
|
code: "unknown_id",
|
|
1549
|
-
message: `proposal references unknown
|
|
1661
|
+
message: `proposal references unknown ${targetAuthority.authority}: ${proposal.target_id}`,
|
|
1550
1662
|
subjectId: proposal.proposal_id,
|
|
1551
1663
|
}));
|
|
1552
1664
|
}
|
|
@@ -1626,10 +1738,6 @@ export function validateFinalOutputProvenance(args) {
|
|
|
1626
1738
|
}
|
|
1627
1739
|
return violations;
|
|
1628
1740
|
}
|
|
1629
|
-
async function writeYamlDocument(filePath, value) {
|
|
1630
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
1631
|
-
await fs.writeFile(filePath, stringifyYaml(value), "utf8");
|
|
1632
|
-
}
|
|
1633
1741
|
async function readYamlDocument(filePath) {
|
|
1634
1742
|
return parseYaml(await fs.readFile(filePath, "utf8"));
|
|
1635
1743
|
}
|
|
@@ -1690,7 +1798,8 @@ export async function writeCompetencyQuestionsValidationForOntologySeedArtifact(
|
|
|
1690
1798
|
readYamlDocument(args.ontologySeedPath),
|
|
1691
1799
|
readYamlDocument(args.ontologySeedValidationPath),
|
|
1692
1800
|
readYamlDocument(args.sourceObservationsPath),
|
|
1693
|
-
|
|
1801
|
+
args.contractRegistry ??
|
|
1802
|
+
loadReconstructContractRegistry({ registryPath: args.registryPath }),
|
|
1694
1803
|
]);
|
|
1695
1804
|
const manifest = args.governingSnapshot || !args.reconstructRunManifestPath
|
|
1696
1805
|
? null
|
|
@@ -1752,13 +1861,15 @@ export async function writeFailureClassificationValidationArtifact(args) {
|
|
|
1752
1861
|
return validation;
|
|
1753
1862
|
}
|
|
1754
1863
|
export async function writeRevisionProposalValidationArtifact(args) {
|
|
1755
|
-
const [revisionProposal, failureClassification] = await Promise.all([
|
|
1864
|
+
const [revisionProposal, failureClassification, ontologySeed] = await Promise.all([
|
|
1756
1865
|
readYamlDocument(args.revisionProposalPath),
|
|
1757
1866
|
readYamlDocument(args.failureClassificationPath),
|
|
1867
|
+
readYamlDocument(args.ontologySeedPath),
|
|
1758
1868
|
]);
|
|
1759
1869
|
const validation = validateRevisionProposal({
|
|
1760
1870
|
revisionProposal,
|
|
1761
1871
|
failureClassification,
|
|
1872
|
+
ontologySeed,
|
|
1762
1873
|
revisionProposalRef: path.resolve(args.revisionProposalPath),
|
|
1763
1874
|
failureClassificationRef: path.resolve(args.failureClassificationPath),
|
|
1764
1875
|
});
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import { parse as parseYaml } from "yaml";
|
|
3
|
+
import { atomicWriteYamlDocument as writeYamlDocument } from "../artifact-io.js";
|
|
4
4
|
function isoNow() {
|
|
5
5
|
return new Date().toISOString();
|
|
6
6
|
}
|
|
7
7
|
async function readYamlDocument(filePath) {
|
|
8
8
|
return parseYaml(await fs.readFile(filePath, "utf8"));
|
|
9
9
|
}
|
|
10
|
-
async function writeYamlDocument(filePath, value) {
|
|
11
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
12
|
-
await fs.writeFile(filePath, stringifyYaml(value), "utf8");
|
|
13
|
-
}
|
|
14
10
|
function violation(args) {
|
|
15
11
|
return {
|
|
16
12
|
code: args.code,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { parse as parseYaml
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
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";
|
|
4
7
|
const PURPOSE_EVIDENCE_KINDS = [
|
|
5
8
|
"P1",
|
|
6
9
|
"P2",
|
|
@@ -8,16 +11,18 @@ const PURPOSE_EVIDENCE_KINDS = [
|
|
|
8
11
|
"P4",
|
|
9
12
|
"P5",
|
|
10
13
|
];
|
|
14
|
+
const PURPOSE_FRAME_STATUSES = [
|
|
15
|
+
"source_declared",
|
|
16
|
+
"evidence_inferred",
|
|
17
|
+
"limitation_backed",
|
|
18
|
+
"unresolved",
|
|
19
|
+
];
|
|
11
20
|
function isoNow() {
|
|
12
21
|
return new Date().toISOString();
|
|
13
22
|
}
|
|
14
23
|
async function readYamlDocument(filePath) {
|
|
15
24
|
return parseYaml(await fs.readFile(filePath, "utf8"));
|
|
16
25
|
}
|
|
17
|
-
async function writeYamlDocument(filePath, value) {
|
|
18
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
19
|
-
await fs.writeFile(filePath, stringifyYaml(value), "utf8");
|
|
20
|
-
}
|
|
21
26
|
function isRecord(value) {
|
|
22
27
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23
28
|
}
|
|
@@ -96,6 +101,22 @@ function hasNonEmptyString(value) {
|
|
|
96
101
|
export function validateSourcePurposeCandidates(args) {
|
|
97
102
|
const artifact = args.sourcePurposeCandidates;
|
|
98
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");
|
|
99
120
|
const evidenceIndex = observationEvidenceIndex(args.sourceObservations);
|
|
100
121
|
const rawArtifact = artifact;
|
|
101
122
|
if ("source_purpose_status" in rawArtifact || "inference_status" in rawArtifact) {
|
|
@@ -220,12 +241,74 @@ export function validateSourcePurposeCandidates(args) {
|
|
|
220
241
|
subjectId: candidate.purpose_candidate_id,
|
|
221
242
|
}));
|
|
222
243
|
}
|
|
223
|
-
|
|
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) {
|
|
224
265
|
violations.push(violation({
|
|
225
266
|
code: "required_element_missing",
|
|
226
|
-
message: `${candidatePath}.adequacy_frame.
|
|
267
|
+
message: `${candidatePath}.adequacy_frame.material_kind_requirements is required`,
|
|
227
268
|
subjectId: candidate.purpose_candidate_id,
|
|
228
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
|
+
}
|
|
229
312
|
continue;
|
|
230
313
|
}
|
|
231
314
|
for (const [elementIndex, element] of frame.required_elements.entries()) {
|
|
@@ -292,6 +375,7 @@ export function validateSourcePurposeCandidates(args) {
|
|
|
292
375
|
validation_results: violations.length === 0
|
|
293
376
|
? ["source_purpose_candidates_valid"]
|
|
294
377
|
: ["source_purpose_candidates_invalid"],
|
|
378
|
+
asserted_obligation_ids: assertedObligationIds,
|
|
295
379
|
violations,
|
|
296
380
|
};
|
|
297
381
|
}
|
|
@@ -299,6 +383,33 @@ export function validatePurposeConfirmation(args) {
|
|
|
299
383
|
const confirmation = args.purposeConfirmation;
|
|
300
384
|
const sourceValidation = args.sourcePurposeCandidatesValidation;
|
|
301
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");
|
|
302
413
|
if (confirmation.session_id !== sourceValidation.session_id) {
|
|
303
414
|
violations.push(violation({
|
|
304
415
|
code: "session_id_mismatch",
|
|
@@ -387,6 +498,7 @@ export function validatePurposeConfirmation(args) {
|
|
|
387
498
|
validation_results: violations.length === 0
|
|
388
499
|
? ["purpose_confirmation_valid"]
|
|
389
500
|
: ["purpose_confirmation_invalid"],
|
|
501
|
+
asserted_obligation_ids: assertedObligationIds,
|
|
390
502
|
violations,
|
|
391
503
|
};
|
|
392
504
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { parse as parseYaml
|
|
4
|
+
import { parse as parseYaml } from "yaml";
|
|
5
|
+
import { atomicWriteYamlDocument } from "../artifact-io.js";
|
|
5
6
|
const RECORD_ARTIFACT_KEYS = [
|
|
6
7
|
"reconstruct_run_control",
|
|
7
8
|
"reconstruct_run_control_validation",
|
|
@@ -14,11 +15,21 @@ const RECORD_ARTIFACT_KEYS = [
|
|
|
14
15
|
"source_inventory",
|
|
15
16
|
"initial_source_frontier",
|
|
16
17
|
"source_observations",
|
|
18
|
+
"seed_stage_prompt_source_observations",
|
|
17
19
|
"source_observation_delta",
|
|
18
20
|
"source_observation_delta_validation",
|
|
19
21
|
"source_observation_reentry_validation",
|
|
20
22
|
"source_observation_lineage_index",
|
|
21
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",
|
|
22
33
|
"source_safety_ledger",
|
|
23
34
|
"source_safety_ledger_validation",
|
|
24
35
|
"source_scout_pack",
|
|
@@ -78,6 +89,8 @@ const RECORD_ARTIFACT_KEYS = [
|
|
|
78
89
|
"maturation_authority_response_validation",
|
|
79
90
|
"answer_support_ledger",
|
|
80
91
|
"answer_support_ledger_validation",
|
|
92
|
+
"answer_support_judgment",
|
|
93
|
+
"answer_support_judgment_validation",
|
|
81
94
|
"maturation_answer_claims",
|
|
82
95
|
"maturation_answer_claims_validation",
|
|
83
96
|
"ontology_expansion",
|
|
@@ -303,6 +316,30 @@ function buildWarnings(args) {
|
|
|
303
316
|
}
|
|
304
317
|
return warnings;
|
|
305
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
|
+
}
|
|
306
343
|
export async function assembleReconstructRecord(params) {
|
|
307
344
|
const sessionRoot = path.resolve(params.sessionRoot);
|
|
308
345
|
const sessionId = path.basename(sessionRoot);
|
|
@@ -377,17 +414,25 @@ export async function assembleReconstructRecord(params) {
|
|
|
377
414
|
stopDecisionPresent: await exists(artifactRefs.stop_decision),
|
|
378
415
|
finalOutputPresent: await exists(artifactRefs.final_output),
|
|
379
416
|
});
|
|
417
|
+
assertReconstructTerminalDispositionCoherent(recordStage, params.terminalDisposition);
|
|
380
418
|
const record = {
|
|
381
419
|
schema_version: "1",
|
|
382
420
|
reconstruct_record_id: `reconstruct-record:${sessionId}`,
|
|
383
421
|
session_id: sessionId,
|
|
384
422
|
entrypoint: "reconstruct",
|
|
385
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
|
+
: {}),
|
|
386
428
|
created_at: now,
|
|
387
429
|
updated_at: now,
|
|
388
430
|
target_material_kind: targetMaterialProfile?.target_material_kind ?? null,
|
|
389
431
|
support_status: targetMaterialProfile?.support_status ?? null,
|
|
390
432
|
artifact_refs: artifactRefs,
|
|
433
|
+
...(params.dispatchFallback && recordStage === "completed"
|
|
434
|
+
? { dispatch_fallback: structuredClone(params.dispatchFallback) }
|
|
435
|
+
: {}),
|
|
391
436
|
artifact_integrity: artifactIntegrity,
|
|
392
437
|
validation_summary: {
|
|
393
438
|
target_material_profile_status: targetMaterialProfileStatus,
|
|
@@ -494,6 +539,7 @@ export async function assembleReconstructRecord(params) {
|
|
|
494
539
|
"maturation_authority_response",
|
|
495
540
|
"maturation_authority_response_validation",
|
|
496
541
|
"answer_support_ledger_validation",
|
|
542
|
+
"answer_support_judgment_validation",
|
|
497
543
|
"maturation_answer_claims_validation",
|
|
498
544
|
"ontology_expansion_validation",
|
|
499
545
|
"maturation_source_delta",
|
|
@@ -536,6 +582,7 @@ export async function assembleReconstructRecord(params) {
|
|
|
536
582
|
"maturation_question_frontier",
|
|
537
583
|
"maturation_closure_frontier",
|
|
538
584
|
"answer_support_ledger",
|
|
585
|
+
"answer_support_judgment",
|
|
539
586
|
"maturation_answer_claims",
|
|
540
587
|
"ontology_expansion",
|
|
541
588
|
"final_output",
|
|
@@ -556,7 +603,6 @@ export async function assembleReconstructRecord(params) {
|
|
|
556
603
|
const outputPath = params.outputPath
|
|
557
604
|
? path.resolve(params.outputPath)
|
|
558
605
|
: path.join(sessionRoot, "reconstruct-record.yaml");
|
|
559
|
-
await
|
|
560
|
-
await fs.writeFile(outputPath, stringifyYaml(record), "utf8");
|
|
606
|
+
await atomicWriteYamlDocument(outputPath, record);
|
|
561
607
|
return record;
|
|
562
608
|
}
|