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
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { constants as fsConstants } from "node:fs";
|
|
5
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { dispatchDescriptorProjection, } from "../llm/sealed-dispatch-capability.js";
|
|
8
|
+
import { isDispatchIncompleteArtifact } from "../llm/dispatch-breaker.js";
|
|
9
|
+
import { resolveDispatchFallbackAdapterCapabilities } from "../llm/dispatch-fallback-adapter-capabilities.js";
|
|
10
|
+
const FailureEvidenceCommonShape = {
|
|
11
|
+
descriptor_id: z.string().min(1),
|
|
12
|
+
capability_instance_id: z.string().min(1),
|
|
13
|
+
logical_dispatch_id: z.string().min(1),
|
|
14
|
+
actual_adapter_request_count: z.number().int().nonnegative(),
|
|
15
|
+
};
|
|
16
|
+
const failureEvidenceVariant = (failureClass, failureCode, source, extraShape) => z.object({
|
|
17
|
+
...FailureEvidenceCommonShape,
|
|
18
|
+
...extraShape,
|
|
19
|
+
failure_class: z.literal(failureClass),
|
|
20
|
+
failure_code: z.literal(failureCode),
|
|
21
|
+
source: z.literal(source),
|
|
22
|
+
}).strict();
|
|
23
|
+
const failureEvidenceSchema = (extraShape) => z.union([
|
|
24
|
+
failureEvidenceVariant("rate_limit", "http_429", "sdk_http_status", extraShape),
|
|
25
|
+
failureEvidenceVariant("rate_limit", "provider_rate_limit_code", "sdk_error_code", extraShape),
|
|
26
|
+
failureEvidenceVariant("auth", "http_401", "sdk_http_status", extraShape),
|
|
27
|
+
failureEvidenceVariant("auth", "http_403", "sdk_http_status", extraShape),
|
|
28
|
+
failureEvidenceVariant("auth", "provider_auth_code", "sdk_error_code", extraShape),
|
|
29
|
+
failureEvidenceVariant("transport", "timeout", "sdk_exception_type", extraShape),
|
|
30
|
+
failureEvidenceVariant("transport", "connection_failure", "sdk_exception_type", extraShape),
|
|
31
|
+
failureEvidenceVariant("transport", "http_5xx", "sdk_http_status", extraShape),
|
|
32
|
+
failureEvidenceVariant(null, "provider_request_rejected", "sdk_http_status", extraShape),
|
|
33
|
+
failureEvidenceVariant(null, "adapter_contract_violation", "sdk_exception_type", extraShape),
|
|
34
|
+
failureEvidenceVariant(null, "adapter_unknown", "sdk_error_code", extraShape),
|
|
35
|
+
failureEvidenceVariant(null, "adapter_unknown", "sdk_exception_type", extraShape),
|
|
36
|
+
]);
|
|
37
|
+
const FailureEvidenceSchema = failureEvidenceSchema({});
|
|
38
|
+
const DescriptorSchema = z
|
|
39
|
+
.object({
|
|
40
|
+
descriptor_id: z.string().min(1),
|
|
41
|
+
model_provider: z.enum(["openai", "anthropic"]),
|
|
42
|
+
model_id: z.string().min(1),
|
|
43
|
+
execution_adapter: z.enum(["openai_sdk", "anthropic_sdk"]),
|
|
44
|
+
protocol_version: z.enum(["openai_responses_v1", "anthropic_messages_v1"]),
|
|
45
|
+
adapter_package_version: z.string().min(1),
|
|
46
|
+
auth: z.literal("api_key"),
|
|
47
|
+
endpoint_kind: z.literal("official_sdk"),
|
|
48
|
+
service_tier: z.string().nullable(),
|
|
49
|
+
reasoning_effort: z.string().nullable(),
|
|
50
|
+
dispatch_role: z.enum(["semantic_map_synthesize", "semantic_map_verify"]),
|
|
51
|
+
})
|
|
52
|
+
.strict()
|
|
53
|
+
.superRefine((descriptor, ctx) => {
|
|
54
|
+
const openAI = descriptor.model_provider === "openai";
|
|
55
|
+
if (descriptor.execution_adapter !== (openAI ? "openai_sdk" : "anthropic_sdk") ||
|
|
56
|
+
descriptor.protocol_version !== (openAI ? "openai_responses_v1" : "anthropic_messages_v1") ||
|
|
57
|
+
!resolveDispatchFallbackAdapterCapabilities({
|
|
58
|
+
executionAdapter: descriptor.execution_adapter,
|
|
59
|
+
adapterPackageVersion: descriptor.adapter_package_version,
|
|
60
|
+
protocolVersion: descriptor.protocol_version,
|
|
61
|
+
})) {
|
|
62
|
+
ctx.addIssue({ code: "custom", message: "descriptor provider/adapter/protocol/version binding is invalid" });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
const IdPartitionSchema = z
|
|
66
|
+
.object({
|
|
67
|
+
planned: z.array(z.string().min(1)),
|
|
68
|
+
completed: z.array(z.string().min(1)),
|
|
69
|
+
dead_letter: z.array(z.string().min(1)),
|
|
70
|
+
incomplete: z.array(z.string().min(1)),
|
|
71
|
+
})
|
|
72
|
+
.strict();
|
|
73
|
+
const ActivationContributorSchema = failureEvidenceSchema({
|
|
74
|
+
observation_id: z.string().min(1),
|
|
75
|
+
operation: z.enum(["semantic_map_synthesize", "semantic_map_verify"]),
|
|
76
|
+
});
|
|
77
|
+
export const DispatchFallbackActivationSchema = z
|
|
78
|
+
.object({
|
|
79
|
+
schema_version: z.literal("dispatch-fallback-activation/v1"),
|
|
80
|
+
session_id: z.string().min(1),
|
|
81
|
+
created_at: z.string().datetime(),
|
|
82
|
+
owner_attempt_id: z.string().min(1),
|
|
83
|
+
owner_lock_token_hash: z.string().min(1),
|
|
84
|
+
trigger: z
|
|
85
|
+
.object({
|
|
86
|
+
failure_class: z.literal("rate_limit"),
|
|
87
|
+
systemic_failure_threshold: z.number().int().min(1),
|
|
88
|
+
contributors: z.array(ActivationContributorSchema).min(1),
|
|
89
|
+
})
|
|
90
|
+
.strict(),
|
|
91
|
+
primary_descriptor: DescriptorSchema,
|
|
92
|
+
primary_capability_instance_id: z.string().min(1),
|
|
93
|
+
fallback_descriptors: z
|
|
94
|
+
.object({
|
|
95
|
+
synthesize: DescriptorSchema,
|
|
96
|
+
verify: DescriptorSchema,
|
|
97
|
+
})
|
|
98
|
+
.strict(),
|
|
99
|
+
partition: IdPartitionSchema,
|
|
100
|
+
route_relation: z.literal("cross_provider"),
|
|
101
|
+
})
|
|
102
|
+
.strict()
|
|
103
|
+
.superRefine((artifact, ctx) => {
|
|
104
|
+
const sets = Object.values(artifact.partition);
|
|
105
|
+
for (const values of sets) {
|
|
106
|
+
if (new Set(values).size !== values.length) {
|
|
107
|
+
ctx.addIssue({ code: "custom", message: "partition sets must contain unique ids" });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const union = new Set([
|
|
111
|
+
...artifact.partition.completed,
|
|
112
|
+
...artifact.partition.dead_letter,
|
|
113
|
+
...artifact.partition.incomplete,
|
|
114
|
+
]);
|
|
115
|
+
if (union.size !==
|
|
116
|
+
artifact.partition.completed.length +
|
|
117
|
+
artifact.partition.dead_letter.length +
|
|
118
|
+
artifact.partition.incomplete.length ||
|
|
119
|
+
union.size !== artifact.partition.planned.length ||
|
|
120
|
+
artifact.partition.planned.some((id) => !union.has(id))) {
|
|
121
|
+
ctx.addIssue({ code: "custom", message: "planned must exactly and disjointly partition into completed/dead_letter/incomplete" });
|
|
122
|
+
}
|
|
123
|
+
for (const contributor of artifact.trigger.contributors) {
|
|
124
|
+
if (contributor.failure_class !== "rate_limit" ||
|
|
125
|
+
contributor.actual_adapter_request_count < 1 ||
|
|
126
|
+
contributor.descriptor_id !== artifact.primary_descriptor.descriptor_id ||
|
|
127
|
+
contributor.capability_instance_id !==
|
|
128
|
+
artifact.primary_capability_instance_id ||
|
|
129
|
+
contributor.operation !== artifact.primary_descriptor.dispatch_role ||
|
|
130
|
+
!artifact.partition.incomplete.includes(contributor.observation_id)) {
|
|
131
|
+
ctx.addIssue({ code: "custom", message: "contributors must be counted rate-limit evidence from the expected primary operation and incomplete set" });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (artifact.trigger.contributors.length <
|
|
135
|
+
artifact.trigger.systemic_failure_threshold) {
|
|
136
|
+
ctx.addIssue({ code: "custom", message: "activation contributors must meet the primary breaker threshold" });
|
|
137
|
+
}
|
|
138
|
+
if (new Set(artifact.trigger.contributors.map((contributor) => contributor.observation_id)).size !== artifact.trigger.contributors.length) {
|
|
139
|
+
ctx.addIssue({ code: "custom", message: "activation contributors must have distinct observation ids" });
|
|
140
|
+
}
|
|
141
|
+
if (artifact.fallback_descriptors.synthesize.dispatch_role !== "semantic_map_synthesize" ||
|
|
142
|
+
artifact.fallback_descriptors.verify.dispatch_role !== "semantic_map_verify" ||
|
|
143
|
+
artifact.fallback_descriptors.synthesize.model_provider !==
|
|
144
|
+
artifact.fallback_descriptors.verify.model_provider ||
|
|
145
|
+
artifact.primary_descriptor.model_provider ===
|
|
146
|
+
artifact.fallback_descriptors.synthesize.model_provider ||
|
|
147
|
+
artifact.primary_descriptor.model_provider ===
|
|
148
|
+
artifact.fallback_descriptors.verify.model_provider) {
|
|
149
|
+
ctx.addIssue({ code: "custom", message: "fallback descriptors must be a complete same-provider pair cross-provider from primary" });
|
|
150
|
+
}
|
|
151
|
+
for (const descriptor of [
|
|
152
|
+
artifact.primary_descriptor,
|
|
153
|
+
artifact.fallback_descriptors.synthesize,
|
|
154
|
+
artifact.fallback_descriptors.verify,
|
|
155
|
+
]) {
|
|
156
|
+
const { descriptor_id: _descriptorId, ...preimage } = descriptor;
|
|
157
|
+
if (dispatchDescriptorProjection(preimage).descriptor_id !==
|
|
158
|
+
descriptor.descriptor_id) {
|
|
159
|
+
ctx.addIssue({ code: "custom", message: `descriptor_id does not match its canonical preimage for ${descriptor.dispatch_role}` });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
const ArtifactIntegritySchema = z
|
|
164
|
+
.object({ ref: z.string().min(1), sha256: z.string().regex(/^[a-f0-9]{64}$/) })
|
|
165
|
+
.strict();
|
|
166
|
+
export const DispatchFallbackOutcomeSchema = z
|
|
167
|
+
.object({
|
|
168
|
+
schema_version: z.literal("dispatch-fallback-outcome/v1"),
|
|
169
|
+
session_id: z.string().min(1),
|
|
170
|
+
created_at: z.string().datetime(),
|
|
171
|
+
owner_attempt_id: z.string().min(1),
|
|
172
|
+
activation: ArtifactIntegritySchema,
|
|
173
|
+
status: z.enum(["completed", "halted"]),
|
|
174
|
+
partition: z
|
|
175
|
+
.object({
|
|
176
|
+
target_count: z.number().int().nonnegative(),
|
|
177
|
+
completed_count: z.number().int().nonnegative(),
|
|
178
|
+
dead_letter_count: z.number().int().nonnegative(),
|
|
179
|
+
incomplete_count: z.number().int().nonnegative(),
|
|
180
|
+
})
|
|
181
|
+
.strict(),
|
|
182
|
+
dispatch_counts: z
|
|
183
|
+
.object({
|
|
184
|
+
synthesize_logical: z.number().int().nonnegative(),
|
|
185
|
+
verify_logical: z.number().int().nonnegative(),
|
|
186
|
+
synthesize_adapter_requests: z.number().int().nonnegative(),
|
|
187
|
+
verify_adapter_requests: z.number().int().nonnegative(),
|
|
188
|
+
})
|
|
189
|
+
.strict(),
|
|
190
|
+
final_artifacts: z
|
|
191
|
+
.object({
|
|
192
|
+
dispatch_incomplete: ArtifactIntegritySchema,
|
|
193
|
+
semantic_map_census: ArtifactIntegritySchema,
|
|
194
|
+
semantic_map: ArtifactIntegritySchema,
|
|
195
|
+
})
|
|
196
|
+
.strict(),
|
|
197
|
+
terminal_failure: FailureEvidenceSchema.nullable(),
|
|
198
|
+
})
|
|
199
|
+
.strict()
|
|
200
|
+
.superRefine((artifact, ctx) => {
|
|
201
|
+
if (artifact.partition.target_count !==
|
|
202
|
+
artifact.partition.completed_count +
|
|
203
|
+
artifact.partition.dead_letter_count +
|
|
204
|
+
artifact.partition.incomplete_count) {
|
|
205
|
+
ctx.addIssue({ code: "custom", message: "outcome partition counts must reconcile" });
|
|
206
|
+
}
|
|
207
|
+
if (artifact.status === "completed" && artifact.partition.incomplete_count !== 0) {
|
|
208
|
+
ctx.addIssue({ code: "custom", message: "completed outcome cannot retain incomplete items" });
|
|
209
|
+
}
|
|
210
|
+
if (artifact.status === "halted" && artifact.partition.incomplete_count === 0) {
|
|
211
|
+
ctx.addIssue({ code: "custom", message: "halted outcome requires incomplete items" });
|
|
212
|
+
}
|
|
213
|
+
if (artifact.status === "halted" &&
|
|
214
|
+
(artifact.terminal_failure === null ||
|
|
215
|
+
artifact.terminal_failure.failure_class === null ||
|
|
216
|
+
artifact.terminal_failure.actual_adapter_request_count < 1)) {
|
|
217
|
+
ctx.addIssue({ code: "custom", message: "halted outcome requires typed terminal failure evidence" });
|
|
218
|
+
}
|
|
219
|
+
if (artifact.status === "completed" && artifact.terminal_failure !== null) {
|
|
220
|
+
ctx.addIssue({ code: "custom", message: "completed outcome cannot carry terminal failure evidence" });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
function nonnegativeSafeInteger(value) {
|
|
224
|
+
return Number.isSafeInteger(value) && value >= 0;
|
|
225
|
+
}
|
|
226
|
+
export function assertDispatchFallbackTerminalArtifactContracts(args) {
|
|
227
|
+
if (!isDispatchIncompleteArtifact(args.partition)) {
|
|
228
|
+
throw new Error("dispatch fallback terminal dispatch partition is malformed.");
|
|
229
|
+
}
|
|
230
|
+
const census = args.census;
|
|
231
|
+
if (!census ||
|
|
232
|
+
typeof census !== "object" ||
|
|
233
|
+
census.schema_version !== "1" ||
|
|
234
|
+
!Array.isArray(census.by_observation) ||
|
|
235
|
+
![
|
|
236
|
+
census.observations_total,
|
|
237
|
+
census.observations_map_present,
|
|
238
|
+
census.observations_map_absent,
|
|
239
|
+
census.synthesize_calls_total,
|
|
240
|
+
census.verify_calls_total,
|
|
241
|
+
census.max_synthesize_calls,
|
|
242
|
+
census.max_verify_calls,
|
|
243
|
+
].every(nonnegativeSafeInteger) ||
|
|
244
|
+
census.observations_total !== census.by_observation.length ||
|
|
245
|
+
census.observations_total !==
|
|
246
|
+
census.observations_map_present + census.observations_map_absent) {
|
|
247
|
+
throw new Error("dispatch fallback terminal semantic-map census is malformed.");
|
|
248
|
+
}
|
|
249
|
+
const observationIds = new Set();
|
|
250
|
+
let synthesizeCalls = 0;
|
|
251
|
+
let verifyCalls = 0;
|
|
252
|
+
const mapPresentIds = new Set();
|
|
253
|
+
for (const observation of census.by_observation) {
|
|
254
|
+
if (!observation ||
|
|
255
|
+
typeof observation.observation_id !== "string" ||
|
|
256
|
+
observationIds.has(observation.observation_id) ||
|
|
257
|
+
typeof observation.map_present !== "boolean" ||
|
|
258
|
+
!Array.isArray(observation.columns)) {
|
|
259
|
+
throw new Error("dispatch fallback terminal semantic-map census observation is malformed.");
|
|
260
|
+
}
|
|
261
|
+
observationIds.add(observation.observation_id);
|
|
262
|
+
if (observation.map_present)
|
|
263
|
+
mapPresentIds.add(observation.observation_id);
|
|
264
|
+
for (const column of observation.columns) {
|
|
265
|
+
if (!column ||
|
|
266
|
+
!nonnegativeSafeInteger(column.synthesize_calls) ||
|
|
267
|
+
!nonnegativeSafeInteger(column.verify_calls)) {
|
|
268
|
+
throw new Error("dispatch fallback terminal semantic-map census column is malformed.");
|
|
269
|
+
}
|
|
270
|
+
synthesizeCalls += column.synthesize_calls;
|
|
271
|
+
verifyCalls += column.verify_calls;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (synthesizeCalls !== census.synthesize_calls_total ||
|
|
275
|
+
verifyCalls !== census.verify_calls_total ||
|
|
276
|
+
mapPresentIds.size !== census.observations_map_present) {
|
|
277
|
+
throw new Error("dispatch fallback terminal semantic-map census totals do not reconcile.");
|
|
278
|
+
}
|
|
279
|
+
const sidecar = args.sidecar;
|
|
280
|
+
if (!sidecar || sidecar.schema_version !== "1" || !Array.isArray(sidecar.observations)) {
|
|
281
|
+
throw new Error("dispatch fallback terminal semantic-map sidecar is malformed.");
|
|
282
|
+
}
|
|
283
|
+
const sidecarIds = new Set();
|
|
284
|
+
for (const observation of sidecar.observations) {
|
|
285
|
+
const projection = observation?.projection;
|
|
286
|
+
if (!observation ||
|
|
287
|
+
typeof observation.observation_id !== "string" ||
|
|
288
|
+
sidecarIds.has(observation.observation_id) ||
|
|
289
|
+
!Array.isArray(observation.node_epochs) ||
|
|
290
|
+
!projection ||
|
|
291
|
+
projection.authority !== "non_authoritative" ||
|
|
292
|
+
projection.provisional !== true ||
|
|
293
|
+
!Array.isArray(projection.nodes) ||
|
|
294
|
+
!Array.isArray(projection.refuted_disclosure) ||
|
|
295
|
+
!nonnegativeSafeInteger(projection.nodes_total) ||
|
|
296
|
+
!nonnegativeSafeInteger(projection.refuted_disclosure_total) ||
|
|
297
|
+
!nonnegativeSafeInteger(projection.unanchored_unverified_total) ||
|
|
298
|
+
projection.nodes.length > projection.nodes_total ||
|
|
299
|
+
projection.refuted_disclosure.length > projection.refuted_disclosure_total) {
|
|
300
|
+
throw new Error("dispatch fallback terminal semantic-map sidecar observation is malformed.");
|
|
301
|
+
}
|
|
302
|
+
sidecarIds.add(observation.observation_id);
|
|
303
|
+
}
|
|
304
|
+
if (sidecarIds.size !== mapPresentIds.size ||
|
|
305
|
+
[...sidecarIds].some((id) => !mapPresentIds.has(id))) {
|
|
306
|
+
throw new Error("dispatch fallback terminal semantic-map sidecar does not match map-present census rows.");
|
|
307
|
+
}
|
|
308
|
+
return { partition: args.partition, census, sidecar };
|
|
309
|
+
}
|
|
310
|
+
export function dispatchFallbackActivationPath(sessionRoot) {
|
|
311
|
+
return path.join(path.resolve(sessionRoot), "dispatch-fallback-activation.yaml");
|
|
312
|
+
}
|
|
313
|
+
export function dispatchFallbackOutcomePath(sessionRoot) {
|
|
314
|
+
return path.join(path.resolve(sessionRoot), "dispatch-fallback-outcome.yaml");
|
|
315
|
+
}
|
|
316
|
+
async function readRegularFileIfPresent(filePath) {
|
|
317
|
+
try {
|
|
318
|
+
const stat = await fs.lstat(filePath);
|
|
319
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
320
|
+
throw new Error(`dispatch fallback artifact must be a regular direct-child file: ${filePath}`);
|
|
321
|
+
}
|
|
322
|
+
return await fs.readFile(filePath, "utf8");
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
if (error.code === "ENOENT")
|
|
326
|
+
return null;
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async function readRunControlIfPresent(sessionRoot) {
|
|
331
|
+
const raw = await readRegularFileIfPresent(path.join(path.resolve(sessionRoot), "reconstruct-run-control.yaml"));
|
|
332
|
+
return raw === null ? null : parseYaml(raw);
|
|
333
|
+
}
|
|
334
|
+
function parseArtifact(raw, schema, label) {
|
|
335
|
+
const parsed = schema.safeParse(parseYaml(raw));
|
|
336
|
+
if (!parsed.success) {
|
|
337
|
+
throw new Error(`dispatch fallback ${label} artifact is malformed: ${parsed.error.issues
|
|
338
|
+
.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`)
|
|
339
|
+
.join("; ")}`);
|
|
340
|
+
}
|
|
341
|
+
return parsed.data;
|
|
342
|
+
}
|
|
343
|
+
export async function readDispatchFallbackActivation(sessionRoot) {
|
|
344
|
+
const raw = await readRegularFileIfPresent(dispatchFallbackActivationPath(sessionRoot));
|
|
345
|
+
return raw === null
|
|
346
|
+
? null
|
|
347
|
+
: parseArtifact(raw, DispatchFallbackActivationSchema, "activation");
|
|
348
|
+
}
|
|
349
|
+
export async function readDispatchFallbackOutcome(sessionRoot) {
|
|
350
|
+
const raw = await readRegularFileIfPresent(dispatchFallbackOutcomePath(sessionRoot));
|
|
351
|
+
return raw === null
|
|
352
|
+
? null
|
|
353
|
+
: parseArtifact(raw, DispatchFallbackOutcomeSchema, "outcome");
|
|
354
|
+
}
|
|
355
|
+
export async function assertDispatchFallbackSessionAdmission(args) {
|
|
356
|
+
const [activation, outcome, runControl] = await Promise.all([
|
|
357
|
+
readDispatchFallbackActivation(args.sessionRoot),
|
|
358
|
+
readDispatchFallbackOutcome(args.sessionRoot),
|
|
359
|
+
readRunControlIfPresent(args.sessionRoot),
|
|
360
|
+
]);
|
|
361
|
+
if (outcome && !activation) {
|
|
362
|
+
throw new Error("dispatch fallback structural corruption: outcome exists without activation.");
|
|
363
|
+
}
|
|
364
|
+
if (activation) {
|
|
365
|
+
const resolvedSessionRoot = await fs.realpath(args.sessionRoot);
|
|
366
|
+
if (activation.session_id !== path.basename(resolvedSessionRoot) ||
|
|
367
|
+
(outcome &&
|
|
368
|
+
(outcome.session_id !== activation.session_id ||
|
|
369
|
+
outcome.owner_attempt_id !== activation.owner_attempt_id))) {
|
|
370
|
+
throw new Error("dispatch fallback structural corruption: activation/outcome session ownership mismatch.");
|
|
371
|
+
}
|
|
372
|
+
if (outcome) {
|
|
373
|
+
if (outcome.status === "halted" &&
|
|
374
|
+
outcome.terminal_failure &&
|
|
375
|
+
![
|
|
376
|
+
activation.fallback_descriptors.synthesize.descriptor_id,
|
|
377
|
+
activation.fallback_descriptors.verify.descriptor_id,
|
|
378
|
+
].includes(outcome.terminal_failure.descriptor_id)) {
|
|
379
|
+
throw new Error("dispatch fallback structural corruption: terminal failure is not bound to the fallback pair.");
|
|
380
|
+
}
|
|
381
|
+
const activationPath = dispatchFallbackActivationPath(resolvedSessionRoot);
|
|
382
|
+
const activationHash = (await artifactIntegrity(activationPath)).sha256;
|
|
383
|
+
if (path.resolve(outcome.activation.ref) !== activationPath) {
|
|
384
|
+
throw new Error("dispatch fallback structural corruption: outcome activation ref mismatch.");
|
|
385
|
+
}
|
|
386
|
+
if (outcome.activation.sha256 !== activationHash) {
|
|
387
|
+
throw new Error("dispatch fallback structural corruption: outcome activation hash mismatch.");
|
|
388
|
+
}
|
|
389
|
+
const expectedFinalRefs = {
|
|
390
|
+
dispatch_incomplete: path.join(resolvedSessionRoot, "dispatch-incomplete.yaml"),
|
|
391
|
+
semantic_map_census: path.join(resolvedSessionRoot, "comprehension", "semantic-map-census.yaml"),
|
|
392
|
+
semantic_map: path.join(resolvedSessionRoot, "comprehension", "semantic-map.yaml"),
|
|
393
|
+
};
|
|
394
|
+
for (const key of Object.keys(expectedFinalRefs)) {
|
|
395
|
+
const integrity = outcome.final_artifacts[key];
|
|
396
|
+
const expectedRef = expectedFinalRefs[key];
|
|
397
|
+
const actualHash = (await artifactIntegrity(expectedRef)).sha256;
|
|
398
|
+
if (path.resolve(integrity.ref) !== expectedRef ||
|
|
399
|
+
integrity.sha256 !== actualHash) {
|
|
400
|
+
throw new Error(`dispatch fallback structural corruption: ${key} ref/hash mismatch.`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const finalPartitionRaw = parseYaml((await readRegularFileIfPresent(expectedFinalRefs.dispatch_incomplete)));
|
|
404
|
+
const terminalArtifacts = assertDispatchFallbackTerminalArtifactContracts({
|
|
405
|
+
partition: finalPartitionRaw,
|
|
406
|
+
census: parseYaml((await readRegularFileIfPresent(expectedFinalRefs.semantic_map_census))),
|
|
407
|
+
sidecar: parseYaml((await readRegularFileIfPresent(expectedFinalRefs.semantic_map))),
|
|
408
|
+
});
|
|
409
|
+
const finalPartition = terminalArtifacts.partition;
|
|
410
|
+
if (finalPartition.pipeline !== "reconstruct" ||
|
|
411
|
+
finalPartition.batch_label !== "semantic-map") {
|
|
412
|
+
throw new Error("dispatch fallback structural corruption: final dispatch partition is malformed.");
|
|
413
|
+
}
|
|
414
|
+
const targetIds = new Set(activation.partition.incomplete);
|
|
415
|
+
const finalDeadLetterIds = finalPartition.dead_letter.map((entry) => entry.item_id);
|
|
416
|
+
const finalAllIds = [
|
|
417
|
+
...finalPartition.completed_item_ids,
|
|
418
|
+
...finalDeadLetterIds,
|
|
419
|
+
...finalPartition.incomplete_item_ids,
|
|
420
|
+
];
|
|
421
|
+
const plannedIds = new Set(activation.partition.planned);
|
|
422
|
+
if (new Set(finalAllIds).size !== finalAllIds.length ||
|
|
423
|
+
new Set(finalAllIds).size !== plannedIds.size ||
|
|
424
|
+
finalAllIds.some((id) => !plannedIds.has(id)) ||
|
|
425
|
+
activation.partition.completed.some((id) => !finalPartition.completed_item_ids.includes(id)) ||
|
|
426
|
+
activation.partition.dead_letter.some((id) => !finalDeadLetterIds.includes(id))) {
|
|
427
|
+
throw new Error("dispatch fallback structural corruption: final full partition does not preserve activation lineage.");
|
|
428
|
+
}
|
|
429
|
+
const completedIds = finalPartition.completed_item_ids.filter((id) => targetIds.has(id));
|
|
430
|
+
const deadLetterIds = finalDeadLetterIds.filter((id) => targetIds.has(id));
|
|
431
|
+
const incompleteIds = finalPartition.incomplete_item_ids.filter((id) => targetIds.has(id));
|
|
432
|
+
const terminalIds = [...completedIds, ...deadLetterIds, ...incompleteIds];
|
|
433
|
+
if (outcome.partition.target_count !== targetIds.size ||
|
|
434
|
+
outcome.partition.completed_count !== completedIds.length ||
|
|
435
|
+
outcome.partition.dead_letter_count !== deadLetterIds.length ||
|
|
436
|
+
outcome.partition.incomplete_count !== incompleteIds.length ||
|
|
437
|
+
new Set(terminalIds).size !== terminalIds.length ||
|
|
438
|
+
new Set(terminalIds).size !== targetIds.size ||
|
|
439
|
+
terminalIds.some((id) => !targetIds.has(id))) {
|
|
440
|
+
throw new Error("dispatch fallback structural corruption: outcome/final target partition mismatch.");
|
|
441
|
+
}
|
|
442
|
+
if ((outcome.status === "completed" &&
|
|
443
|
+
(finalPartition.breaker.tripped ||
|
|
444
|
+
finalPartition.breaker.failure_class !== null ||
|
|
445
|
+
finalPartition.breaker.consecutive_item_count !== null ||
|
|
446
|
+
finalPartition.incomplete_item_ids.length !== 0)) ||
|
|
447
|
+
(outcome.status === "halted" &&
|
|
448
|
+
(!finalPartition.breaker.tripped || incompleteIds.length === 0))) {
|
|
449
|
+
throw new Error("dispatch fallback structural corruption: outcome/final breaker state mismatch.");
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
throw new Error(`dispatch fallback activation already consumed this session lineage; retry_with_new_session (${dispatchFallbackActivationPath(args.sessionRoot)}).`);
|
|
453
|
+
}
|
|
454
|
+
if (!runControl)
|
|
455
|
+
return;
|
|
456
|
+
assertDispatchFallbackRunControlHasNoLiveOwner({
|
|
457
|
+
runControl,
|
|
458
|
+
enabled: args.enabled,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
export function assertDispatchFallbackRunControlHasNoLiveOwner(args) {
|
|
462
|
+
if (!args.enabled)
|
|
463
|
+
return;
|
|
464
|
+
const runningAttemptIds = new Set(args.runControl.attempt_rows
|
|
465
|
+
.filter((row) => row.attempt_status === "running")
|
|
466
|
+
.map((row) => row.attempt_id));
|
|
467
|
+
const hasLiveOwner = args.runControl.lock_rows.some((row) => row.lock_scope === "session_root" &&
|
|
468
|
+
row.lock_status === "held" &&
|
|
469
|
+
runningAttemptIds.has(row.owner_attempt_id));
|
|
470
|
+
if (hasLiveOwner) {
|
|
471
|
+
throw new Error("dispatch fallback enabled session has a running attempt holding the session lock; lease expiry is not takeover authority, retry_with_new_session.");
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
export function assertDispatchFallbackAttemptOwner(args) {
|
|
475
|
+
const attempt = args.runControl.attempt_rows.find((row) => row.attempt_id === args.attemptId);
|
|
476
|
+
if (!attempt ||
|
|
477
|
+
attempt.attempt_status !== "running" ||
|
|
478
|
+
(args.requireInitial &&
|
|
479
|
+
(attempt.attempt_kind !== "initial" || attempt.parent_attempt_id !== null))) {
|
|
480
|
+
throw new Error(`dispatch fallback attempt ${args.attemptId} is not the eligible running owner.`);
|
|
481
|
+
}
|
|
482
|
+
const lock = args.runControl.lock_rows.find((row) => row.lock_scope === "session_root" &&
|
|
483
|
+
row.owner_attempt_id === args.attemptId &&
|
|
484
|
+
row.lock_status === "held");
|
|
485
|
+
if (!lock || lock.lock_token_hash !== args.lockTokenHash) {
|
|
486
|
+
throw new Error(`dispatch fallback attempt ${args.attemptId} does not own the expected held lock.`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
async function assertPinnedSessionRoot(sessionRoot) {
|
|
490
|
+
await fs.mkdir(sessionRoot, { recursive: true });
|
|
491
|
+
const stat = await fs.lstat(sessionRoot);
|
|
492
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
493
|
+
throw new Error(`dispatch fallback session root must be a real directory: ${sessionRoot}`);
|
|
494
|
+
}
|
|
495
|
+
return fs.realpath(sessionRoot);
|
|
496
|
+
}
|
|
497
|
+
async function fsyncDirectory(directory) {
|
|
498
|
+
const handle = await fs.open(directory, fsConstants.O_RDONLY);
|
|
499
|
+
try {
|
|
500
|
+
await handle.sync();
|
|
501
|
+
}
|
|
502
|
+
finally {
|
|
503
|
+
await handle.close();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
async function writeTempFile(directory, finalName, contents) {
|
|
507
|
+
const tempPath = path.join(directory, `.${finalName}.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
508
|
+
const handle = await fs.open(tempPath, fsConstants.O_CREAT |
|
|
509
|
+
fsConstants.O_EXCL |
|
|
510
|
+
fsConstants.O_WRONLY |
|
|
511
|
+
fsConstants.O_NOFOLLOW, 0o600);
|
|
512
|
+
try {
|
|
513
|
+
await handle.writeFile(contents, "utf8");
|
|
514
|
+
await handle.sync();
|
|
515
|
+
}
|
|
516
|
+
finally {
|
|
517
|
+
await handle.close();
|
|
518
|
+
}
|
|
519
|
+
return tempPath;
|
|
520
|
+
}
|
|
521
|
+
async function secureCreateOnceYaml(args) {
|
|
522
|
+
const parsed = args.schema.parse(args.artifact);
|
|
523
|
+
const realRoot = await assertPinnedSessionRoot(args.sessionRoot);
|
|
524
|
+
const finalPath = path.join(realRoot, args.fileName);
|
|
525
|
+
const contents = stringifyYaml(parsed);
|
|
526
|
+
const tempPath = await writeTempFile(realRoot, args.fileName, contents);
|
|
527
|
+
try {
|
|
528
|
+
await fs.link(tempPath, finalPath);
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
if (error.code === "EEXIST") {
|
|
532
|
+
throw new Error(`dispatch fallback create-once artifact already exists: ${finalPath}`);
|
|
533
|
+
}
|
|
534
|
+
throw error;
|
|
535
|
+
}
|
|
536
|
+
finally {
|
|
537
|
+
await fs.rm(tempPath, { force: true });
|
|
538
|
+
}
|
|
539
|
+
await fsyncDirectory(realRoot);
|
|
540
|
+
return { path: finalPath, sha256: sha256Text(contents) };
|
|
541
|
+
}
|
|
542
|
+
export async function publishDispatchFallbackActivation(sessionRoot, artifact) {
|
|
543
|
+
return secureCreateOnceYaml({
|
|
544
|
+
sessionRoot,
|
|
545
|
+
fileName: "dispatch-fallback-activation.yaml",
|
|
546
|
+
artifact,
|
|
547
|
+
schema: DispatchFallbackActivationSchema,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
export async function publishDispatchFallbackOutcome(sessionRoot, artifact) {
|
|
551
|
+
return secureCreateOnceYaml({
|
|
552
|
+
sessionRoot,
|
|
553
|
+
fileName: "dispatch-fallback-outcome.yaml",
|
|
554
|
+
artifact,
|
|
555
|
+
schema: DispatchFallbackOutcomeSchema,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
export async function securePublishDispatchFallbackYaml(args) {
|
|
559
|
+
const realRoot = await assertPinnedSessionRoot(args.sessionRoot);
|
|
560
|
+
const finalPath = path.resolve(realRoot, args.relativePath);
|
|
561
|
+
if (path.relative(realRoot, finalPath).startsWith("..")) {
|
|
562
|
+
throw new Error(`dispatch fallback publication escapes the pinned session root: ${args.relativePath}`);
|
|
563
|
+
}
|
|
564
|
+
const parent = path.dirname(finalPath);
|
|
565
|
+
await fs.mkdir(parent, { recursive: true });
|
|
566
|
+
const parentStat = await fs.lstat(parent);
|
|
567
|
+
if (!parentStat.isDirectory() || parentStat.isSymbolicLink()) {
|
|
568
|
+
throw new Error(`dispatch fallback publication parent must be a real directory: ${parent}`);
|
|
569
|
+
}
|
|
570
|
+
const realParent = await fs.realpath(parent);
|
|
571
|
+
if (path.resolve(realParent) !== path.resolve(parent) ||
|
|
572
|
+
path.relative(realRoot, realParent).startsWith("..")) {
|
|
573
|
+
throw new Error(`dispatch fallback publication parent escapes the pinned session root: ${realParent}`);
|
|
574
|
+
}
|
|
575
|
+
const contents = stringifyYaml(args.value);
|
|
576
|
+
const tempPath = await writeTempFile(realParent, path.basename(finalPath), contents);
|
|
577
|
+
try {
|
|
578
|
+
const existing = await fs.lstat(finalPath).catch((error) => {
|
|
579
|
+
if (error.code === "ENOENT")
|
|
580
|
+
return null;
|
|
581
|
+
throw error;
|
|
582
|
+
});
|
|
583
|
+
if (existing && (!existing.isFile() || existing.isSymbolicLink())) {
|
|
584
|
+
throw new Error(`dispatch fallback refuses to replace a non-regular artifact: ${finalPath}`);
|
|
585
|
+
}
|
|
586
|
+
await fs.rename(tempPath, finalPath);
|
|
587
|
+
const [published, parentAfter] = await Promise.all([
|
|
588
|
+
fs.lstat(finalPath),
|
|
589
|
+
fs.lstat(parent),
|
|
590
|
+
]);
|
|
591
|
+
if (!published.isFile() ||
|
|
592
|
+
published.isSymbolicLink() ||
|
|
593
|
+
!parentAfter.isDirectory() ||
|
|
594
|
+
parentAfter.isSymbolicLink() ||
|
|
595
|
+
parentAfter.dev !== parentStat.dev ||
|
|
596
|
+
parentAfter.ino !== parentStat.ino) {
|
|
597
|
+
throw new Error(`dispatch fallback publication identity changed during commit: ${finalPath}`);
|
|
598
|
+
}
|
|
599
|
+
await fsyncDirectory(realParent);
|
|
600
|
+
}
|
|
601
|
+
catch (error) {
|
|
602
|
+
await fs.rm(tempPath, { force: true });
|
|
603
|
+
throw error;
|
|
604
|
+
}
|
|
605
|
+
return { path: finalPath, sha256: sha256Text(contents) };
|
|
606
|
+
}
|
|
607
|
+
export function sha256Text(value) {
|
|
608
|
+
return crypto.createHash("sha256").update(value).digest("hex");
|
|
609
|
+
}
|
|
610
|
+
export async function artifactIntegrity(filePath) {
|
|
611
|
+
const realRef = await fs.realpath(filePath);
|
|
612
|
+
const bytes = await fs.readFile(realRef);
|
|
613
|
+
return {
|
|
614
|
+
ref: realRef,
|
|
615
|
+
sha256: crypto.createHash("sha256").update(bytes).digest("hex"),
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
export function projectDispatchFallbackRecordBlock(args) {
|
|
619
|
+
const outcome = DispatchFallbackOutcomeSchema.parse(args.outcome);
|
|
620
|
+
if (outcome.status !== "completed") {
|
|
621
|
+
throw new Error("only a completed dispatch fallback outcome may enter the reconstruct record.");
|
|
622
|
+
}
|
|
623
|
+
return {
|
|
624
|
+
outcome_ref: path.resolve(args.outcomeIntegrity.path),
|
|
625
|
+
outcome_sha256: args.outcomeIntegrity.sha256,
|
|
626
|
+
activation_sha256: outcome.activation.sha256,
|
|
627
|
+
owner_attempt_id: outcome.owner_attempt_id,
|
|
628
|
+
trigger_code: "rate_limit",
|
|
629
|
+
route_relation: "cross_provider",
|
|
630
|
+
target_count: outcome.partition.target_count,
|
|
631
|
+
completed_count: outcome.partition.completed_count,
|
|
632
|
+
dead_letter_count: outcome.partition.dead_letter_count,
|
|
633
|
+
incomplete_count: outcome.partition.incomplete_count,
|
|
634
|
+
synthesize_logical_dispatch_count: outcome.dispatch_counts.synthesize_logical,
|
|
635
|
+
verify_logical_dispatch_count: outcome.dispatch_counts.verify_logical,
|
|
636
|
+
synthesize_adapter_request_count: outcome.dispatch_counts.synthesize_adapter_requests,
|
|
637
|
+
verify_adapter_request_count: outcome.dispatch_counts.verify_adapter_requests,
|
|
638
|
+
outcome: "completed",
|
|
639
|
+
};
|
|
640
|
+
}
|