@martinloop/mcp 0.7.1 → 0.8.0
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/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/vendor/adapters/claude-cli.d.ts +11 -1
- package/dist/vendor/adapters/claude-cli.js +285 -71
- package/dist/vendor/adapters/cli-bridge.d.ts +12 -3
- package/dist/vendor/adapters/cli-bridge.js +129 -149
- package/dist/vendor/adapters/codex-cli.d.ts +3 -3
- package/dist/vendor/adapters/codex-cli.js +10 -24
- package/dist/vendor/adapters/codex-host.js +127 -132
- package/dist/vendor/adapters/index.d.ts +2 -1
- package/dist/vendor/adapters/index.js +2 -1
- package/dist/vendor/adapters/openai-compatible.js +88 -6
- package/dist/vendor/adapters/process-supervisor.d.ts +60 -0
- package/dist/vendor/adapters/process-supervisor.js +541 -0
- package/dist/vendor/adapters/verifier-only.d.ts +10 -1
- package/dist/vendor/adapters/verifier-only.js +43 -1
- package/dist/vendor/contracts/external-outcome.d.ts +102 -0
- package/dist/vendor/contracts/external-outcome.js +226 -0
- package/dist/vendor/contracts/index.d.ts +8 -1
- package/dist/vendor/contracts/index.js +2 -0
- package/dist/vendor/contracts/swarm-hosted.d.ts +204 -0
- package/dist/vendor/contracts/swarm-hosted.js +629 -0
- package/dist/vendor/contracts/swarm.d.ts +449 -0
- package/dist/vendor/contracts/swarm.js +550 -0
- package/dist/vendor/core/external-outcome.d.ts +34 -0
- package/dist/vendor/core/external-outcome.js +515 -0
- package/dist/vendor/core/grounding.js +1 -1
- package/dist/vendor/core/index.d.ts +47 -1
- package/dist/vendor/core/index.js +53 -3
- package/dist/vendor/core/leash.d.ts +1 -0
- package/dist/vendor/core/leash.js +13 -0
- package/dist/vendor/core/persistence/integrity.d.ts +4 -0
- package/dist/vendor/core/persistence/integrity.js +19 -6
- package/dist/vendor/core/persistence/swarm-integrity.d.ts +63 -0
- package/dist/vendor/core/persistence/swarm-integrity.js +309 -0
- package/dist/vendor/core/swarm/candidates.d.ts +78 -0
- package/dist/vendor/core/swarm/candidates.js +810 -0
- package/dist/vendor/core/swarm/evidence-projection.d.ts +87 -0
- package/dist/vendor/core/swarm/evidence-projection.js +128 -0
- package/dist/vendor/core/swarm/evidence.d.ts +40 -0
- package/dist/vendor/core/swarm/evidence.js +575 -0
- package/dist/vendor/core/swarm/hosted-export.d.ts +48 -0
- package/dist/vendor/core/swarm/hosted-export.js +362 -0
- package/dist/vendor/core/swarm/hosted-projection.d.ts +5 -0
- package/dist/vendor/core/swarm/hosted-projection.js +258 -0
- package/dist/vendor/core/swarm/index.d.ts +110 -0
- package/dist/vendor/core/swarm/index.js +1060 -0
- package/dist/vendor/core/swarm/integration.d.ts +76 -0
- package/dist/vendor/core/swarm/integration.js +615 -0
- package/dist/vendor/core/swarm/lifecycle.d.ts +72 -0
- package/dist/vendor/core/swarm/lifecycle.js +252 -0
- package/dist/vendor/core/swarm/live-runtime.d.ts +78 -0
- package/dist/vendor/core/swarm/live-runtime.js +1482 -0
- package/dist/vendor/core/swarm/live-store.d.ts +99 -0
- package/dist/vendor/core/swarm/live-store.js +860 -0
- package/dist/vendor/core/swarm/operational-reader.d.ts +17 -0
- package/dist/vendor/core/swarm/operational-reader.js +96 -0
- package/dist/vendor/core/swarm/operations.d.ts +29 -0
- package/dist/vendor/core/swarm/operations.js +128 -0
- package/dist/vendor/core/swarm/parent-receipt.d.ts +13 -0
- package/dist/vendor/core/swarm/parent-receipt.js +489 -0
- package/dist/vendor/core/swarm/receipt-projection.d.ts +26 -0
- package/dist/vendor/core/swarm/receipt-projection.js +120 -0
- package/dist/vendor/core/swarm/scheduler.d.ts +92 -0
- package/dist/vendor/core/swarm/scheduler.js +555 -0
- package/dist/vendor/core/swarm/verification.d.ts +70 -0
- package/dist/vendor/core/swarm/verification.js +295 -0
- package/dist/vendor/core/swarm/workspaces.d.ts +77 -0
- package/dist/vendor/core/swarm/workspaces.js +488 -0
- package/dist/vendor/core/verified-handoff.d.ts +4 -0
- package/dist/vendor/core/verified-handoff.js +4 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { FAILURE_CLASSES } from "./index.js";
|
|
3
|
+
import { SWARM_EVENT_TYPES, SWARM_PARENT_RECEIPT_SCHEMA_VERSION, SWARM_SCHEMA_VERSION, } from "./swarm.js";
|
|
4
|
+
export const SWARM_HOSTED_SCHEMA_VERSION = "martin.swarm-hosted.v1";
|
|
5
|
+
export const SWARM_EVIDENCE_INDEX_SCHEMA_VERSION = "martin.swarm-evidence-index.v1";
|
|
6
|
+
export function canonicalSwarmHostedTransportBytes(envelope) {
|
|
7
|
+
const { transportSignature: _signature, ...unsigned } = envelope;
|
|
8
|
+
return `${JSON.stringify(sortJson(unsigned))}\n`;
|
|
9
|
+
}
|
|
10
|
+
export function computeSwarmHostedEnvelopeIdentity(envelope) {
|
|
11
|
+
const { envelopeId: _identity, transportSignature: _signature, ...identityMaterial } = envelope;
|
|
12
|
+
return createHash("sha256").update(JSON.stringify(sortJson(identityMaterial))).digest("hex");
|
|
13
|
+
}
|
|
14
|
+
export function validateSwarmHostedEnvelope(value) {
|
|
15
|
+
const errors = [];
|
|
16
|
+
if (!isRecord(value)) {
|
|
17
|
+
add(errors, "INVALID_HOSTED_ENVELOPE", "envelope", "must be an object");
|
|
18
|
+
return invalid(errors);
|
|
19
|
+
}
|
|
20
|
+
exactKeys(value, [
|
|
21
|
+
"schemaVersion", "envelopeId", "sourceSchemas", "sourceIdentities", "runtimeVersion", "createdAt", "swarm", "topology",
|
|
22
|
+
"budget", "interventions", "integration", "events", "globalVerification", "parentOutcome",
|
|
23
|
+
"taskVerificationState", "receiptIntegrityState", "transportSignature", "extensions",
|
|
24
|
+
], "envelope", errors);
|
|
25
|
+
equal(value.schemaVersion, SWARM_HOSTED_SCHEMA_VERSION, "schemaVersion", errors);
|
|
26
|
+
hex(value.envelopeId, 64, "envelopeId", errors);
|
|
27
|
+
safeIdentifier(value.runtimeVersion, "runtimeVersion", errors);
|
|
28
|
+
timestamp(value.createdAt, "createdAt", errors);
|
|
29
|
+
const schemas = object(value.sourceSchemas, "sourceSchemas", errors);
|
|
30
|
+
if (schemas) {
|
|
31
|
+
exactKeys(schemas, ["swarm", "receipt", "evidenceIndex"], "sourceSchemas", errors);
|
|
32
|
+
equal(schemas.swarm, SWARM_SCHEMA_VERSION, "sourceSchemas.swarm", errors);
|
|
33
|
+
equal(schemas.receipt, SWARM_PARENT_RECEIPT_SCHEMA_VERSION, "sourceSchemas.receipt", errors);
|
|
34
|
+
equal(schemas.evidenceIndex, SWARM_EVIDENCE_INDEX_SCHEMA_VERSION, "sourceSchemas.evidenceIndex", errors);
|
|
35
|
+
}
|
|
36
|
+
const sourceIdentities = object(value.sourceIdentities, "sourceIdentities", errors);
|
|
37
|
+
if (sourceIdentities) {
|
|
38
|
+
exactKeys(sourceIdentities, ["receiptId", "receiptSha256", "evidenceIndexSha256", "eventChainSha256"], "sourceIdentities", errors);
|
|
39
|
+
safeIdentifier(sourceIdentities.receiptId, "sourceIdentities.receiptId", errors);
|
|
40
|
+
hex(sourceIdentities.receiptSha256, 64, "sourceIdentities.receiptSha256", errors);
|
|
41
|
+
hex(sourceIdentities.evidenceIndexSha256, 64, "sourceIdentities.evidenceIndexSha256", errors);
|
|
42
|
+
hex(sourceIdentities.eventChainSha256, 64, "sourceIdentities.eventChainSha256", errors);
|
|
43
|
+
}
|
|
44
|
+
const swarm = object(value.swarm, "swarm", errors);
|
|
45
|
+
if (swarm) {
|
|
46
|
+
exactKeys(swarm, ["swarmId", "workspaceId", "projectId", "planHash", "baselineCommit"], "swarm", errors);
|
|
47
|
+
for (const key of ["swarmId", "workspaceId", "projectId"])
|
|
48
|
+
safeIdentifier(swarm[key], `swarm.${key}`, errors);
|
|
49
|
+
hex(swarm.planHash, 64, "swarm.planHash", errors);
|
|
50
|
+
hex(swarm.baselineCommit, 40, "swarm.baselineCommit", errors);
|
|
51
|
+
}
|
|
52
|
+
const topology = object(value.topology, "topology", errors);
|
|
53
|
+
const tasks = topology ? array(topology.tasks, "topology.tasks", errors) : [];
|
|
54
|
+
const agents = topology ? array(topology.agents, "topology.agents", errors) : [];
|
|
55
|
+
const children = topology ? array(topology.children, "topology.children", errors) : [];
|
|
56
|
+
if (topology)
|
|
57
|
+
exactKeys(topology, ["tasks", "agents", "children"], "topology", errors);
|
|
58
|
+
if (tasks.length === 0)
|
|
59
|
+
add(errors, "EMPTY_HOSTED_TASKS", "topology.tasks", "must contain at least one task");
|
|
60
|
+
if (agents.length === 0)
|
|
61
|
+
add(errors, "EMPTY_HOSTED_AGENTS", "topology.agents", "must contain at least one agent");
|
|
62
|
+
if (children.length === 0)
|
|
63
|
+
add(errors, "EMPTY_HOSTED_CHILDREN", "topology.children", "must contain at least one child");
|
|
64
|
+
const taskIds = new Set();
|
|
65
|
+
tasks.forEach((item, index) => validateTask(item, index, taskIds, errors));
|
|
66
|
+
const agentIds = new Set();
|
|
67
|
+
agents.forEach((item, index) => validateAgent(item, index, agentIds, taskIds, errors));
|
|
68
|
+
tasks.forEach((item, index) => validateTaskLinks(item, index, taskIds, agentIds, errors));
|
|
69
|
+
const eventList = array(value.events, "events", errors);
|
|
70
|
+
const events = new Map();
|
|
71
|
+
eventList.forEach((item, index) => validateEvent(item, index, events, taskIds, agentIds, errors));
|
|
72
|
+
for (let index = 0; index < eventList.length; index += 1) {
|
|
73
|
+
const event = eventList[index];
|
|
74
|
+
if (event?.sequence !== index + 1)
|
|
75
|
+
add(errors, "NON_CONTIGUOUS_HOSTED_EVENTS", `events[${index}].sequence`, `must equal ${index + 1}`);
|
|
76
|
+
}
|
|
77
|
+
const childRunIds = new Set();
|
|
78
|
+
children.forEach((item, index) => validateChild(item, index, childRunIds, taskIds, agentIds, events, errors));
|
|
79
|
+
eventList.forEach((item, index) => validateEventChildLink(item, index, childRunIds, errors));
|
|
80
|
+
validateTopologyConsistency(tasks, agents, children, eventList, events, errors);
|
|
81
|
+
validateBudget(value.budget, errors);
|
|
82
|
+
validateInterventions(value.interventions, taskIds, agentIds, events, errors);
|
|
83
|
+
validateIntegration(value.integration, taskIds, agentIds, childRunIds, events, errors);
|
|
84
|
+
validateGlobal(value.globalVerification, events, errors);
|
|
85
|
+
validateParent(value.parentOutcome, events, errors);
|
|
86
|
+
validateLatestGlobalAuthority(value.globalVerification, value.parentOutcome, eventList, errors);
|
|
87
|
+
const integration = isRecord(value.integration) ? value.integration : undefined;
|
|
88
|
+
const globalVerification = isRecord(value.globalVerification) ? value.globalVerification : undefined;
|
|
89
|
+
if (typeof integration?.integratedTreeHash === "string"
|
|
90
|
+
&& typeof globalVerification?.integratedTreeHash === "string"
|
|
91
|
+
&& integration.integratedTreeHash !== globalVerification.integratedTreeHash) {
|
|
92
|
+
add(errors, "HOSTED_INTEGRATED_TREE_MISMATCH", "globalVerification.integratedTreeHash", "must match the exact integrated tree verified by the parent");
|
|
93
|
+
}
|
|
94
|
+
enumValue(value.taskVerificationState, ["passed", "failed", "unknown"], "taskVerificationState", errors);
|
|
95
|
+
enumValue(value.receiptIntegrityState, RECEIPT_INTEGRITY_STATES, "receiptIntegrityState", errors);
|
|
96
|
+
validateSignature(value.transportSignature, errors);
|
|
97
|
+
validateExtensions(value.extensions, errors);
|
|
98
|
+
const parent = isRecord(value.parentOutcome) ? value.parentOutcome : undefined;
|
|
99
|
+
const global = isRecord(value.globalVerification) ? value.globalVerification : undefined;
|
|
100
|
+
if (parent?.state === "verified" && (global?.state !== "passed"
|
|
101
|
+
|| value.taskVerificationState !== "passed"
|
|
102
|
+
|| value.receiptIntegrityState !== "verified")) {
|
|
103
|
+
add(errors, "INVALID_HOSTED_AUTHORITY_BINDING", "parentOutcome.state", "verified parent requires persisted passed task/global states and verified receipt integrity");
|
|
104
|
+
}
|
|
105
|
+
if (errors.length === 0) {
|
|
106
|
+
const envelope = value;
|
|
107
|
+
if (envelope.envelopeId !== computeSwarmHostedEnvelopeIdentity(envelope)) {
|
|
108
|
+
add(errors, "HOSTED_ENVELOPE_ID_MISMATCH", "envelopeId", "must bind the canonical hosted identity material");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return errors.length === 0 ? { ok: true, errors: [] } : invalid(errors);
|
|
112
|
+
}
|
|
113
|
+
const RECEIPT_INTEGRITY_STATES = [
|
|
114
|
+
"verified", "unsigned", "tamper_detected", "relocated", "material_missing", "selector_noncanonical",
|
|
115
|
+
];
|
|
116
|
+
const TASK_STATUSES = ["queued", "ready", "running", "accepted", "rejected", "stopped", "needs_review"];
|
|
117
|
+
const AGENT_STATUSES = ["queued", "running", "verified", "stopped", "needs_review"];
|
|
118
|
+
function validateTask(value, index, ids, errors) {
|
|
119
|
+
const path = `topology.tasks[${index}]`;
|
|
120
|
+
const item = object(value, path, errors);
|
|
121
|
+
if (!item)
|
|
122
|
+
return;
|
|
123
|
+
exactKeys(item, ["taskId", "required", "dependsOn", "plannedAgentId", "effectiveAgentId", "status"], path, errors);
|
|
124
|
+
uniqueIdentifier(item.taskId, `${path}.taskId`, ids, errors);
|
|
125
|
+
if (typeof item.required !== "boolean")
|
|
126
|
+
add(errors, "INVALID_HOSTED_BOOLEAN", `${path}.required`, "must be boolean");
|
|
127
|
+
stringArray(item.dependsOn, `${path}.dependsOn`, errors);
|
|
128
|
+
optionalIdentifier(item.plannedAgentId, `${path}.plannedAgentId`, errors);
|
|
129
|
+
optionalIdentifier(item.effectiveAgentId, `${path}.effectiveAgentId`, errors);
|
|
130
|
+
enumValue(item.status, TASK_STATUSES, `${path}.status`, errors);
|
|
131
|
+
}
|
|
132
|
+
function validateTaskLinks(value, index, taskIds, agentIds, errors) {
|
|
133
|
+
if (!isRecord(value))
|
|
134
|
+
return;
|
|
135
|
+
for (const [depIndex, dependency] of (Array.isArray(value.dependsOn) ? value.dependsOn : []).entries()) {
|
|
136
|
+
if (!taskIds.has(String(dependency)) || dependency === value.taskId)
|
|
137
|
+
add(errors, "DANGLING_HOSTED_TASK_LINK", `topology.tasks[${index}].dependsOn[${depIndex}]`, "must reference another hosted task");
|
|
138
|
+
}
|
|
139
|
+
for (const field of ["plannedAgentId", "effectiveAgentId"]) {
|
|
140
|
+
if (typeof value[field] === "string" && !agentIds.has(value[field]))
|
|
141
|
+
add(errors, "DANGLING_HOSTED_AGENT_LINK", `topology.tasks[${index}].${field}`, "must reference a hosted agent");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function validateAgent(value, index, ids, taskIds, errors) {
|
|
145
|
+
const path = `topology.agents[${index}]`;
|
|
146
|
+
const item = object(value, path, errors);
|
|
147
|
+
if (!item)
|
|
148
|
+
return;
|
|
149
|
+
exactKeys(item, ["agentId", "role", "status", "taskIds", "childRunId"], path, errors);
|
|
150
|
+
uniqueIdentifier(item.agentId, `${path}.agentId`, ids, errors);
|
|
151
|
+
safeIdentifier(item.role, `${path}.role`, errors);
|
|
152
|
+
enumValue(item.status, AGENT_STATUSES, `${path}.status`, errors);
|
|
153
|
+
linkedStringArray(item.taskIds, `${path}.taskIds`, taskIds, errors);
|
|
154
|
+
optionalIdentifier(item.childRunId, `${path}.childRunId`, errors);
|
|
155
|
+
}
|
|
156
|
+
function validateChild(value, index, ids, taskIds, agentIds, events, errors) {
|
|
157
|
+
const path = `topology.children[${index}]`;
|
|
158
|
+
const item = object(value, path, errors);
|
|
159
|
+
if (!item)
|
|
160
|
+
return;
|
|
161
|
+
exactKeys(item, ["childRunId", "agentId", "attemptId", "taskIds", "status", "receiptIntegrityState", "evidence"], path, errors);
|
|
162
|
+
uniqueIdentifier(item.childRunId, `${path}.childRunId`, ids, errors);
|
|
163
|
+
linkedText(item.agentId, `${path}.agentId`, agentIds, errors);
|
|
164
|
+
safeIdentifier(item.attemptId, `${path}.attemptId`, errors);
|
|
165
|
+
linkedStringArray(item.taskIds, `${path}.taskIds`, taskIds, errors);
|
|
166
|
+
enumValue(item.status, AGENT_STATUSES, `${path}.status`, errors);
|
|
167
|
+
enumValue(item.receiptIntegrityState, RECEIPT_INTEGRITY_STATES, `${path}.receiptIntegrityState`, errors);
|
|
168
|
+
validateEvidence(item.evidence, `${path}.evidence`, events, errors);
|
|
169
|
+
}
|
|
170
|
+
function validateEvent(value, index, events, taskIds, agentIds, errors) {
|
|
171
|
+
const path = `events[${index}]`;
|
|
172
|
+
const item = object(value, path, errors);
|
|
173
|
+
if (!item)
|
|
174
|
+
return;
|
|
175
|
+
exactKeys(item, ["eventId", "sourceIdempotencyKey", "sequence", "type", "timestamp", "taskId", "agentId", "childRunId", "attemptId", "relatedAttemptId", "toAgentId", "candidateId", "verificationId", "failureClass"], path, errors);
|
|
176
|
+
safeIdentifier(item.eventId, `${path}.eventId`, errors);
|
|
177
|
+
safeIdentifier(item.sourceIdempotencyKey, `${path}.sourceIdempotencyKey`, errors);
|
|
178
|
+
if (typeof item.eventId === "string" && item.eventId.length > 0) {
|
|
179
|
+
if (events.has(item.eventId))
|
|
180
|
+
add(errors, "DUPLICATE_HOSTED_EVENT_ID", `${path}.eventId`, "must be unique");
|
|
181
|
+
else
|
|
182
|
+
events.set(item.eventId, item);
|
|
183
|
+
}
|
|
184
|
+
if (item.eventId !== item.sourceIdempotencyKey)
|
|
185
|
+
add(errors, "HOSTED_EVENT_IDEMPOTENCY_MISMATCH", `${path}.sourceIdempotencyKey`, "must exactly match the persisted event idempotency identity");
|
|
186
|
+
integer(item.sequence, `${path}.sequence`, errors);
|
|
187
|
+
enumValue(item.type, SWARM_EVENT_TYPES, `${path}.type`, errors);
|
|
188
|
+
timestamp(item.timestamp, `${path}.timestamp`, errors);
|
|
189
|
+
optionalLinkedText(item.taskId, `${path}.taskId`, taskIds, errors);
|
|
190
|
+
optionalLinkedText(item.agentId, `${path}.agentId`, agentIds, errors);
|
|
191
|
+
for (const key of ["childRunId", "attemptId", "relatedAttemptId", "candidateId", "verificationId"])
|
|
192
|
+
optionalIdentifier(item[key], `${path}.${key}`, errors);
|
|
193
|
+
optionalLinkedText(item.toAgentId, `${path}.toAgentId`, agentIds, errors);
|
|
194
|
+
if (item.failureClass !== undefined)
|
|
195
|
+
enumValue(item.failureClass, FAILURE_CLASSES, `${path}.failureClass`, errors);
|
|
196
|
+
validateEventTypeIdentities(item, path, errors);
|
|
197
|
+
}
|
|
198
|
+
function validateEventChildLink(value, index, childIds, errors) {
|
|
199
|
+
if (isRecord(value) && typeof value.childRunId === "string" && !childIds.has(value.childRunId))
|
|
200
|
+
add(errors, "DANGLING_HOSTED_CHILD_LINK", `events[${index}].childRunId`, "must reference a hosted child");
|
|
201
|
+
}
|
|
202
|
+
function validateTopologyConsistency(rawTasks, rawAgents, rawChildren, rawEvents, events, errors) {
|
|
203
|
+
const tasks = new Map(rawTasks.filter(isRecord).filter((task) => typeof task.taskId === "string").map((task) => [task.taskId, task]));
|
|
204
|
+
const agents = new Map(rawAgents.filter(isRecord).filter((agent) => typeof agent.agentId === "string").map((agent) => [agent.agentId, agent]));
|
|
205
|
+
const children = new Map(rawChildren.filter(isRecord).filter((child) => typeof child.childRunId === "string").map((child) => [child.childRunId, child]));
|
|
206
|
+
for (const [taskId, task] of tasks) {
|
|
207
|
+
for (const field of ["plannedAgentId", "effectiveAgentId"]) {
|
|
208
|
+
if (typeof task[field] !== "string")
|
|
209
|
+
continue;
|
|
210
|
+
const agent = agents.get(task[field]);
|
|
211
|
+
if (!agent || !Array.isArray(agent.taskIds) || !agent.taskIds.includes(taskId)) {
|
|
212
|
+
add(errors, "HOSTED_TASK_AGENT_ASSIGNMENT_MISMATCH", `topology.tasks.${taskId}.${field}`, "assigned agent must include the exact task identity");
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
for (const [agentId, agent] of agents) {
|
|
217
|
+
for (const taskId of Array.isArray(agent.taskIds) ? agent.taskIds : []) {
|
|
218
|
+
const task = typeof taskId === "string" ? tasks.get(taskId) : undefined;
|
|
219
|
+
if (task && task.plannedAgentId !== agentId && task.effectiveAgentId !== agentId) {
|
|
220
|
+
add(errors, "HOSTED_AGENT_TASK_ASSIGNMENT_MISMATCH", `topology.agents.${agentId}.taskIds`, "task must name the agent as planned or effective assignee");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (typeof agent.childRunId === "string") {
|
|
224
|
+
const child = children.get(agent.childRunId);
|
|
225
|
+
if (!child || child.agentId !== agentId)
|
|
226
|
+
add(errors, "HOSTED_AGENT_CHILD_MISMATCH", `topology.agents.${agentId}.childRunId`, "must bind the agent's exact child identity");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (const [childRunId, child] of children) {
|
|
230
|
+
const agent = typeof child.agentId === "string" ? agents.get(child.agentId) : undefined;
|
|
231
|
+
if (!agent || agent.childRunId !== childRunId)
|
|
232
|
+
add(errors, "HOSTED_CHILD_AGENT_MISMATCH", `topology.children.${childRunId}.agentId`, "must bind the exact agent child identity");
|
|
233
|
+
for (const taskId of Array.isArray(child.taskIds) ? child.taskIds : []) {
|
|
234
|
+
if (!agent || !Array.isArray(agent.taskIds) || !agent.taskIds.includes(taskId))
|
|
235
|
+
add(errors, "HOSTED_CHILD_TASK_MISMATCH", `topology.children.${childRunId}.taskIds`, "child tasks must be assigned to its exact agent");
|
|
236
|
+
}
|
|
237
|
+
const referenced = referencedEvents(child.evidence, events);
|
|
238
|
+
if (referenced.length === 0)
|
|
239
|
+
add(errors, "EMPTY_HOSTED_CHILD_EVIDENCE", `topology.children.${childRunId}.evidence`, "must bind at least one exact child event");
|
|
240
|
+
for (const event of referenced) {
|
|
241
|
+
if (event.childRunId !== childRunId || event.agentId !== child.agentId || event.attemptId !== child.attemptId || (event.taskId !== undefined && !Array.isArray(child.taskIds)) || (event.taskId !== undefined && !child.taskIds.includes(event.taskId))) {
|
|
242
|
+
add(errors, "HOSTED_CHILD_EVIDENCE_MISMATCH", `topology.children.${childRunId}.evidence`, "must bind exact child agent attempt and task identities");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
rawEvents.filter(isRecord).forEach((event, index) => {
|
|
247
|
+
if (typeof event.taskId === "string" && typeof event.agentId === "string") {
|
|
248
|
+
const agent = agents.get(event.agentId);
|
|
249
|
+
const task = tasks.get(event.taskId);
|
|
250
|
+
if (!agent || !Array.isArray(agent.taskIds) || !agent.taskIds.includes(event.taskId)
|
|
251
|
+
|| !task || (task.plannedAgentId !== event.agentId && task.effectiveAgentId !== event.agentId)) {
|
|
252
|
+
add(errors, "HOSTED_EVENT_ASSIGNMENT_MISMATCH", `events[${index}]`, "event task and agent must match planned or effective topology");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (typeof event.childRunId === "string") {
|
|
256
|
+
const child = children.get(event.childRunId);
|
|
257
|
+
if (!child || child.agentId !== event.agentId || child.attemptId !== event.attemptId || (typeof event.taskId === "string" && (!Array.isArray(child.taskIds) || !child.taskIds.includes(event.taskId)))) {
|
|
258
|
+
add(errors, "HOSTED_EVENT_CHILD_IDENTITY_MISMATCH", `events[${index}]`, "event child must match exact agent attempt and task identities");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (event.type === "TASK_REASSIGNED") {
|
|
262
|
+
const fromChild = [...children.values()].find((child) => child.agentId === event.agentId
|
|
263
|
+
&& child.attemptId === event.attemptId
|
|
264
|
+
&& Array.isArray(child.taskIds)
|
|
265
|
+
&& child.taskIds.includes(event.taskId));
|
|
266
|
+
const toChild = [...children.values()].find((child) => child.agentId === event.toAgentId
|
|
267
|
+
&& child.attemptId === event.relatedAttemptId
|
|
268
|
+
&& Array.isArray(child.taskIds)
|
|
269
|
+
&& child.taskIds.includes(event.taskId));
|
|
270
|
+
if (!fromChild || !toChild)
|
|
271
|
+
add(errors, "HOSTED_REASSIGNMENT_ATTEMPT_MISMATCH", `events[${index}]`, "from and to attempts must resolve to real child identities for the exact task and agents");
|
|
272
|
+
else if (event.agentId === event.toAgentId
|
|
273
|
+
|| event.attemptId === event.relatedAttemptId
|
|
274
|
+
|| fromChild.childRunId === toChild.childRunId) {
|
|
275
|
+
add(errors, "HOSTED_REASSIGNMENT_NOOP", `events[${index}]`, "reassignment must move to a distinct agent attempt and child identity");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
function validateEventTypeIdentities(item, path, errors) {
|
|
281
|
+
const type = item.type;
|
|
282
|
+
const childTypes = [
|
|
283
|
+
"CHILD_STARTED", "CHILD_PROGRESS", "CHILD_VERIFIER_STARTED", "CHILD_VERIFIED", "CHILD_STOPPED",
|
|
284
|
+
"CHILD_NEEDS_REVIEW", "CHILD_PATCH_PROPOSED", "CHILD_PATCH_REJECTED", "CHILD_PATCH_ADMITTED",
|
|
285
|
+
];
|
|
286
|
+
if (typeof type === "string" && childTypes.includes(type)) {
|
|
287
|
+
for (const field of ["taskId", "agentId", "childRunId", "attemptId"])
|
|
288
|
+
requireIdentifier(item[field], `${path}.${field}`, errors);
|
|
289
|
+
}
|
|
290
|
+
if (type === "CHILD_PATCH_PROPOSED" || type === "CHILD_PATCH_REJECTED" || type === "CHILD_PATCH_ADMITTED")
|
|
291
|
+
requireIdentifier(item.candidateId, `${path}.candidateId`, errors);
|
|
292
|
+
if (type === "TASK_REASSIGNED")
|
|
293
|
+
for (const field of ["taskId", "agentId", "attemptId", "relatedAttemptId", "toAgentId"])
|
|
294
|
+
requireIdentifier(item[field], `${path}.${field}`, errors);
|
|
295
|
+
if (type === "ACTION_BLOCKED")
|
|
296
|
+
for (const field of ["taskId", "agentId", "attemptId"])
|
|
297
|
+
requireIdentifier(item[field], `${path}.${field}`, errors);
|
|
298
|
+
if (type === "GLOBAL_VERIFIER_PASSED" || type === "GLOBAL_VERIFIER_FAILED")
|
|
299
|
+
requireIdentifier(item.verificationId, `${path}.verificationId`, errors);
|
|
300
|
+
}
|
|
301
|
+
function validateBudget(value, errors) {
|
|
302
|
+
const item = object(value, "budget", errors);
|
|
303
|
+
if (!item)
|
|
304
|
+
return;
|
|
305
|
+
exactKeys(item, ["capUsd", "capTokens", "settledUsd", "settledTokens"], "budget", errors);
|
|
306
|
+
for (const key of ["capUsd", "settledUsd", "settledTokens"])
|
|
307
|
+
nonnegative(item[key], `budget.${key}`, errors);
|
|
308
|
+
if (item.capTokens !== undefined)
|
|
309
|
+
nonnegative(item.capTokens, "budget.capTokens", errors);
|
|
310
|
+
if (typeof item.capUsd === "number" && typeof item.settledUsd === "number" && item.settledUsd > item.capUsd)
|
|
311
|
+
add(errors, "HOSTED_BUDGET_EXCEEDED", "budget.settledUsd", "must not exceed capUsd");
|
|
312
|
+
}
|
|
313
|
+
function validateInterventions(value, taskIds, agentIds, events, errors) {
|
|
314
|
+
const item = object(value, "interventions", errors);
|
|
315
|
+
if (!item)
|
|
316
|
+
return;
|
|
317
|
+
exactKeys(item, ["blockedActions", "reassignments"], "interventions", errors);
|
|
318
|
+
array(item.blockedActions, "interventions.blockedActions", errors).forEach((entry, index) => {
|
|
319
|
+
const path = `interventions.blockedActions[${index}]`;
|
|
320
|
+
const action = object(entry, path, errors);
|
|
321
|
+
if (!action)
|
|
322
|
+
return;
|
|
323
|
+
exactKeys(action, ["action", "reasonCode", "taskId", "agentId", "attemptId", "evidence"], path, errors);
|
|
324
|
+
safeCodeIdentifier(action.action, `${path}.action`, errors);
|
|
325
|
+
safeCodeIdentifier(action.reasonCode, `${path}.reasonCode`, errors);
|
|
326
|
+
linkedText(action.taskId, `${path}.taskId`, taskIds, errors);
|
|
327
|
+
linkedText(action.agentId, `${path}.agentId`, agentIds, errors);
|
|
328
|
+
safeIdentifier(action.attemptId, `${path}.attemptId`, errors);
|
|
329
|
+
validateEvidence(action.evidence, `${path}.evidence`, events, errors);
|
|
330
|
+
const actionEvent = singleReferencedEvent(action.evidence, events, `${path}.evidence`, errors);
|
|
331
|
+
if (actionEvent && (actionEvent.type !== "ACTION_BLOCKED" || actionEvent.taskId !== action.taskId || actionEvent.agentId !== action.agentId || actionEvent.attemptId !== action.attemptId)) {
|
|
332
|
+
add(errors, "HOSTED_BLOCKED_ACTION_EVIDENCE_MISMATCH", `${path}.evidence`, "must bind the exact blocked action event identities");
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
array(item.reassignments, "interventions.reassignments", errors).forEach((entry, index) => {
|
|
336
|
+
const path = `interventions.reassignments[${index}]`;
|
|
337
|
+
const reassignment = object(entry, path, errors);
|
|
338
|
+
if (!reassignment)
|
|
339
|
+
return;
|
|
340
|
+
exactKeys(reassignment, ["taskId", "fromAgentId", "toAgentId", "fromAttemptId", "toAttemptId", "reasonCode", "evidence"], path, errors);
|
|
341
|
+
linkedText(reassignment.taskId, `${path}.taskId`, taskIds, errors);
|
|
342
|
+
linkedText(reassignment.fromAgentId, `${path}.fromAgentId`, agentIds, errors);
|
|
343
|
+
linkedText(reassignment.toAgentId, `${path}.toAgentId`, agentIds, errors);
|
|
344
|
+
safeIdentifier(reassignment.fromAttemptId, `${path}.fromAttemptId`, errors);
|
|
345
|
+
safeIdentifier(reassignment.toAttemptId, `${path}.toAttemptId`, errors);
|
|
346
|
+
safeCodeIdentifier(reassignment.reasonCode, `${path}.reasonCode`, errors);
|
|
347
|
+
validateEvidence(reassignment.evidence, `${path}.evidence`, events, errors);
|
|
348
|
+
const reassignmentEvent = singleReferencedEvent(reassignment.evidence, events, `${path}.evidence`, errors);
|
|
349
|
+
if (reassignmentEvent && (reassignmentEvent.type !== "TASK_REASSIGNED"
|
|
350
|
+
|| reassignmentEvent.taskId !== reassignment.taskId
|
|
351
|
+
|| reassignmentEvent.agentId !== reassignment.fromAgentId
|
|
352
|
+
|| reassignmentEvent.toAgentId !== reassignment.toAgentId
|
|
353
|
+
|| reassignmentEvent.attemptId !== reassignment.fromAttemptId
|
|
354
|
+
|| reassignmentEvent.relatedAttemptId !== reassignment.toAttemptId)) {
|
|
355
|
+
add(errors, "HOSTED_REASSIGNMENT_EVIDENCE_MISMATCH", `${path}.evidence`, "must bind the exact reassignment event identities");
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
function validateIntegration(value, taskIds, agentIds, childIds, events, errors) {
|
|
360
|
+
const item = object(value, "integration", errors);
|
|
361
|
+
if (!item)
|
|
362
|
+
return;
|
|
363
|
+
exactKeys(item, ["admissions", "rejections", "conflicts", "integratedTreeHash"], "integration", errors);
|
|
364
|
+
for (const lane of ["admissions", "rejections"])
|
|
365
|
+
array(item[lane], `integration.${lane}`, errors).forEach((entry, index) => {
|
|
366
|
+
const path = `integration.${lane}[${index}]`;
|
|
367
|
+
const decision = object(entry, path, errors);
|
|
368
|
+
if (!decision)
|
|
369
|
+
return;
|
|
370
|
+
exactKeys(decision, ["candidateId", "taskId", "agentId", "childRunId", "state", "evidence"], path, errors);
|
|
371
|
+
safeIdentifier(decision.candidateId, `${path}.candidateId`, errors);
|
|
372
|
+
linkedText(decision.taskId, `${path}.taskId`, taskIds, errors);
|
|
373
|
+
linkedText(decision.agentId, `${path}.agentId`, agentIds, errors);
|
|
374
|
+
linkedText(decision.childRunId, `${path}.childRunId`, childIds, errors);
|
|
375
|
+
equal(decision.state, lane === "admissions" ? "admitted" : "rejected", `${path}.state`, errors);
|
|
376
|
+
validateEvidence(decision.evidence, `${path}.evidence`, events, errors);
|
|
377
|
+
const decisionEvent = singleReferencedEvent(decision.evidence, events, `${path}.evidence`, errors);
|
|
378
|
+
const expectedType = lane === "admissions" ? "CHILD_PATCH_ADMITTED" : "CHILD_PATCH_REJECTED";
|
|
379
|
+
if (decisionEvent && (decisionEvent.type !== expectedType
|
|
380
|
+
|| decisionEvent.candidateId !== decision.candidateId
|
|
381
|
+
|| decisionEvent.taskId !== decision.taskId
|
|
382
|
+
|| decisionEvent.agentId !== decision.agentId
|
|
383
|
+
|| decisionEvent.childRunId !== decision.childRunId)) {
|
|
384
|
+
add(errors, "HOSTED_INTEGRATION_EVIDENCE_MISMATCH", `${path}.evidence`, "must bind the exact integration decision event identities");
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
array(item.conflicts, "integration.conflicts", errors).forEach((entry, index) => {
|
|
388
|
+
const path = `integration.conflicts[${index}]`;
|
|
389
|
+
const conflict = object(entry, path, errors);
|
|
390
|
+
if (!conflict)
|
|
391
|
+
return;
|
|
392
|
+
exactKeys(conflict, ["conflictId", "taskIds", "state", "evidence"], path, errors);
|
|
393
|
+
safeIdentifier(conflict.conflictId, `${path}.conflictId`, errors);
|
|
394
|
+
linkedStringArray(conflict.taskIds, `${path}.taskIds`, taskIds, errors);
|
|
395
|
+
enumValue(conflict.state, ["detected", "resolved", "blocking"], `${path}.state`, errors);
|
|
396
|
+
validateEvidence(conflict.evidence, `${path}.evidence`, events, errors);
|
|
397
|
+
});
|
|
398
|
+
if (item.integratedTreeHash !== undefined)
|
|
399
|
+
hex(item.integratedTreeHash, 40, "integration.integratedTreeHash", errors);
|
|
400
|
+
}
|
|
401
|
+
function validateGlobal(value, events, errors) {
|
|
402
|
+
const item = object(value, "globalVerification", errors);
|
|
403
|
+
if (!item)
|
|
404
|
+
return;
|
|
405
|
+
exactKeys(item, ["state", "verificationId", "integratedTreeHash", "evidence"], "globalVerification", errors);
|
|
406
|
+
enumValue(item.state, ["passed", "failed", "unknown"], "globalVerification.state", errors);
|
|
407
|
+
optionalIdentifier(item.verificationId, "globalVerification.verificationId", errors);
|
|
408
|
+
if (item.state !== "unknown" && typeof item.verificationId !== "string")
|
|
409
|
+
add(errors, "MISSING_HOSTED_VERIFICATION_ID", "globalVerification.verificationId", "is required for passed or failed global verification");
|
|
410
|
+
if (item.integratedTreeHash !== undefined)
|
|
411
|
+
hex(item.integratedTreeHash, 40, "globalVerification.integratedTreeHash", errors);
|
|
412
|
+
validateEvidence(item.evidence, "globalVerification.evidence", events, errors);
|
|
413
|
+
const evidence = Array.isArray(item.evidence) ? item.evidence : [];
|
|
414
|
+
if (item.state === "unknown") {
|
|
415
|
+
if (item.verificationId !== undefined || evidence.length !== 0)
|
|
416
|
+
add(errors, "INVALID_UNKNOWN_GLOBAL_EVIDENCE", "globalVerification", "unknown verification must not claim an ID or authoritative event evidence");
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
const verificationEvent = singleReferencedEvent(item.evidence, events, "globalVerification.evidence", errors);
|
|
420
|
+
const expectedType = item.state === "passed" ? "GLOBAL_VERIFIER_PASSED" : "GLOBAL_VERIFIER_FAILED";
|
|
421
|
+
if (verificationEvent && (verificationEvent.type !== expectedType || verificationEvent.verificationId !== item.verificationId)) {
|
|
422
|
+
add(errors, "HOSTED_GLOBAL_EVIDENCE_MISMATCH", "globalVerification.evidence", "must bind the exact matching global verifier event");
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function validateParent(value, events, errors) {
|
|
427
|
+
const item = object(value, "parentOutcome", errors);
|
|
428
|
+
if (!item)
|
|
429
|
+
return;
|
|
430
|
+
exactKeys(item, ["state", "source", "evidence"], "parentOutcome", errors);
|
|
431
|
+
enumValue(item.state, ["verified", "stopped", "needs_review"], "parentOutcome.state", errors);
|
|
432
|
+
equal(item.source, "sealed_parent_receipt", "parentOutcome.source", errors);
|
|
433
|
+
validateEvidence(item.evidence, "parentOutcome.evidence", events, errors);
|
|
434
|
+
const outcomeEvent = singleReferencedEvent(item.evidence, events, "parentOutcome.evidence", errors);
|
|
435
|
+
const terminalType = item.state === "verified" ? "SWARM_VERIFIED" : item.state === "stopped" ? "SWARM_STOPPED" : "SWARM_NEEDS_REVIEW";
|
|
436
|
+
if (outcomeEvent && outcomeEvent.type !== terminalType)
|
|
437
|
+
add(errors, "HOSTED_PARENT_EVIDENCE_MISMATCH", "parentOutcome.evidence", "must bind the exact matching authoritative parent terminal event");
|
|
438
|
+
if (outcomeEvent && outcomeEvent.sequence !== events.size)
|
|
439
|
+
add(errors, "HOSTED_PARENT_EVENT_NOT_FINAL", "parentOutcome.evidence", "must bind the final persisted event");
|
|
440
|
+
}
|
|
441
|
+
function validateLatestGlobalAuthority(globalValue, parentValue, eventList, errors) {
|
|
442
|
+
if (!isRecord(globalValue) || !isRecord(parentValue))
|
|
443
|
+
return;
|
|
444
|
+
const globalEvidence = Array.isArray(globalValue.evidence) ? globalValue.evidence[0] : undefined;
|
|
445
|
+
const parentEvidence = Array.isArray(parentValue.evidence) ? parentValue.evidence[0] : undefined;
|
|
446
|
+
if (!isRecord(parentEvidence) || typeof parentEvidence.sequence !== "number")
|
|
447
|
+
return;
|
|
448
|
+
const terminalEvents = eventList
|
|
449
|
+
.filter(isRecord)
|
|
450
|
+
.filter((event) => (event.type === "GLOBAL_VERIFIER_PASSED" || event.type === "GLOBAL_VERIFIER_FAILED")
|
|
451
|
+
&& typeof event.sequence === "number"
|
|
452
|
+
&& event.sequence < parentEvidence.sequence)
|
|
453
|
+
.sort((left, right) => Number(left.sequence) - Number(right.sequence));
|
|
454
|
+
if (globalValue.state === "unknown") {
|
|
455
|
+
if (terminalEvents.length > 0)
|
|
456
|
+
add(errors, "HOSTED_UNKNOWN_GLOBAL_CONTRADICTS_EVENTS", "globalVerification.state", "unknown is allowed only when no persisted terminal global result exists before the parent terminal");
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (!isRecord(globalEvidence))
|
|
460
|
+
return;
|
|
461
|
+
const latest = terminalEvents.at(-1);
|
|
462
|
+
if (!latest || latest.eventId !== globalEvidence.eventId || latest.sequence !== globalEvidence.sequence) {
|
|
463
|
+
add(errors, "HOSTED_GLOBAL_NOT_LATEST_AUTHORITY", "globalVerification.evidence", "must bind the latest persisted global verifier terminal before the final parent event");
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function validateSignature(value, errors) {
|
|
467
|
+
const item = object(value, "transportSignature", errors);
|
|
468
|
+
if (!item)
|
|
469
|
+
return;
|
|
470
|
+
exactKeys(item, ["algorithm", "keyId", "keyLocatorHash", "signatureHmacSha256"], "transportSignature", errors);
|
|
471
|
+
equal(item.algorithm, "hmac-sha256", "transportSignature.algorithm", errors);
|
|
472
|
+
safeIdentifier(item.keyId, "transportSignature.keyId", errors);
|
|
473
|
+
hex(item.keyLocatorHash, 64, "transportSignature.keyLocatorHash", errors);
|
|
474
|
+
hex(item.signatureHmacSha256, 64, "transportSignature.signatureHmacSha256", errors);
|
|
475
|
+
}
|
|
476
|
+
function validateExtensions(value, errors) {
|
|
477
|
+
if (value === undefined)
|
|
478
|
+
return;
|
|
479
|
+
const item = object(value, "extensions", errors);
|
|
480
|
+
if (!item)
|
|
481
|
+
return;
|
|
482
|
+
if (Object.keys(item).length > 32 || JSON.stringify(item).length > 16_384)
|
|
483
|
+
add(errors, "HOSTED_EXTENSIONS_TOO_LARGE", "extensions", "must remain within the bounded extension budget");
|
|
484
|
+
for (const [key, extension] of Object.entries(item)) {
|
|
485
|
+
if (!/^[a-z0-9]+(?:[.-][a-z0-9]+)+$/u.test(key))
|
|
486
|
+
add(errors, "INVALID_HOSTED_EXTENSION_NAMESPACE", `extensions.${key}`, "must use a namespaced key");
|
|
487
|
+
validateExtensionValue(extension, `extensions.${key}`, 0, { nodes: 0 }, errors);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function validateEvidence(value, path, events, errors) {
|
|
491
|
+
const refs = array(value, path, errors);
|
|
492
|
+
let previous = 0;
|
|
493
|
+
refs.forEach((entry, index) => {
|
|
494
|
+
const refPath = `${path}[${index}]`;
|
|
495
|
+
const ref = object(entry, refPath, errors);
|
|
496
|
+
if (!ref)
|
|
497
|
+
return;
|
|
498
|
+
exactKeys(ref, ["eventId", "sequence"], refPath, errors);
|
|
499
|
+
safeIdentifier(ref.eventId, `${refPath}.eventId`, errors);
|
|
500
|
+
integer(ref.sequence, `${refPath}.sequence`, errors);
|
|
501
|
+
const event = typeof ref.eventId === "string" ? events.get(ref.eventId) : undefined;
|
|
502
|
+
if (!event || event.sequence !== ref.sequence)
|
|
503
|
+
add(errors, "DANGLING_HOSTED_EVIDENCE", refPath, "must reference the exact hosted event ID and sequence");
|
|
504
|
+
if (typeof ref.sequence === "number" && ref.sequence <= previous)
|
|
505
|
+
add(errors, "UNORDERED_HOSTED_EVIDENCE", `${refPath}.sequence`, "must be strictly ordered");
|
|
506
|
+
if (typeof ref.sequence === "number")
|
|
507
|
+
previous = ref.sequence;
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
function singleReferencedEvent(value, events, path, errors) {
|
|
511
|
+
const refs = Array.isArray(value) ? value : [];
|
|
512
|
+
if (refs.length !== 1) {
|
|
513
|
+
add(errors, "INVALID_HOSTED_AUTHORITY_EVIDENCE_COUNT", path, "must contain exactly one authoritative event reference");
|
|
514
|
+
return undefined;
|
|
515
|
+
}
|
|
516
|
+
return referencedEvents(value, events)[0];
|
|
517
|
+
}
|
|
518
|
+
function referencedEvents(value, events) {
|
|
519
|
+
if (!Array.isArray(value))
|
|
520
|
+
return [];
|
|
521
|
+
return value.flatMap((entry) => {
|
|
522
|
+
if (!isRecord(entry) || typeof entry.eventId !== "string" || typeof entry.sequence !== "number")
|
|
523
|
+
return [];
|
|
524
|
+
const event = events.get(entry.eventId);
|
|
525
|
+
return event?.sequence === entry.sequence ? [event] : [];
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
function validateExtensionValue(value, path, depth, state, errors) {
|
|
529
|
+
state.nodes += 1;
|
|
530
|
+
if (state.nodes > 256 || depth > 4) {
|
|
531
|
+
add(errors, "HOSTED_EXTENSIONS_TOO_COMPLEX", path, "must remain within recursive depth and node limits");
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
if (value === null || typeof value === "boolean" || (typeof value === "number" && Number.isFinite(value)))
|
|
535
|
+
return;
|
|
536
|
+
if (typeof value === "string") {
|
|
537
|
+
if (!SAFE_IDENTIFIER_PATTERN.test(value) || SECRET_VALUE_PATTERN.test(value) || OPAQUE_TOKEN_PATTERN.test(value) || PATH_VALUE_PATTERN.test(value)) {
|
|
538
|
+
add(errors, "UNSAFE_HOSTED_EXTENSION_VALUE", path, "must be a bounded non-secret identifier rather than raw path command prompt or token content");
|
|
539
|
+
}
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (Array.isArray(value)) {
|
|
543
|
+
if (value.length > 64)
|
|
544
|
+
add(errors, "HOSTED_EXTENSIONS_TOO_COMPLEX", path, "arrays must contain at most 64 values");
|
|
545
|
+
value.forEach((entry, index) => validateExtensionValue(entry, `${path}[${index}]`, depth + 1, state, errors));
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
if (!isRecord(value)) {
|
|
549
|
+
add(errors, "INVALID_HOSTED_EXTENSION_VALUE", path, "must be bounded JSON data");
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
if (Object.keys(value).length > 64)
|
|
553
|
+
add(errors, "HOSTED_EXTENSIONS_TOO_COMPLEX", path, "objects must contain at most 64 keys");
|
|
554
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
555
|
+
if (!/^[A-Za-z][A-Za-z0-9_.-]{0,63}$/u.test(key) || SENSITIVE_EXTENSION_KEY_PATTERN.test(key)) {
|
|
556
|
+
add(errors, "UNSAFE_HOSTED_EXTENSION_KEY", `${path}.${key}`, "must not name raw path command prompt token secret or payload content");
|
|
557
|
+
}
|
|
558
|
+
validateExtensionValue(entry, `${path}.${key}`, depth + 1, state, errors);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function exactKeys(value, allowed, path, errors) {
|
|
562
|
+
const allowedSet = new Set(allowed);
|
|
563
|
+
for (const key of Object.keys(value))
|
|
564
|
+
if (!allowedSet.has(key))
|
|
565
|
+
add(errors, "UNKNOWN_HOSTED_FIELD", `${path}.${key}`, "is not allowlisted");
|
|
566
|
+
}
|
|
567
|
+
function object(value, path, errors) {
|
|
568
|
+
if (!isRecord(value)) {
|
|
569
|
+
add(errors, "INVALID_HOSTED_OBJECT", path, "must be an object");
|
|
570
|
+
return undefined;
|
|
571
|
+
}
|
|
572
|
+
return value;
|
|
573
|
+
}
|
|
574
|
+
function array(value, path, errors) {
|
|
575
|
+
if (!Array.isArray(value)) {
|
|
576
|
+
add(errors, "INVALID_HOSTED_ARRAY", path, "must be an array");
|
|
577
|
+
return [];
|
|
578
|
+
}
|
|
579
|
+
return value;
|
|
580
|
+
}
|
|
581
|
+
function text(value, path, errors) { if (typeof value !== "string" || value.trim().length === 0)
|
|
582
|
+
add(errors, "INVALID_HOSTED_TEXT", path, "must be non-empty text"); }
|
|
583
|
+
function optionalText(value, path, errors) { if (value !== undefined)
|
|
584
|
+
text(value, path, errors); }
|
|
585
|
+
function safeIdentifier(value, path, errors) { if (typeof value !== "string" || !SAFE_IDENTIFIER_PATTERN.test(value) || SECRET_VALUE_PATTERN.test(value) || PATH_VALUE_PATTERN.test(value))
|
|
586
|
+
add(errors, "INVALID_HOSTED_IDENTIFIER", path, "must be a bounded non-secret opaque identifier"); }
|
|
587
|
+
function safeCodeIdentifier(value, path, errors) { if (typeof value !== "string" || !SAFE_CODE_PATTERN.test(value) || SECRET_VALUE_PATTERN.test(value) || OPAQUE_TOKEN_PATTERN.test(value))
|
|
588
|
+
add(errors, "INVALID_HOSTED_CODE", path, "must use a closed sanitized action or reason identifier"); }
|
|
589
|
+
function requireIdentifier(value, path, errors) { safeIdentifier(value, path, errors); }
|
|
590
|
+
function optionalIdentifier(value, path, errors) { if (value !== undefined)
|
|
591
|
+
safeIdentifier(value, path, errors); }
|
|
592
|
+
function uniqueIdentifier(value, path, ids, errors) { safeIdentifier(value, path, errors); if (typeof value === "string" && SAFE_IDENTIFIER_PATTERN.test(value)) {
|
|
593
|
+
if (ids.has(value))
|
|
594
|
+
add(errors, "DUPLICATE_HOSTED_ID", path, "must be unique");
|
|
595
|
+
else
|
|
596
|
+
ids.add(value);
|
|
597
|
+
} }
|
|
598
|
+
function uniqueText(value, path, ids, errors) { uniqueIdentifier(value, path, ids, errors); }
|
|
599
|
+
function linkedText(value, path, ids, errors) { safeIdentifier(value, path, errors); if (typeof value === "string" && !ids.has(value))
|
|
600
|
+
add(errors, "DANGLING_HOSTED_ID", path, "must reference an allowlisted identity"); }
|
|
601
|
+
function optionalLinkedText(value, path, ids, errors) { if (value !== undefined)
|
|
602
|
+
linkedText(value, path, ids, errors); }
|
|
603
|
+
function stringArray(value, path, errors) { const items = array(value, path, errors); const seen = new Set(); items.forEach((item, index) => uniqueText(item, `${path}[${index}]`, seen, errors)); return items.filter((item) => typeof item === "string"); }
|
|
604
|
+
function linkedStringArray(value, path, ids, errors) { stringArray(value, path, errors).forEach((item, index) => { if (!ids.has(item))
|
|
605
|
+
add(errors, "DANGLING_HOSTED_ID", `${path}[${index}]`, "must reference an allowlisted identity"); }); }
|
|
606
|
+
function integer(value, path, errors) { if (!Number.isInteger(value) || Number(value) < 1)
|
|
607
|
+
add(errors, "INVALID_HOSTED_INTEGER", path, "must be a positive integer"); }
|
|
608
|
+
function nonnegative(value, path, errors) { if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
|
|
609
|
+
add(errors, "INVALID_HOSTED_NUMBER", path, "must be a finite non-negative number"); }
|
|
610
|
+
function timestamp(value, path, errors) { text(value, path, errors); if (typeof value === "string" && !Number.isFinite(Date.parse(value)))
|
|
611
|
+
add(errors, "INVALID_HOSTED_TIMESTAMP", path, "must be an ISO timestamp"); }
|
|
612
|
+
function hex(value, length, path, errors) { if (typeof value !== "string" || !new RegExp(`^[a-f0-9]{${length}}$`, "u").test(value))
|
|
613
|
+
add(errors, "INVALID_HOSTED_HASH", path, `must be ${length} lowercase hexadecimal characters`); }
|
|
614
|
+
function equal(value, expected, path, errors) { if (value !== expected)
|
|
615
|
+
add(errors, "UNSUPPORTED_HOSTED_VALUE", path, `must equal ${expected}`); }
|
|
616
|
+
function enumValue(value, values, path, errors) { if (typeof value !== "string" || !values.includes(value))
|
|
617
|
+
add(errors, "INVALID_HOSTED_ENUM", path, "must use an allowlisted value"); }
|
|
618
|
+
function add(errors, code, path, message) { errors.push({ code, path, message }); }
|
|
619
|
+
function invalid(errors) { return { ok: false, errors }; }
|
|
620
|
+
function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype; }
|
|
621
|
+
function sortJson(value) { if (Array.isArray(value))
|
|
622
|
+
return value.map(sortJson); if (isRecord(value))
|
|
623
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, sortJson(item)])); return value; }
|
|
624
|
+
const SAFE_IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@-]{0,127}$/u;
|
|
625
|
+
const SAFE_CODE_PATTERN = /^[a-z][a-z0-9_.:-]{0,63}$/u;
|
|
626
|
+
const SECRET_VALUE_PATTERN = /^(?:sk-(?:proj-|live_|test_)?[A-Za-z0-9_-]{8,}|rk_live_[A-Za-z0-9_-]{8,}|gh[pousr]_[A-Za-z0-9_]{16,}|github_pat_[A-Za-z0-9_]{16,}|npm_[A-Za-z0-9_]{16,}|xox[baprs]-[A-Za-z0-9-]{16,}|AKIA[A-Z0-9]{16}|ASIA[A-Z0-9]{16}|AIza[A-Za-z0-9_-]{20,}|ya29\.[A-Za-z0-9_-]{16,}|eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,})/u;
|
|
627
|
+
const OPAQUE_TOKEN_PATTERN = /^(?=.{32,128}$)(?=.*[A-Za-z])(?=.*\d)[A-Za-z0-9_-]+$/u;
|
|
628
|
+
const PATH_VALUE_PATTERN = /^(?:[A-Za-z]:[\\/]|\\\\|\/(?:home|Users|private|tmp|var)\/|file:\/\/)/u;
|
|
629
|
+
const SENSITIVE_EXTENSION_KEY_PATTERN = /(?:path|command|prompt|token|secret|credential|password|authorization|auth|bearer|session|cookie|stdout|stderr|payload|diff|apiKey|privateKey|accessKey|keyMaterial)/iu;
|