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