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
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { constants as fsConstants } from "node:fs";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { RECONSTRUCT_STAGE_IDS, } from "./artifact-types.js";
|
|
8
|
+
import { OPENAI_RESPONSES_MAX_OUTPUT_TOKENS_FAILURE_CODE, } from "../llm/openai-responses-incomplete-error.js";
|
|
9
|
+
export const RECONSTRUCT_LLM_DISPATCH_FAILURE_SCHEMA_VERSION = "reconstruct-llm-dispatch-failure/v1";
|
|
10
|
+
export const RECONSTRUCT_LLM_DISPATCH_FAILURE_DIR = "llm-dispatch-failures";
|
|
11
|
+
const ReconstructLlmCallKindSchema = z.enum([
|
|
12
|
+
"initial",
|
|
13
|
+
"parse_repair",
|
|
14
|
+
"semantic_repair",
|
|
15
|
+
"timeout_recovery",
|
|
16
|
+
]);
|
|
17
|
+
const NullableTokenCountSchema = z.number().int().nonnegative().nullable();
|
|
18
|
+
export const ReconstructLlmDispatchFailureArtifactSchema = z
|
|
19
|
+
.object({
|
|
20
|
+
schema_version: z.literal(RECONSTRUCT_LLM_DISPATCH_FAILURE_SCHEMA_VERSION),
|
|
21
|
+
failure_id: z.string().regex(/^llm-failure:[a-f0-9]{20}$/),
|
|
22
|
+
session_id: z.string().min(1),
|
|
23
|
+
created_at: z.string().min(1),
|
|
24
|
+
owner_attempt_id: z.string().min(1),
|
|
25
|
+
unit_id: z.enum(RECONSTRUCT_STAGE_IDS),
|
|
26
|
+
artifact_name: z.string().min(1),
|
|
27
|
+
call_kind: ReconstructLlmCallKindSchema,
|
|
28
|
+
failure_code: z.literal(OPENAI_RESPONSES_MAX_OUTPUT_TOKENS_FAILURE_CODE),
|
|
29
|
+
provider_status: z.literal("incomplete"),
|
|
30
|
+
incomplete_reason: z.literal("max_output_tokens"),
|
|
31
|
+
base_output_ceiling_tokens: z.number().int().positive(),
|
|
32
|
+
configured_output_headroom_tokens: z.number().int().nonnegative(),
|
|
33
|
+
effective_max_output_tokens: z.number().int().positive(),
|
|
34
|
+
input_tokens: NullableTokenCountSchema,
|
|
35
|
+
cached_input_tokens: NullableTokenCountSchema,
|
|
36
|
+
output_tokens: NullableTokenCountSchema,
|
|
37
|
+
reasoning_tokens: NullableTokenCountSchema,
|
|
38
|
+
non_reasoning_output_tokens: NullableTokenCountSchema,
|
|
39
|
+
partial_output_chars: z.number().int().nonnegative(),
|
|
40
|
+
partial_output_sha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
41
|
+
provider_model: z.string().min(1),
|
|
42
|
+
provider_response_id: z.string().min(1).nullable(),
|
|
43
|
+
provider_request_id: z.string().min(1).nullable(),
|
|
44
|
+
effective_base_url: z.string().min(1),
|
|
45
|
+
runtime_logical_call_count: z.literal(1),
|
|
46
|
+
runtime_incomplete_retry_count: z.literal(0),
|
|
47
|
+
sdk_max_retries: z.number().int().nonnegative(),
|
|
48
|
+
actual_adapter_request_count: z.null(),
|
|
49
|
+
request_count_observability: z.literal("unavailable"),
|
|
50
|
+
})
|
|
51
|
+
.strict();
|
|
52
|
+
export class ReconstructLlmDispatchFailureError extends Error {
|
|
53
|
+
unitId;
|
|
54
|
+
artifactName;
|
|
55
|
+
callKind;
|
|
56
|
+
evidence;
|
|
57
|
+
constructor(args) {
|
|
58
|
+
super(`reconstruct LLM dispatch failed for ${args.artifactName}: ` +
|
|
59
|
+
args.evidence.failure_code, { cause: args.cause });
|
|
60
|
+
this.name = "ReconstructLlmDispatchFailureError";
|
|
61
|
+
this.unitId = args.unitId;
|
|
62
|
+
this.artifactName = args.artifactName;
|
|
63
|
+
this.callKind = args.callKind;
|
|
64
|
+
this.evidence = args.evidence;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function readReconstructLlmDispatchFailureError(error) {
|
|
68
|
+
return error instanceof ReconstructLlmDispatchFailureError ? error : null;
|
|
69
|
+
}
|
|
70
|
+
function sha256(value) {
|
|
71
|
+
return crypto.createHash("sha256").update(value).digest("hex");
|
|
72
|
+
}
|
|
73
|
+
export function createReconstructLlmDispatchFailureArtifact(args) {
|
|
74
|
+
const failureId = `llm-failure:${sha256([
|
|
75
|
+
args.sessionId,
|
|
76
|
+
args.attemptId,
|
|
77
|
+
args.error.unitId,
|
|
78
|
+
args.error.artifactName,
|
|
79
|
+
args.error.callKind,
|
|
80
|
+
args.error.evidence.provider_response_id ?? "no-response-id",
|
|
81
|
+
].join(":")).slice(0, 20)}`;
|
|
82
|
+
return ReconstructLlmDispatchFailureArtifactSchema.parse({
|
|
83
|
+
schema_version: RECONSTRUCT_LLM_DISPATCH_FAILURE_SCHEMA_VERSION,
|
|
84
|
+
failure_id: failureId,
|
|
85
|
+
session_id: args.sessionId,
|
|
86
|
+
created_at: args.createdAt ?? new Date().toISOString(),
|
|
87
|
+
owner_attempt_id: args.attemptId,
|
|
88
|
+
unit_id: args.error.unitId,
|
|
89
|
+
artifact_name: args.error.artifactName,
|
|
90
|
+
call_kind: args.error.callKind,
|
|
91
|
+
...args.error.evidence,
|
|
92
|
+
runtime_logical_call_count: 1,
|
|
93
|
+
runtime_incomplete_retry_count: 0,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function failureFileName(failureId) {
|
|
97
|
+
if (!/^llm-failure:[a-f0-9]{20}$/.test(failureId)) {
|
|
98
|
+
throw new Error(`invalid reconstruct LLM dispatch failure id: ${failureId}`);
|
|
99
|
+
}
|
|
100
|
+
return `${failureId.replace(/^llm-failure:/, "failure-")}.yaml`;
|
|
101
|
+
}
|
|
102
|
+
export function reconstructLlmDispatchFailurePath(sessionRoot, failureId) {
|
|
103
|
+
return path.join(path.resolve(sessionRoot), RECONSTRUCT_LLM_DISPATCH_FAILURE_DIR, failureFileName(failureId));
|
|
104
|
+
}
|
|
105
|
+
export function isReconstructLlmDispatchFailureRef(sessionRoot, artifactRef) {
|
|
106
|
+
const root = path.resolve(sessionRoot, RECONSTRUCT_LLM_DISPATCH_FAILURE_DIR);
|
|
107
|
+
const resolved = path.resolve(artifactRef);
|
|
108
|
+
return path.dirname(resolved) === root &&
|
|
109
|
+
path.basename(resolved).startsWith("failure-") &&
|
|
110
|
+
path.extname(resolved) === ".yaml";
|
|
111
|
+
}
|
|
112
|
+
export function isReconstructLlmDispatchFailureTempRef(sessionRoot, artifactRef) {
|
|
113
|
+
const root = path.resolve(sessionRoot, RECONSTRUCT_LLM_DISPATCH_FAILURE_DIR);
|
|
114
|
+
const resolved = path.resolve(artifactRef);
|
|
115
|
+
return path.dirname(resolved) === root &&
|
|
116
|
+
path.basename(resolved).startsWith(".pending-") &&
|
|
117
|
+
path.extname(resolved) === ".yaml";
|
|
118
|
+
}
|
|
119
|
+
async function fsyncDirectory(dirPath) {
|
|
120
|
+
const handle = await fs.open(dirPath, fsConstants.O_RDONLY);
|
|
121
|
+
try {
|
|
122
|
+
await handle.sync();
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
await handle.close();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
export async function assertReconstructLlmDispatchFailureDirectory(sessionRoot) {
|
|
129
|
+
const lexicalRoot = path.resolve(sessionRoot);
|
|
130
|
+
const rootStat = await fs.lstat(lexicalRoot);
|
|
131
|
+
if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
|
|
132
|
+
throw new Error(`reconstruct session root is not a real directory: ${lexicalRoot}`);
|
|
133
|
+
}
|
|
134
|
+
const realRoot = await fs.realpath(lexicalRoot);
|
|
135
|
+
const dirPath = path.join(lexicalRoot, RECONSTRUCT_LLM_DISPATCH_FAILURE_DIR);
|
|
136
|
+
let created = false;
|
|
137
|
+
try {
|
|
138
|
+
await fs.mkdir(dirPath);
|
|
139
|
+
created = true;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (error.code !== "EEXIST")
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
const stat = await fs.lstat(dirPath);
|
|
146
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
147
|
+
throw new Error(`LLM dispatch failure directory is not a real directory: ${dirPath}`);
|
|
148
|
+
}
|
|
149
|
+
const realDir = await fs.realpath(dirPath);
|
|
150
|
+
if (path.dirname(realDir) !== realRoot) {
|
|
151
|
+
throw new Error(`LLM dispatch failure directory escapes session root: ${realDir}`);
|
|
152
|
+
}
|
|
153
|
+
if (created)
|
|
154
|
+
await fsyncDirectory(lexicalRoot);
|
|
155
|
+
return dirPath;
|
|
156
|
+
}
|
|
157
|
+
export async function planReconstructLlmDispatchFailureWrite(args) {
|
|
158
|
+
const artifact = ReconstructLlmDispatchFailureArtifactSchema.parse(args.artifact);
|
|
159
|
+
const dirPath = await assertReconstructLlmDispatchFailureDirectory(args.sessionRoot);
|
|
160
|
+
const contents = stringifyYaml(artifact);
|
|
161
|
+
const contentSha256 = sha256(contents);
|
|
162
|
+
return {
|
|
163
|
+
finalRef: path.join(dirPath, failureFileName(artifact.failure_id)),
|
|
164
|
+
tempRef: path.join(dirPath, `.pending-${contentSha256.slice(0, 16)}-${crypto.randomUUID()}.yaml`),
|
|
165
|
+
contentSha256,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
export async function writeReconstructLlmDispatchFailureTemp(args) {
|
|
169
|
+
const artifact = ReconstructLlmDispatchFailureArtifactSchema.parse(args.artifact);
|
|
170
|
+
const dirPath = await assertReconstructLlmDispatchFailureDirectory(args.sessionRoot);
|
|
171
|
+
const contents = stringifyYaml(artifact);
|
|
172
|
+
const contentSha256 = sha256(contents);
|
|
173
|
+
const plan = args.plan ?? await planReconstructLlmDispatchFailureWrite(args);
|
|
174
|
+
if (plan.contentSha256 !== contentSha256 ||
|
|
175
|
+
plan.finalRef !== path.join(dirPath, failureFileName(artifact.failure_id)) ||
|
|
176
|
+
!isReconstructLlmDispatchFailureTempRef(args.sessionRoot, plan.tempRef)) {
|
|
177
|
+
throw new Error("LLM dispatch failure write plan does not match artifact bytes");
|
|
178
|
+
}
|
|
179
|
+
const scratchRef = path.join(dirPath, `.scratch-${contentSha256.slice(0, 16)}-${crypto.randomUUID()}.yaml`);
|
|
180
|
+
const handle = await fs.open(scratchRef, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY | fsConstants.O_NOFOLLOW, 0o600);
|
|
181
|
+
try {
|
|
182
|
+
await handle.writeFile(contents, "utf8");
|
|
183
|
+
await handle.sync();
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
await handle.close();
|
|
187
|
+
}
|
|
188
|
+
await fs.rename(scratchRef, plan.tempRef);
|
|
189
|
+
await fsyncDirectory(dirPath);
|
|
190
|
+
return plan;
|
|
191
|
+
}
|
|
192
|
+
export async function publishReconstructLlmDispatchFailureTemp(args) {
|
|
193
|
+
const dirPath = await assertReconstructLlmDispatchFailureDirectory(args.sessionRoot);
|
|
194
|
+
if (!isReconstructLlmDispatchFailureTempRef(args.sessionRoot, args.tempRef) ||
|
|
195
|
+
!isReconstructLlmDispatchFailureRef(args.sessionRoot, args.finalRef) ||
|
|
196
|
+
path.dirname(path.resolve(args.tempRef)) !== dirPath ||
|
|
197
|
+
path.dirname(path.resolve(args.finalRef)) !== dirPath) {
|
|
198
|
+
throw new Error("LLM dispatch failure publish refs must stay inside the failure directory");
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
await fs.link(args.tempRef, args.finalRef);
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
const code = error.code;
|
|
205
|
+
if (code !== "EEXIST") {
|
|
206
|
+
if (code !== "ENOENT")
|
|
207
|
+
throw error;
|
|
208
|
+
const finalStat = await fs.lstat(args.finalRef).catch(() => null);
|
|
209
|
+
if (!finalStat?.isFile() || finalStat.isSymbolicLink())
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
await fs.rm(args.tempRef, { force: true });
|
|
214
|
+
await fsyncDirectory(dirPath);
|
|
215
|
+
}
|
|
216
|
+
async function readFailureBytesNoFollow(artifactRef) {
|
|
217
|
+
const handle = await fs.open(artifactRef, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
218
|
+
try {
|
|
219
|
+
const stat = await handle.stat();
|
|
220
|
+
if (!stat.isFile()) {
|
|
221
|
+
throw new Error(`LLM dispatch failure artifact is not a regular file: ${artifactRef}`);
|
|
222
|
+
}
|
|
223
|
+
return await handle.readFile();
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
await handle.close();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function parseFailureBytes(bytes) {
|
|
230
|
+
return ReconstructLlmDispatchFailureArtifactSchema.parse(parseYaml(bytes.toString("utf8")));
|
|
231
|
+
}
|
|
232
|
+
export async function readReconstructLlmDispatchFailureArtifactWithHash(args) {
|
|
233
|
+
await assertReconstructLlmDispatchFailureDirectory(args.sessionRoot);
|
|
234
|
+
if (!isReconstructLlmDispatchFailureRef(args.sessionRoot, args.artifactRef) &&
|
|
235
|
+
!(args.allowTemp === true &&
|
|
236
|
+
isReconstructLlmDispatchFailureTempRef(args.sessionRoot, args.artifactRef))) {
|
|
237
|
+
throw new Error(`LLM dispatch failure artifact ref escapes failure directory: ${args.artifactRef}`);
|
|
238
|
+
}
|
|
239
|
+
const bytes = await readFailureBytesNoFollow(args.artifactRef);
|
|
240
|
+
return {
|
|
241
|
+
artifact: parseFailureBytes(bytes),
|
|
242
|
+
sha256: sha256(bytes),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
export async function readReconstructLlmDispatchFailureArtifact(artifactRef) {
|
|
246
|
+
return parseFailureBytes(await readFailureBytesNoFollow(artifactRef));
|
|
247
|
+
}
|
|
248
|
+
export async function sha256ReconstructLlmDispatchFailureArtifact(artifactRef) {
|
|
249
|
+
return sha256(await readFailureBytesNoFollow(artifactRef));
|
|
250
|
+
}
|
|
251
|
+
export function projectReconstructLlmDispatchFailureSummary(artifact, artifactRef) {
|
|
252
|
+
return {
|
|
253
|
+
failure_code: artifact.failure_code,
|
|
254
|
+
unit_id: artifact.unit_id,
|
|
255
|
+
artifact_name: artifact.artifact_name,
|
|
256
|
+
provider_status: artifact.provider_status,
|
|
257
|
+
incomplete_reason: artifact.incomplete_reason,
|
|
258
|
+
base_output_ceiling_tokens: artifact.base_output_ceiling_tokens,
|
|
259
|
+
configured_output_headroom_tokens: artifact.configured_output_headroom_tokens,
|
|
260
|
+
effective_max_output_tokens: artifact.effective_max_output_tokens,
|
|
261
|
+
input_tokens: artifact.input_tokens,
|
|
262
|
+
cached_input_tokens: artifact.cached_input_tokens,
|
|
263
|
+
output_tokens: artifact.output_tokens,
|
|
264
|
+
reasoning_tokens: artifact.reasoning_tokens,
|
|
265
|
+
non_reasoning_output_tokens: artifact.non_reasoning_output_tokens,
|
|
266
|
+
actual_adapter_request_count: artifact.actual_adapter_request_count,
|
|
267
|
+
request_count_observability: artifact.request_count_observability,
|
|
268
|
+
failure_artifact_ref: artifactRef,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
function stableJson(value) {
|
|
3
|
+
if (Array.isArray(value))
|
|
4
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
5
|
+
if (value && typeof value === "object") {
|
|
6
|
+
return `{${Object.keys(value)
|
|
7
|
+
.sort()
|
|
8
|
+
.map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
|
|
9
|
+
.join(",")}}`;
|
|
10
|
+
}
|
|
11
|
+
return JSON.stringify(value) ?? "null";
|
|
12
|
+
}
|
|
13
|
+
function sha256Text(text) {
|
|
14
|
+
return createHash("sha256").update(text).digest("hex");
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Compute the staged non-circular fingerprint. The parameter types admit ONLY ⓐ + ⓑ — the in-epoch
|
|
18
|
+
* LLM output (ⓒ) has no slot, so it cannot reach the key by construction (§4.4 non-circular-key).
|
|
19
|
+
*/
|
|
20
|
+
export function llmTouchFingerprint(layer1, preExecution) {
|
|
21
|
+
const declaredClosure = [
|
|
22
|
+
// ⓐ
|
|
23
|
+
"content_sha256",
|
|
24
|
+
"adapter_version",
|
|
25
|
+
"value_tile_config",
|
|
26
|
+
"data_layer_caps",
|
|
27
|
+
// ⓑ
|
|
28
|
+
"leaf_reader_model_identity",
|
|
29
|
+
"execution_adapter",
|
|
30
|
+
"declared_billing_mode",
|
|
31
|
+
"reasoning_effort",
|
|
32
|
+
"leaf_prompt_sha256",
|
|
33
|
+
"schema_tool_version",
|
|
34
|
+
"comprehension_version",
|
|
35
|
+
"structure_leaf_trigger_config",
|
|
36
|
+
"read_set_logic_sha256",
|
|
37
|
+
];
|
|
38
|
+
const fingerprint_sha256 = sha256Text(stableJson({ layer1_pre_image: layer1, pre_execution_pre_image: preExecution }));
|
|
39
|
+
return {
|
|
40
|
+
fingerprint_sha256,
|
|
41
|
+
declared_llm_touch_dependency_closure: declaredClosure,
|
|
42
|
+
fingerprint_covers_declared_closure: true,
|
|
43
|
+
dependency_discovery_realization: "declared_closure_only",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* In-epoch LLM OUTPUT (ⓒ) field names that must NEVER appear inside a gating reuse key (§11 R3).
|
|
48
|
+
* If any of these is serialized into a seed digest, an LLM output would gate its own reuse (the
|
|
49
|
+
* self-gating circularity). The fingerprint VALUE (`epoch_fingerprint_contribution`) is allowed —
|
|
50
|
+
* it is the ⓐ+ⓑ digest, not ⓒ.
|
|
51
|
+
*/
|
|
52
|
+
export const LLM_TOUCH_IN_EPOCH_OUTPUT_FIELDS = [
|
|
53
|
+
"spine_claims",
|
|
54
|
+
"confidence_by_claim",
|
|
55
|
+
"limiting_witness",
|
|
56
|
+
"leaf_read_attempt",
|
|
57
|
+
"tentative_label",
|
|
58
|
+
// P1-C2-B′ §3: the captured role/note are LLM output too — guard them out of any gating key.
|
|
59
|
+
"semantic_role",
|
|
60
|
+
"captured_note",
|
|
61
|
+
// Layer-2 semantic-map ⓒ fields (wiring design 20260702 §5 F9): the denylist must learn each new
|
|
62
|
+
// LLM-output vocabulary or a future edit serializing a map/projection instance into a gating key
|
|
63
|
+
// passes fail-OPEN (the recursive guard only knows enumerated names).
|
|
64
|
+
"semantic_summary",
|
|
65
|
+
"semantic_boundaries",
|
|
66
|
+
"character_before",
|
|
67
|
+
"character_after",
|
|
68
|
+
"refuted_disclosure",
|
|
69
|
+
];
|
|
70
|
+
const IN_EPOCH_OUTPUT_SET = new Set(LLM_TOUCH_IN_EPOCH_OUTPUT_FIELDS);
|
|
71
|
+
/**
|
|
72
|
+
* Runtime non-circular validator (§11 R3): assert that a gating-key input (the object a seed reuse
|
|
73
|
+
* digest serializes — e.g. the source-observations reuse projection or the authored-artifact reuse
|
|
74
|
+
* match) contains NO in-epoch LLM output field. Scoped to the REAL seed digests, not just the
|
|
75
|
+
* fingerprint function (the gate's T3 finding: the type guard only protects llmTouchFingerprint).
|
|
76
|
+
* Throws on the first ⓒ key found so the leak fails closed at construction time.
|
|
77
|
+
*/
|
|
78
|
+
export function assertGatingKeyExcludesInEpochOutput(label, gatingKeyInput) {
|
|
79
|
+
const visit = (node, path) => {
|
|
80
|
+
if (Array.isArray(node)) {
|
|
81
|
+
node.forEach((item, i) => visit(item, `${path}[${i}]`));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (node && typeof node === "object") {
|
|
85
|
+
for (const key of Object.keys(node)) {
|
|
86
|
+
if (IN_EPOCH_OUTPUT_SET.has(key)) {
|
|
87
|
+
throw new Error(`${label}: gating reuse key must not contain in-epoch LLM output field '${key}' at ${path}.${key} (§11 R3 non-circular-key: an LLM output would gate its own reuse). Fold the llm_touch_fingerprint VALUE instead of the comprehension-artifact instance.`);
|
|
88
|
+
}
|
|
89
|
+
visit(node[key], `${path}.${key}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
visit(gatingKeyInput, label);
|
|
94
|
+
}
|
|
@@ -2,6 +2,7 @@ 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";
|
|
5
6
|
const ADMISSION_PHASES = [
|
|
6
7
|
"pre_seed_purpose_element",
|
|
7
8
|
"pre_seed_material_value",
|
|
@@ -264,6 +265,22 @@ export function validateMaterialAdmissionLedger(args) {
|
|
|
264
265
|
const seen = new Set();
|
|
265
266
|
let requiredOrAdmittedRowCount = 0;
|
|
266
267
|
let downstreamConsumedRowCount = 0;
|
|
268
|
+
// G(a) obligation recorder (INV-OBLIGATION-COVERAGE-1). Placed before the per-row loop so the
|
|
269
|
+
// unconditional row-rule enforcers (uniqueness / consumer-closure / diagnostic-actionability /
|
|
270
|
+
// rejected-replay) record on zero-row input too. The two reference-integrity obligations are
|
|
271
|
+
// gated on their authoritative input being present, since the validator skips them otherwise
|
|
272
|
+
// (control never reaches the enforcer when the selected frame / observed source refs are absent).
|
|
273
|
+
const assertedObligationIds = [];
|
|
274
|
+
assertObligation(assertedObligationIds, "validate_admission_row_uniqueness");
|
|
275
|
+
assertObligation(assertedObligationIds, "require_admitted_required_or_supporting_rows_to_have_candidate_seed_maturation_limitation_blocked_or_out_of_scope_consumer");
|
|
276
|
+
assertObligation(assertedObligationIds, "prevent_diagnostic_or_trace_only_rows_from_silently_affecting_actionability");
|
|
277
|
+
assertObligation(assertedObligationIds, "require_rejected_ambiguous_rows_to_preserve_replayable_evidence_or_limitation");
|
|
278
|
+
if (purposeElements.size > 0) {
|
|
279
|
+
assertObligation(assertedObligationIds, "validate_purpose_element_refs_against_selected_purpose_frame");
|
|
280
|
+
}
|
|
281
|
+
if (knownSourceRefs.size > 0) {
|
|
282
|
+
assertObligation(assertedObligationIds, "validate_source_refs_against_observed_source_refs");
|
|
283
|
+
}
|
|
267
284
|
for (const [index, row] of ledger.admission_rows.entries()) {
|
|
268
285
|
const subject = row.admission_id || `admission_rows[${index}]`;
|
|
269
286
|
if (!row.admission_id) {
|
|
@@ -461,6 +478,7 @@ export function validateMaterialAdmissionLedger(args) {
|
|
|
461
478
|
validation_results: violations.length === 0
|
|
462
479
|
? ["material_admission_valid"]
|
|
463
480
|
: ["material_admission_invalid"],
|
|
481
|
+
asserted_obligation_ids: assertedObligationIds,
|
|
464
482
|
violations,
|
|
465
483
|
};
|
|
466
484
|
}
|
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { parse as parseYaml } from "yaml";
|
|
4
4
|
import { atomicWriteYamlDocument as writeYamlDocument, assertArrayField } from "../artifact-io.js";
|
|
5
5
|
import { isTargetMaterialKind, } from "../target-material-kind.js";
|
|
6
|
+
import { assertObligation } from "./obligation-assertion.js";
|
|
6
7
|
import { loadReconstructContractRegistry, resolveRegistryRef, } from "./contract-registry.js";
|
|
7
8
|
function isoNow() {
|
|
8
9
|
return new Date().toISOString();
|
|
@@ -58,6 +59,18 @@ export function validateTargetMaterialProfile(args) {
|
|
|
58
59
|
assertArrayField(args.targetMaterialProfile.detection?.per_ref, "target-material-profile", "detection.per_ref");
|
|
59
60
|
const profile = args.targetMaterialProfile;
|
|
60
61
|
const violations = [];
|
|
62
|
+
// G(a) deferred-7 slice 2: record the two obligations this validator fully enforces over every selected
|
|
63
|
+
// source profile. Stamped before the per-profile loop so they fire on zero selected profiles. The
|
|
64
|
+
// validation artifact is reuse-hashed (run.ts target_material_profile_validation_sha256) AND
|
|
65
|
+
// scout-captured — asserted_obligation_ids is in-memory-only (Stage 0 #145), so stamping it does not
|
|
66
|
+
// rotate reuse. PARKED (see obligation-coverage-ledger.yaml): the four mixed_targets_/partial_composite_
|
|
67
|
+
// obligations — member→aggregate purpose mapping and partial-member ready-projection limiting are
|
|
68
|
+
// NOT_FOUND (no field, no check), and the two per-member preservation obligations are PARTIAL (the
|
|
69
|
+
// field-match runs for ALL selected profiles, not the named mixed scope, and names facets absent from
|
|
70
|
+
// the schema: support_state / source_refs).
|
|
71
|
+
const assertedObligationIds = [];
|
|
72
|
+
assertObligation(assertedObligationIds, "every_selected_source_profile_snapshot_contains_version_policy_required_fields");
|
|
73
|
+
assertObligation(assertedObligationIds, "every_selected_source_profile_snapshot_matches_source_profile_records_by_profile_id_and_target_material_kind");
|
|
61
74
|
const projectRoot = projectRootFromRegistryPath(args.registryRef);
|
|
62
75
|
if (!isTargetMaterialKind(profile.target_material_kind)) {
|
|
63
76
|
violations.push(violation({
|
|
@@ -186,6 +199,7 @@ export function validateTargetMaterialProfile(args) {
|
|
|
186
199
|
validation_results: violations.length === 0
|
|
187
200
|
? ["target_material_profile_valid"]
|
|
188
201
|
: ["target_material_profile_invalid"],
|
|
202
|
+
asserted_obligation_ids: assertedObligationIds,
|
|
189
203
|
violations,
|
|
190
204
|
};
|
|
191
205
|
}
|