@corca-org/ceal-host-linux-arm64 1.0.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/README.md +5 -0
- package/bin/ceal-host +0 -0
- package/libexec/ceal-host/claude-hook.mjs +2413 -0
- package/libexec/ceal-host/claude-producer.mjs +2157 -0
- package/libexec/ceal-host/codex-hook.mjs +3881 -0
- package/libexec/ceal-host/codex-invocation-authentication.mjs +3977 -0
- package/libexec/ceal-host/codex-producer.mjs +3480 -0
- package/libexec/ceal-host/control.mjs +8430 -0
- package/libexec/ceal-host/node/bin/node +0 -0
- package/libexec/ceal-host/relay.mjs +2987 -0
- package/package.json +25 -0
- package/share/ceal-host/bundle-manifest.json +89 -0
- package/share/ceal-host/bundle-manifest.sigstore.json +1 -0
|
@@ -0,0 +1,2413 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// host/direct-invocation.ts
|
|
4
|
+
import { realpathSync } from "node:fs";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
function isDirectInvocation(importMetaUrl, argv1 = process.argv[1]) {
|
|
7
|
+
if (!argv1) return false;
|
|
8
|
+
try {
|
|
9
|
+
return realpathSync(fileURLToPath(importMetaUrl)) === realpathSync(argv1);
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// host/claude/src/claude-types.ts
|
|
16
|
+
var CLAUDE_HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "Stop", "StopFailure"];
|
|
17
|
+
var CLAUDE_HOST_ERROR_CODES = ["invalid_input", "storage", "configuration"];
|
|
18
|
+
function isClaudeHostErrorCode(value) {
|
|
19
|
+
return typeof value === "string" && CLAUDE_HOST_ERROR_CODES.some((code3) => code3 === value);
|
|
20
|
+
}
|
|
21
|
+
var CLAUDE_OUTBOX_SCHEMA = "ceal.claude_hook_outbox.v1";
|
|
22
|
+
var CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION = "ceal.claude_invocation_context.v1";
|
|
23
|
+
var ClaudeHostError = class extends Error {
|
|
24
|
+
name = "ClaudeHostError";
|
|
25
|
+
code;
|
|
26
|
+
constructor(code3, message) {
|
|
27
|
+
super(message);
|
|
28
|
+
if (!isClaudeHostErrorCode(code3)) throw new TypeError("Claude Host error code is invalid.");
|
|
29
|
+
this.code = code3;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
function isClaudeHookEvent(value) {
|
|
33
|
+
return typeof value === "string" && CLAUDE_HOOK_EVENTS.some((event) => event === value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// host/claude/src/claude-decoder.ts
|
|
37
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
38
|
+
|
|
39
|
+
// packages/ceal-client-protocol/src/canonical-json.ts
|
|
40
|
+
function cealCanonicalJson(value) {
|
|
41
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
|
|
42
|
+
if (typeof value === "number") {
|
|
43
|
+
if (!Number.isFinite(value)) throw new TypeError("Ceal canonical JSON does not support non-finite numbers.");
|
|
44
|
+
return JSON.stringify(value);
|
|
45
|
+
}
|
|
46
|
+
if (value === void 0) throw new TypeError("Ceal canonical JSON does not support undefined values.");
|
|
47
|
+
if (Array.isArray(value)) {
|
|
48
|
+
const entries = [];
|
|
49
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
50
|
+
if (!Object.hasOwn(value, index)) throw new TypeError("Ceal canonical JSON does not support sparse arrays.");
|
|
51
|
+
entries.push(cealCanonicalJson(value[index]));
|
|
52
|
+
}
|
|
53
|
+
return `[${entries.join(",")}]`;
|
|
54
|
+
}
|
|
55
|
+
if (!isPlainRecord(value)) throw new TypeError("Ceal canonical JSON only supports JSON data.");
|
|
56
|
+
return `{${Object.keys(value).sort(cealCompareOrdered).map((key) => `${JSON.stringify(key)}:${cealCanonicalJson(value[key])}`).join(",")}}`;
|
|
57
|
+
}
|
|
58
|
+
function cealCompareOrdered(left, right) {
|
|
59
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
60
|
+
}
|
|
61
|
+
function isPlainRecord(value) {
|
|
62
|
+
const prototype = Object.getPrototypeOf(value);
|
|
63
|
+
return prototype === Object.prototype || prototype === null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// packages/ceal-client-protocol/client-wire-contract.json
|
|
67
|
+
var client_wire_contract_default = {
|
|
68
|
+
schema_version: "ceal.client_wire_contract.v1",
|
|
69
|
+
canonical_json: {
|
|
70
|
+
ordering: "utf16_code_unit_ascending"
|
|
71
|
+
},
|
|
72
|
+
artifact_descriptor: {
|
|
73
|
+
operation_contract_bundle_schema_version: "ceal.operation_contract_bundle.v1",
|
|
74
|
+
schema_version: "ceal.verified_artifact_descriptor.v1",
|
|
75
|
+
binding_schema_version: "ceal.verified_artifact_binding.v1",
|
|
76
|
+
kind: "verified_artifact",
|
|
77
|
+
binding_kind: "catalog",
|
|
78
|
+
media_type: "application/json",
|
|
79
|
+
digest: {
|
|
80
|
+
algorithm: "sha256",
|
|
81
|
+
encoding: "lowercase_hex",
|
|
82
|
+
length: 64,
|
|
83
|
+
pattern: "^[a-f0-9]{64}$"
|
|
84
|
+
},
|
|
85
|
+
catalog_revision: {
|
|
86
|
+
prefix: "catalog:",
|
|
87
|
+
digest_length: 64,
|
|
88
|
+
length: 72,
|
|
89
|
+
pattern: "^catalog:[a-f0-9]{64}$"
|
|
90
|
+
},
|
|
91
|
+
max_bytes: 4194304,
|
|
92
|
+
envelope_max_bytes: 389
|
|
93
|
+
},
|
|
94
|
+
command_request: {
|
|
95
|
+
schema_version: "ceal.command_request.v1",
|
|
96
|
+
absolute_transport_max_bytes: 65536
|
|
97
|
+
},
|
|
98
|
+
command_response: {
|
|
99
|
+
schema_version: "ceal.command_response.v1",
|
|
100
|
+
descriptor_field: "verified_artifact",
|
|
101
|
+
required_fields: [
|
|
102
|
+
"schema_version",
|
|
103
|
+
"exit_code",
|
|
104
|
+
"document"
|
|
105
|
+
],
|
|
106
|
+
optional_fields: [
|
|
107
|
+
"verified_artifact"
|
|
108
|
+
],
|
|
109
|
+
bare_document_fields: [
|
|
110
|
+
"invocation",
|
|
111
|
+
"next",
|
|
112
|
+
"common_concepts",
|
|
113
|
+
"operations",
|
|
114
|
+
"catalog_revision"
|
|
115
|
+
],
|
|
116
|
+
exit_code_max: 125,
|
|
117
|
+
bare_document_max_bytes: 65536,
|
|
118
|
+
complete_max_bytes: 66019,
|
|
119
|
+
absolute_transport_max_bytes: 4194304
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// packages/ceal-client-protocol/src/client-wire-contract.ts
|
|
124
|
+
var CEAL_CLIENT_WIRE_CONTRACT = Object.freeze(client_wire_contract_default);
|
|
125
|
+
var artifact = CEAL_CLIENT_WIRE_CONTRACT.artifact_descriptor;
|
|
126
|
+
var request = CEAL_CLIENT_WIRE_CONTRACT.command_request;
|
|
127
|
+
var response = CEAL_CLIENT_WIRE_CONTRACT.command_response;
|
|
128
|
+
var CEAL_OPERATION_CONTRACT_BUNDLE_SCHEMA_VERSION = artifact.operation_contract_bundle_schema_version;
|
|
129
|
+
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_SCHEMA_VERSION = artifact.schema_version;
|
|
130
|
+
var CEAL_VERIFIED_ARTIFACT_BINDING_SCHEMA_VERSION = artifact.binding_schema_version;
|
|
131
|
+
var CEAL_VERIFIED_ARTIFACT_KIND = artifact.kind;
|
|
132
|
+
var CEAL_VERIFIED_ARTIFACT_BINDING_KIND = artifact.binding_kind;
|
|
133
|
+
var CEAL_VERIFIED_ARTIFACT_MEDIA_TYPE = artifact.media_type;
|
|
134
|
+
var CEAL_VERIFIED_ARTIFACT_DIGEST_LENGTH = artifact.digest.length;
|
|
135
|
+
var CEAL_VERIFIED_ARTIFACT_DIGEST_PATTERN = artifact.digest.pattern;
|
|
136
|
+
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_LENGTH = artifact.catalog_revision.length;
|
|
137
|
+
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_PATTERN = artifact.catalog_revision.pattern;
|
|
138
|
+
var CEAL_VERIFIED_ARTIFACT_MAX_BYTES = artifact.max_bytes;
|
|
139
|
+
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_MAX_BYTES = artifact.envelope_max_bytes;
|
|
140
|
+
var CEAL_COMMAND_REQUEST_SCHEMA_VERSION = request.schema_version;
|
|
141
|
+
var CEAL_COMMAND_REQUEST_MAX_BYTES = request.absolute_transport_max_bytes;
|
|
142
|
+
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_FIELDS = response.bare_document_fields;
|
|
143
|
+
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_MAX_BYTES = response.bare_document_max_bytes;
|
|
144
|
+
var CEAL_COMMAND_RESPONSE_MAX_BYTES = response.absolute_transport_max_bytes;
|
|
145
|
+
|
|
146
|
+
// packages/ceal-client-protocol/src/vocabulary.ts
|
|
147
|
+
function vocabulary(words) {
|
|
148
|
+
return Object.freeze(words);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// packages/ceal-client-protocol/src/operation-contract-types.ts
|
|
152
|
+
var CEAL_JSON_SCHEMA_2020_12 = "https://json-schema.org/draft/2020-12/schema";
|
|
153
|
+
var CEAL_OPERATION_ERROR_SCHEMA_VERSION = "ceal.operation_error.v1";
|
|
154
|
+
var CEAL_JSON_SCHEMA_TYPES = vocabulary(["object", "array", "string", "integer", "number", "boolean", "null"]);
|
|
155
|
+
var OBSERVATION_POSTURES = vocabulary(["direct_readback", "approved_no_readback", "operator_mediated", "excluded"]);
|
|
156
|
+
var EFFECT_DOMAINS = vocabulary(["none", "gateway_state", "provider", "external_world"]);
|
|
157
|
+
var SAFETY_CLASSES = vocabulary(["routine", "sensitive", "high_impact"]);
|
|
158
|
+
var IDEMPOTENCY_CLASSES = vocabulary(["idempotent", "conditionally_idempotent", "non_idempotent"]);
|
|
159
|
+
var REVERSIBILITY_CLASSES = vocabulary(["none", "reversible", "compensatable", "irreversible"]);
|
|
160
|
+
var COMPENSATION_OWNERS = vocabulary(["none", "connector", "operator"]);
|
|
161
|
+
var CANCELLATION_BOUNDARIES = vocabulary(["before_admission", "before_handoff", "after_handoff_reconcile"]);
|
|
162
|
+
var HANDOFF_CLASSES = vocabulary(["not_applicable", "submission", "effect"]);
|
|
163
|
+
var AUTHORITATIVE_READBACK_CLASSES = vocabulary(["required", "unavailable"]);
|
|
164
|
+
var PROVIDER_READBACK_CLASSES = vocabulary(["required", "not_available", "not_applicable"]);
|
|
165
|
+
var RECONCILIATION_CLASSES = vocabulary(["none", "automatic", "operator"]);
|
|
166
|
+
var NO_READBACK_APPROVAL_CLASSES = vocabulary(["not_applicable", "explicit", "operator"]);
|
|
167
|
+
var REPLAY_PREREQUISITES = vocabulary(["never", "fresh_admission", "reconciled_non_application", "operator_approval"]);
|
|
168
|
+
var UNKNOWN_OUTCOMES = vocabulary(["reconcile", "operator_action", "excluded"]);
|
|
169
|
+
var RECOVERY_OWNERS = vocabulary(["gateway", "connector", "operator"]);
|
|
170
|
+
var CEAL_OPERATION_ADMISSION_STATES = vocabulary(["proposed", "refused", "admitted"]);
|
|
171
|
+
var CEAL_OPERATION_EXECUTION_STATES = vocabulary(["not_started", "started", "completed"]);
|
|
172
|
+
var CEAL_OPERATION_PROVIDER_HANDOFF_STATES = vocabulary(["not_offered", "offered", "accepted", "rejected", "unknown"]);
|
|
173
|
+
var CEAL_OPERATION_EFFECT_STATES = vocabulary(["none", "applied", "failed", "unknown", "compensated"]);
|
|
174
|
+
var CEAL_OPERATION_OBSERVATION_STATES = vocabulary(["not_started", "not_required", "pending", "observed", "unavailable"]);
|
|
175
|
+
var CEAL_OPERATION_RECOVERY_KINDS = vocabulary(["none", "reconcile", "compensate", "operator_action"]);
|
|
176
|
+
var CEAL_OPERATION_RECOVERY_STATUSES = vocabulary(["not_started", "active", "completed"]);
|
|
177
|
+
var CEAL_OPERATION_PROVIDER_READBACK_AVAILABILITY = vocabulary(["not_started", "not_applicable", "not_available", "pending", "available"]);
|
|
178
|
+
var CEAL_OPERATION_OFFERED_HANDOFF_STATES = vocabulary(["accepted", "rejected", "unknown"]);
|
|
179
|
+
var CEAL_OPERATION_APPROVAL_DISPOSITIONS = vocabulary([
|
|
180
|
+
"approval_not_required_by_policy",
|
|
181
|
+
"interactive_pending",
|
|
182
|
+
"interactive_granted",
|
|
183
|
+
"interactive_denied",
|
|
184
|
+
"interactive_expired",
|
|
185
|
+
"interactive_unavailable"
|
|
186
|
+
]);
|
|
187
|
+
|
|
188
|
+
// packages/ceal-client-protocol/src/gateway-response-types.ts
|
|
189
|
+
var CEAL_CAPABILITY_READINESS_VALUES = vocabulary(["ready", "degraded", "unavailable", "unknown"]);
|
|
190
|
+
var CEAL_TARGET_REQUIREMENTS = vocabulary(["required", "optional", "none"]);
|
|
191
|
+
var CEAL_AUDIT_OUTCOMES = vocabulary(["succeeded", "denied", "failed"]);
|
|
192
|
+
var CEAL_GATEWAY_PROVIDER_STEP_OUTCOMES = vocabulary(["completed", "rejected", "throttled", "failed"]);
|
|
193
|
+
var CEAL_POLICY_DECISIONS = vocabulary(["allowed", "denied", "not_evaluated"]);
|
|
194
|
+
var CEAL_CONNECTOR_ROUTE_PHASES = vocabulary(["scope_observation", "target_selection", "route_resolution"]);
|
|
195
|
+
var CEAL_CONNECTOR_ROUTE_CAUSES = vocabulary(["provider_throttled", "provider_unavailable", "binding_invalid", "scope_limit_exceeded"]);
|
|
196
|
+
var CEAL_WRITE_SOURCE_KINDS = vocabulary(["authenticated_registered_client", "agent_lease_admission", "provider_authenticated_event"]);
|
|
197
|
+
var CEAL_REFRESH_DELIVERIES = vocabulary(["initial", "recovery", "replay", "terminal_failure", "recovery_unavailable"]);
|
|
198
|
+
var CEAL_CLIENT_OPERATIONS = vocabulary(["handshake", "discover", "call", "readback"]);
|
|
199
|
+
var CEAL_WRITE_IDEMPOTENCY_POSTURES = vocabulary(["required", "optional", "not_required"]);
|
|
200
|
+
var CEAL_WRITE_PROVIDER_READBACK_POSTURES = vocabulary(["required", "best_effort", "not_available"]);
|
|
201
|
+
var CEAL_WRITE_ATTRIBUTIONS = vocabulary(["subject", "requester_event", "connector_integration"]);
|
|
202
|
+
var CEAL_PROTOCOL_VERSION = "1.4.0";
|
|
203
|
+
var CEAL_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "phase", "schema_version"]);
|
|
204
|
+
var CEAL_CLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["cause", ...CEAL_CONNECTOR_ROUTE_FAILURE_KEYS]);
|
|
205
|
+
var CEAL_UNCLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "error_class", "phase", "schema_version"]);
|
|
206
|
+
|
|
207
|
+
// packages/ceal-client-protocol/src/gateway-validation-primitives.ts
|
|
208
|
+
var CealProtocolValidationError = class extends Error {
|
|
209
|
+
name = "CealProtocolValidationError";
|
|
210
|
+
code;
|
|
211
|
+
constructor(code3) {
|
|
212
|
+
super(code3 === "invalid_gateway_request" ? "Ceal Gateway request is invalid." : "Ceal client response is invalid.");
|
|
213
|
+
this.code = code3;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
var SAFE_REF = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
217
|
+
var MAX_SAFE_TOKEN_LENGTH = 64;
|
|
218
|
+
var SAFE_CODE = new RegExp(`^[a-z][a-z0-9_]{0,${MAX_SAFE_TOKEN_LENGTH - 1}}$`, "u");
|
|
219
|
+
var SAFE_CONNECTOR_KIND = new RegExp(`^[a-z][a-z0-9-]{0,${MAX_SAFE_TOKEN_LENGTH - 1}}$`, "u");
|
|
220
|
+
var FORBIDDEN_SECRET_KEY = /^(?:[a-z0-9_]*(?:token|secret|password|credential(?:s)?|private_?key)|api_?key|authorization|bearer|raw_?provider_?payload|provider_?payload)$/iu;
|
|
221
|
+
var FORBIDDEN_AUTHORITY_KEY = /^(?:actor_?ref|owner_?ref|registration_?ref|runner_?ref|auth_?decision|policy_?decision|host_?decision)$/iu;
|
|
222
|
+
var TEXT_ENCODER = new TextEncoder();
|
|
223
|
+
var CEAL_WIRE_SAFE_TEXT_MAX_BYTES = 8 * 1024;
|
|
224
|
+
function assertSafeJsonValue(value, options, depth = 0, count = { value: 0 }) {
|
|
225
|
+
count.value += 1;
|
|
226
|
+
if (depth > 8 || count.value > (options.maxNodes ?? 512)) invalidByContext(options);
|
|
227
|
+
if (value === null || typeof value === "boolean") return;
|
|
228
|
+
if (typeof value === "number") return assertSafeJsonNumber(value, options);
|
|
229
|
+
if (typeof value === "string") return assertSafeJsonString(value, options);
|
|
230
|
+
if (Array.isArray(value)) {
|
|
231
|
+
return assertSafeJsonArray(value, options, depth, count);
|
|
232
|
+
}
|
|
233
|
+
assertSafeJsonRecord(requireRecord(value), options, depth, count);
|
|
234
|
+
}
|
|
235
|
+
function assertSafeJsonNumber(value, options) {
|
|
236
|
+
if (!Number.isFinite(value)) invalidByContext(options);
|
|
237
|
+
}
|
|
238
|
+
function assertSafeJsonString(value, options) {
|
|
239
|
+
if (byteLength(value) > CEAL_WIRE_SAFE_TEXT_MAX_BYTES || firstDisallowedControl(value, "multi_line") !== null) invalidByContext(options);
|
|
240
|
+
}
|
|
241
|
+
function assertSafeJsonArray(value, options, depth, count) {
|
|
242
|
+
if (value.length > 128) invalidByContext(options);
|
|
243
|
+
for (const item of value) assertSafeJsonValue(item, options, depth + 1, count);
|
|
244
|
+
}
|
|
245
|
+
function assertSafeJsonRecord(record7, options, depth, count) {
|
|
246
|
+
const entries = Object.entries(record7);
|
|
247
|
+
if (entries.length > 128) invalidByContext(options);
|
|
248
|
+
const sourceUrlColumn = options.allowHttpsUrl ? compactSourceUrlColumn(record7.fields) : null;
|
|
249
|
+
for (const [key, child] of entries) {
|
|
250
|
+
if (key === "credential_material_included" && child !== false) invalidByContext(options);
|
|
251
|
+
if (!isSafeNegativeMaterialAssertion(key, child)) assertSafeJsonKey(key, options);
|
|
252
|
+
if (key === "rows" && sourceUrlColumn !== null) {
|
|
253
|
+
assertSafeJsonCompactRows(child, sourceUrlColumn, options, depth, count);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
assertSafeJsonRecordChild(key, child, options, depth, count);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function assertSafeJsonRecordChild(key, child, options, depth, count) {
|
|
260
|
+
if (options.allowResultContent && (key === "text" || key === "text_preview")) {
|
|
261
|
+
if (!isSafeResultContent(child, key)) invalidByContext(options);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if ((key === "url" || key === "source_url") && options.allowHttpsUrl && isSafeExternalHttpsUrl(child)) return;
|
|
265
|
+
assertSafeJsonValue(child, options, depth + 1, count);
|
|
266
|
+
}
|
|
267
|
+
function compactSourceUrlColumn(fields) {
|
|
268
|
+
if (!Array.isArray(fields)) return null;
|
|
269
|
+
const index = fields.indexOf("source_url");
|
|
270
|
+
return index < 0 ? null : index;
|
|
271
|
+
}
|
|
272
|
+
function assertSafeJsonCompactRows(value, sourceUrlColumn, options, depth, count) {
|
|
273
|
+
if (!Array.isArray(value) || value.length > 128) invalidByContext(options);
|
|
274
|
+
for (const row of value) {
|
|
275
|
+
if (!Array.isArray(row) || row.length > 128) invalidByContext(options);
|
|
276
|
+
for (const [index, cell] of row.entries()) {
|
|
277
|
+
if (index === sourceUrlColumn && (cell === null || isSafeExternalHttpsUrl(cell))) continue;
|
|
278
|
+
assertSafeJsonValue(cell, options, depth + 2, count);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function isSafeNegativeMaterialAssertion(key, value) {
|
|
283
|
+
return key === "credential_material_included" && value === false;
|
|
284
|
+
}
|
|
285
|
+
function assertSafeJsonKey(key, options) {
|
|
286
|
+
const invalid3 = !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(key) || FORBIDDEN_SECRET_KEY.test(key) || options.forbidAuthorityKeys && FORBIDDEN_AUTHORITY_KEY.test(key);
|
|
287
|
+
if (invalid3) invalidByContext(options);
|
|
288
|
+
}
|
|
289
|
+
function isSafeExternalHttpsUrl(value) {
|
|
290
|
+
if (!isSafeExternalHttpsUrlInput(value)) return false;
|
|
291
|
+
try {
|
|
292
|
+
const url = new URL(value);
|
|
293
|
+
return isSafeExternalHttpsUrlShape(url);
|
|
294
|
+
} catch {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function isSafeExternalHttpsUrlInput(value) {
|
|
299
|
+
return typeof value === "string" && byteLength(value) <= 2048;
|
|
300
|
+
}
|
|
301
|
+
function isSafeExternalHttpsUrlShape(url) {
|
|
302
|
+
return url.protocol === "https:" && url.username === "" && url.password === "" && url.hash === "";
|
|
303
|
+
}
|
|
304
|
+
function isSafeResultContent(value, key) {
|
|
305
|
+
const maximum = key === "text" ? 8192 : 1024;
|
|
306
|
+
return typeof value === "string" && byteLength(value) <= maximum && firstDisallowedControl(value, "multi_line") === null;
|
|
307
|
+
}
|
|
308
|
+
function normalizeCealSingleLineText(value) {
|
|
309
|
+
return value.split("").map((character) => hasControlCharacter(character) ? " " : character).join("").trim();
|
|
310
|
+
}
|
|
311
|
+
function byteLength(value) {
|
|
312
|
+
return TEXT_ENCODER.encode(value).byteLength;
|
|
313
|
+
}
|
|
314
|
+
var LAYOUT_CODE_POINTS = [9, 10, 13];
|
|
315
|
+
function firstDisallowedControl(value, shape) {
|
|
316
|
+
let index = 0;
|
|
317
|
+
for (const character of value) {
|
|
318
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
319
|
+
const laysOutALine = shape === "multi_line" && LAYOUT_CODE_POINTS.includes(codePoint);
|
|
320
|
+
if (!laysOutALine && (codePoint <= 31 || codePoint === 127)) return { codePoint, index };
|
|
321
|
+
index += character.length;
|
|
322
|
+
}
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
function hasControlCharacter(value) {
|
|
326
|
+
return firstDisallowedControl(value, "single_line") !== null;
|
|
327
|
+
}
|
|
328
|
+
var AUTHORITY_METADATA_SUFFIX = "(?:_(?:refs?|revisions?|versions?|generations?|ids?))*$";
|
|
329
|
+
var UNDECLARED_AUTHORITY_STATE_KEY = new RegExp(`(?:^|_)(?:decisions?|authority|grants?|policy|policies|scopes?|tokens?|credentials?|secrets?|permissions?|roles?)${AUTHORITY_METADATA_SUFFIX}`, "iu");
|
|
330
|
+
var UNDECLARED_HANDLE_REF_KEY = new RegExp(`(?:^|_)refs?${AUTHORITY_METADATA_SUFFIX}`, "iu");
|
|
331
|
+
function requireRecord(value) {
|
|
332
|
+
if (!isCealJsonRecord(value)) invalidRequestOrResponse();
|
|
333
|
+
const prototype = Object.getPrototypeOf(value);
|
|
334
|
+
if (prototype !== Object.prototype && prototype !== null) invalidRequestOrResponse();
|
|
335
|
+
return value;
|
|
336
|
+
}
|
|
337
|
+
function isCealJsonRecord(value) {
|
|
338
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
339
|
+
}
|
|
340
|
+
var CEAL_VALIDATION_STATUSES = vocabulary(["not_applicable", "valid", "invalid"]);
|
|
341
|
+
var CEAL_PARSE_STATUSES = vocabulary(["not_applicable", "parsed", "parse_failed"]);
|
|
342
|
+
function invalidByContext(options) {
|
|
343
|
+
if (options.forbidAuthorityKeys) invalidRequest();
|
|
344
|
+
invalidResponse();
|
|
345
|
+
}
|
|
346
|
+
var InvalidWireShapeError = class extends Error {
|
|
347
|
+
};
|
|
348
|
+
function invalidRequestOrResponse() {
|
|
349
|
+
throw new InvalidWireShapeError();
|
|
350
|
+
}
|
|
351
|
+
function invalidRequest() {
|
|
352
|
+
throw new CealProtocolValidationError("invalid_gateway_request");
|
|
353
|
+
}
|
|
354
|
+
function invalidResponse() {
|
|
355
|
+
throw new CealProtocolValidationError("invalid_client_response");
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// packages/ceal-client-protocol/src/refusal-fact.ts
|
|
359
|
+
var REFUSAL_VALUE_MAX_CHARS = 160;
|
|
360
|
+
function renderText(value) {
|
|
361
|
+
return normalizeCealSingleLineText(value);
|
|
362
|
+
}
|
|
363
|
+
var REFUSAL_LIST_HEAD = 5;
|
|
364
|
+
var REFUSAL_FACTS_MAX_CHARS = 360;
|
|
365
|
+
var REFUSAL_SENTENCE_MAX_BYTES = 384;
|
|
366
|
+
var REFUSAL_CLOSING_MAX_BYTES = REFUSAL_SENTENCE_MAX_BYTES / 2;
|
|
367
|
+
function refusalFact(name, value, expected) {
|
|
368
|
+
const rendered = `${JSON.stringify(name)}=${renderRefusalValue(value)}`;
|
|
369
|
+
return expected === void 0 ? rendered : `${rendered}; expected ${cut(renderText(expected))}`;
|
|
370
|
+
}
|
|
371
|
+
function refusalFacts(facts) {
|
|
372
|
+
const joined = facts.map((fact) => refusalFact(fact.name, fact.value, fact.expected)).join("; ");
|
|
373
|
+
return joined.length <= REFUSAL_FACTS_MAX_CHARS ? joined : `${joined.slice(0, REFUSAL_FACTS_MAX_CHARS)}...(+${joined.length - REFUSAL_FACTS_MAX_CHARS} chars, ${facts.length} facts)`;
|
|
374
|
+
}
|
|
375
|
+
function renderRefusalValue(value) {
|
|
376
|
+
if (typeof value === "object" && value !== null) return renderStructure(value);
|
|
377
|
+
if (typeof value === "string") return cut(JSON.stringify(renderText(value)));
|
|
378
|
+
if (typeof value === "function") return `function ${value.name === "" ? "(anonymous)" : value.name} -- a fact must be a value`;
|
|
379
|
+
if (typeof value === "symbol") return value.toString();
|
|
380
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
381
|
+
return String(value);
|
|
382
|
+
}
|
|
383
|
+
function renderStructure(value) {
|
|
384
|
+
if (Array.isArray(value)) return cut(renderList(value));
|
|
385
|
+
if (value instanceof Error) return `${value.name}${"code" in value && typeof value.code === "string" ? `(${value.code})` : ""}`;
|
|
386
|
+
if (value instanceof Date) return value.toISOString();
|
|
387
|
+
if (ArrayBuffer.isView(value)) return `<${value.byteLength} bytes>`;
|
|
388
|
+
return renderRecord(value);
|
|
389
|
+
}
|
|
390
|
+
function renderRecord(value) {
|
|
391
|
+
if (value instanceof Map) return cut(renderList([...value].map(([key, entry]) => `${renderRefusalValue(key)}=${renderRefusalValue(entry)}`), true));
|
|
392
|
+
if (value instanceof Set) return cut(renderList([...value]));
|
|
393
|
+
if (isStatRecord(value)) return `{"mode":"0o${Number(value.mode).toString(8)}","size":${Number(value.size)},"kind":"${statKind(value)}"}`;
|
|
394
|
+
return cut(renderText(JSON.stringify(value, serializable) ?? String(value)));
|
|
395
|
+
}
|
|
396
|
+
function statKind(value) {
|
|
397
|
+
if (value.isDirectory()) return "directory";
|
|
398
|
+
return value.isFile() ? "file" : "other";
|
|
399
|
+
}
|
|
400
|
+
function isStatRecord(value) {
|
|
401
|
+
return "mode" in value && (typeof value.mode === "number" || typeof value.mode === "bigint") && "size" in value && (typeof value.size === "number" || typeof value.size === "bigint") && "isDirectory" in value && typeof value.isDirectory === "function" && "isFile" in value && typeof value.isFile === "function";
|
|
402
|
+
}
|
|
403
|
+
function serializable(_key, entry) {
|
|
404
|
+
if (typeof entry === "bigint") return `${entry}n`;
|
|
405
|
+
return ArrayBuffer.isView(entry) ? `<${entry.byteLength} bytes>` : entry;
|
|
406
|
+
}
|
|
407
|
+
function renderList(value, rendered = false) {
|
|
408
|
+
const head = value.slice(0, REFUSAL_LIST_HEAD).map((item) => rendered ? String(item) : renderRefusalValue(item));
|
|
409
|
+
const rest = value.length - head.length;
|
|
410
|
+
return `[${head.join(", ")}${rest > 0 ? `, ...${rest} more` : ""}] (${value.length} items)`;
|
|
411
|
+
}
|
|
412
|
+
function cut(text) {
|
|
413
|
+
return text.length <= REFUSAL_VALUE_MAX_CHARS ? text : `${text.slice(0, REFUSAL_VALUE_MAX_CHARS)}...(+${text.length - REFUSAL_VALUE_MAX_CHARS} chars)`;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// packages/ceal-client-protocol/src/safe-json-budget.ts
|
|
417
|
+
var SAFE_JSON_MIN_BYTES_PER_NODE = 4;
|
|
418
|
+
function safeJsonNodeBudgetForBytes(maxBytes) {
|
|
419
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < SAFE_JSON_MIN_BYTES_PER_NODE) {
|
|
420
|
+
throw new RangeError(`safe-JSON byte cap is invalid: ${refusalFact("maxBytes", maxBytes, `at least ${SAFE_JSON_MIN_BYTES_PER_NODE} bytes`)}`);
|
|
421
|
+
}
|
|
422
|
+
return Math.floor(maxBytes / SAFE_JSON_MIN_BYTES_PER_NODE);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// packages/ceal-client-protocol/src/protocol-bounds.ts
|
|
426
|
+
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES = 64 * 1024;
|
|
427
|
+
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_NODES = safeJsonNodeBudgetForBytes(CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES);
|
|
428
|
+
var OPERATION_SCHEMA_LITERAL_MAX_BYTES = CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES / 4;
|
|
429
|
+
var CEAL_OPERATION_JSON_SCHEMA_BUDGET = Object.freeze({
|
|
430
|
+
max_depth: 8,
|
|
431
|
+
max_properties: 96,
|
|
432
|
+
max_string_length: OPERATION_SCHEMA_LITERAL_MAX_BYTES / 4,
|
|
433
|
+
max_alternatives: 8,
|
|
434
|
+
max_enum_values: 64,
|
|
435
|
+
max_literal_bytes: OPERATION_SCHEMA_LITERAL_MAX_BYTES
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// packages/ceal-client-protocol/src/operation-validation-primitives.ts
|
|
439
|
+
var ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/u;
|
|
440
|
+
function isOperationRecord(value) {
|
|
441
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
442
|
+
const prototype = Object.getPrototypeOf(value);
|
|
443
|
+
return prototype === Object.prototype || prototype === null;
|
|
444
|
+
}
|
|
445
|
+
function requireOperationRecord(value, message) {
|
|
446
|
+
if (!isOperationRecord(value)) throw new TypeError(message);
|
|
447
|
+
}
|
|
448
|
+
function requireClosedValue(value, members, message) {
|
|
449
|
+
if (typeof value !== "string" || !members.includes(value)) throw new TypeError(message);
|
|
450
|
+
}
|
|
451
|
+
function requireIsoTimestamp(value, message) {
|
|
452
|
+
if (typeof value !== "string" || !ISO_TIMESTAMP.test(value) || !Number.isFinite(Date.parse(value))) throw new TypeError(message);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// packages/ceal-client-protocol/src/operation-contract-validation.ts
|
|
456
|
+
var CEAL_OPERATION_AUTHORITY_POLICY_KEYS = Object.freeze(["profile", "instance_binding", "target", "admission_recheck"]);
|
|
457
|
+
var MAX_OPERATION_INPUT_RESOLVER_BINDINGS = 96;
|
|
458
|
+
var CEAL_OPERATION_RESULT_KEYS = vocabulary(["ok", "operation_id", "operation_receipt", "schema_version", "value"]);
|
|
459
|
+
var CEAL_OPERATION_COMPACT_RESULT_KEYS = vocabulary(["ok", "operation_evidence", "operation_id", "schema_version", "value"]);
|
|
460
|
+
|
|
461
|
+
// packages/ceal-client-protocol/src/operation-catalog.ts
|
|
462
|
+
var CEAL_OPERATION_CATALOG_SCHEMA_VERSION = "ceal.catalog.v3";
|
|
463
|
+
var CLIENT_AUDIENCES = Object.freeze(["ceal", "cealctl", "agent"]);
|
|
464
|
+
var CLIENT_AUDIENCE_SET = new Set(CLIENT_AUDIENCES);
|
|
465
|
+
|
|
466
|
+
// packages/ceal-client-protocol/src/schema-literals.ts
|
|
467
|
+
var CEAL_SCHEMA_LITERALS = [
|
|
468
|
+
"ceal.activation_transport.v1",
|
|
469
|
+
"ceal.capability_access.v1",
|
|
470
|
+
CEAL_OPERATION_CATALOG_SCHEMA_VERSION,
|
|
471
|
+
"ceal.client_refresh_request.v2",
|
|
472
|
+
"ceal.client_refresh_result.v2",
|
|
473
|
+
"ceal.client_revoke_request.v1",
|
|
474
|
+
"ceal.client_revoke_result.v1",
|
|
475
|
+
CEAL_COMMAND_REQUEST_SCHEMA_VERSION,
|
|
476
|
+
"ceal.device_enrollment_challenge.v1",
|
|
477
|
+
"ceal.device_enrollment_challenge_request.v1",
|
|
478
|
+
"ceal.device_enrollment_hpke_aad.v1",
|
|
479
|
+
"ceal.device_enrollment_hpke_info.v1",
|
|
480
|
+
"ceal.device_enrollment_poll.v1",
|
|
481
|
+
"ceal.device_enrollment_poll_result.v1",
|
|
482
|
+
"ceal.device_enrollment_proof.v1",
|
|
483
|
+
"ceal.device_enrollment_start.v1",
|
|
484
|
+
"ceal.device_enrollment_start_result.v1",
|
|
485
|
+
"ceal.effect_approval.v1",
|
|
486
|
+
"ceal.effect_approval_confirmation.v1",
|
|
487
|
+
"ceal.enrollment_create.v1",
|
|
488
|
+
"ceal.enrollment_create_result.v1",
|
|
489
|
+
"ceal.enrollment_exchange.v1",
|
|
490
|
+
"ceal.enrollment_result.v1",
|
|
491
|
+
"ceal.first_activation_causal_join.v1",
|
|
492
|
+
"ceal.gateway_aggregate_authorization_snapshot.v2",
|
|
493
|
+
"ceal.gateway_announcement_policy.v1",
|
|
494
|
+
"ceal.gateway_audit_call_detail.v1",
|
|
495
|
+
"ceal.gateway_audit_event.v1",
|
|
496
|
+
"ceal.gateway_audit_readback.v1",
|
|
497
|
+
"ceal.gateway_authorization_snapshot.v1",
|
|
498
|
+
"ceal.gateway_cache_origin.v1",
|
|
499
|
+
"ceal.gateway_call_result.v1",
|
|
500
|
+
"ceal.gateway_command_resolution.v1",
|
|
501
|
+
"ceal.gateway_connector_route_failure.v1",
|
|
502
|
+
"ceal.gateway_discovery.v3",
|
|
503
|
+
"ceal.gateway_handshake.v1",
|
|
504
|
+
"ceal.gateway_installation_activation_code_issue_request.v1",
|
|
505
|
+
"ceal.gateway_installation_activation_code_issue_result.v1",
|
|
506
|
+
"ceal.gateway_installation_activation_code_list_result.v1",
|
|
507
|
+
"ceal.gateway_installation_activation_code_revoke_request.v1",
|
|
508
|
+
"ceal.gateway_installation_activation_code_revoke_result.v1",
|
|
509
|
+
"ceal.gateway_installation_activation_credential.v1",
|
|
510
|
+
"ceal.gateway_installation_activation_request.v1",
|
|
511
|
+
"ceal.gateway_installation_active_state.v1",
|
|
512
|
+
"ceal.gateway_installation_list_result.v1",
|
|
513
|
+
"ceal.gateway_installation_revoke_request.v1",
|
|
514
|
+
"ceal.gateway_installation_revoke_result.v1",
|
|
515
|
+
"ceal.gateway_policy_denial.v1",
|
|
516
|
+
"ceal.gateway_rate_limit_policy.v1",
|
|
517
|
+
"ceal.gateway_refresh_audit_detail.v1",
|
|
518
|
+
"ceal.gateway_scoped_identity_projection.v1",
|
|
519
|
+
"ceal.gateway_write_receipt_readback.v1",
|
|
520
|
+
"ceal.gateway_write_request_receipt.v1",
|
|
521
|
+
"ceal.host_observation.v1",
|
|
522
|
+
"ceal.host_registration_request.v1",
|
|
523
|
+
"ceal.host_registration_result.v1",
|
|
524
|
+
"ceal.leased-resource-stream.v2",
|
|
525
|
+
"ceal.leased_consumer_attachment_stream_frame.v2",
|
|
526
|
+
"ceal.leased_consumer_attachment_stream_request.v2",
|
|
527
|
+
"ceal.leased_consumer_attachment_stream_transport.v2",
|
|
528
|
+
"ceal.operation_descriptor.v1",
|
|
529
|
+
"ceal.operation_error.v1",
|
|
530
|
+
"ceal.operation_failure.v1",
|
|
531
|
+
"ceal.operation_receipt.v1",
|
|
532
|
+
"ceal.operation_result.v1",
|
|
533
|
+
"ceal.operation_result.compact.v1",
|
|
534
|
+
"ceal.local_result_materialization.v1",
|
|
535
|
+
"ceal.local_result_root.v1",
|
|
536
|
+
"ceal.owner_bootstrap_exchange_request.v1",
|
|
537
|
+
"ceal.owner_bootstrap_exchange_result.v1",
|
|
538
|
+
"ceal.protocol_negotiation.v1",
|
|
539
|
+
"ceal.request.v1",
|
|
540
|
+
"ceal.request_artifact.v1",
|
|
541
|
+
"ceal.request_cancellation.v1",
|
|
542
|
+
"ceal.request_graph_revision.v1",
|
|
543
|
+
"ceal.request_occurrence_correlation.v1",
|
|
544
|
+
"ceal.request_projection.v1"
|
|
545
|
+
];
|
|
546
|
+
var CEAL_SCHEMA_LITERAL_SET = new Set(CEAL_SCHEMA_LITERALS);
|
|
547
|
+
|
|
548
|
+
// packages/ceal-client-protocol/src/bounded-series.ts
|
|
549
|
+
import { join } from "node:path";
|
|
550
|
+
var CEAL_BOUNDED_SERIES_CATALOG = Object.freeze({
|
|
551
|
+
usage_record: (root) => join(root, "run-usage.jsonl"),
|
|
552
|
+
control_commit: (root) => join(root, "control-auto-commit.jsonl"),
|
|
553
|
+
engagement_signal: (root) => join(root, "runtime-state", "slack-feedback.jsonl"),
|
|
554
|
+
runtime_error: (root) => join(root, "runtime-state", "runtime-errors.jsonl"),
|
|
555
|
+
provider_retry: (root) => join(root, "observability", "provider-retry-queue", "queue.jsonl"),
|
|
556
|
+
turn_bridge_request: (root) => join(root, ".ceal", "turn-bridge", "requests.jsonl"),
|
|
557
|
+
channel_log: (root) => join(root, "log.jsonl"),
|
|
558
|
+
thread_run_events: (root) => join(root, "run-events.jsonl"),
|
|
559
|
+
bounded_store_sidecar: (root) => `${root}.jsonl`,
|
|
560
|
+
gateway_audit_archive: (root) => `${root}.jsonl`,
|
|
561
|
+
gateway_capability_audit: (root) => join(root, "capability-audit.jsonl"),
|
|
562
|
+
gateway_retrieval_waste_partition: (root) => `${root}.jsonl`,
|
|
563
|
+
gateway_access_policy_publication: (root) => join(root, "access-policy-publications.jsonl"),
|
|
564
|
+
gateway_event_lease_journal: (root) => `${root}.journal.jsonl`,
|
|
565
|
+
gateway_control_audit: (root) => `${root}.control-audits.jsonl`,
|
|
566
|
+
gateway_profile_connector_control_audit: (root) => `${root}.audit.jsonl`,
|
|
567
|
+
gateway_profile_connector_scope_audit: (root) => join(root, "profile-connector-scope-audit.jsonl"),
|
|
568
|
+
gateway_profile_connector_activity_scope_audit: (root) => join(root, "profile-connector-activity-scope-audit.jsonl"),
|
|
569
|
+
gateway_profile_connector_readiness_audit: (root) => join(root, "profile-connector-readiness.audit.jsonl"),
|
|
570
|
+
gateway_organization_audit_ledger: (root) => join(root, "organization-ledger.jsonl"),
|
|
571
|
+
gateway_personal_client_audit: (root) => join(root, "audit.jsonl"),
|
|
572
|
+
gateway_personal_client_access_audit: (root) => join(root, "access-audit.jsonl"),
|
|
573
|
+
gateway_admin_enrollment: (root) => join(root, "enrollments.jsonl"),
|
|
574
|
+
gateway_admin_login: (root) => join(root, "logins.jsonl"),
|
|
575
|
+
gateway_admin_family: (root) => join(root, "families.jsonl"),
|
|
576
|
+
gateway_admin_refresh_token: (root) => join(root, "refresh-tokens.jsonl"),
|
|
577
|
+
gateway_admin_access_token: (root) => join(root, "access-tokens.jsonl")
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
// packages/ceal-client-protocol/src/host-registration.ts
|
|
581
|
+
var CEAL_HOST_REGISTRATION_RESULT_KEYS = Object.freeze(["credential_generation", "host_access_token", "host_installation_ref", "host_source_ref", "ok", "registration_attempt_ref", "schema_version", "status"]);
|
|
582
|
+
|
|
583
|
+
// packages/ceal-client-protocol/src/agent-session-handoff.ts
|
|
584
|
+
var CEAL_AGENT_ADMISSION_PHASES = vocabulary([
|
|
585
|
+
"incumbent_admitted",
|
|
586
|
+
"incumbent_draining",
|
|
587
|
+
"drained",
|
|
588
|
+
"candidate_admitted",
|
|
589
|
+
"candidate_draining",
|
|
590
|
+
"rollback_admitted"
|
|
591
|
+
]);
|
|
592
|
+
var CEAL_AGENT_SERVICE_SESSION_RECORD_KEYS = vocabulary([
|
|
593
|
+
"admission_fence",
|
|
594
|
+
"admission_state",
|
|
595
|
+
"admission_state_generation",
|
|
596
|
+
"bearer",
|
|
597
|
+
"consumer_ref",
|
|
598
|
+
"credential_generation",
|
|
599
|
+
"expires_at",
|
|
600
|
+
"gateway_instance",
|
|
601
|
+
"gateway_origin",
|
|
602
|
+
"gateway_serving_generation",
|
|
603
|
+
"issued_at",
|
|
604
|
+
"record_sha256",
|
|
605
|
+
"renew_after",
|
|
606
|
+
"renew_by",
|
|
607
|
+
"schema_version",
|
|
608
|
+
"session_revision"
|
|
609
|
+
]);
|
|
610
|
+
var CEAL_AGENT_SESSION_COMMIT_KEYS = vocabulary(["attempt_ref", "expected_session_revision", "session_revision"]);
|
|
611
|
+
|
|
612
|
+
// packages/ceal-client-protocol/src/operation-next-actions.ts
|
|
613
|
+
var CALLER_RECOVERABLE_NEXT_ACTION_VALUES = [
|
|
614
|
+
"select_did_you_mean",
|
|
615
|
+
// The graph, not the argument list, is what the caller must change: the
|
|
616
|
+
// resolver that owns this input has no producing node upstream, and no edit
|
|
617
|
+
// to the invocation can conjure one. `resolver_dependency_absent` carried no
|
|
618
|
+
// remedy at all rather than borrow a word that means something else, so the
|
|
619
|
+
// scorer read a fully-diagnosed, caller-fixable refusal as a failed run.
|
|
620
|
+
"request.graph.append",
|
|
621
|
+
"choose_new_output_path",
|
|
622
|
+
"choose_new_idempotency_key",
|
|
623
|
+
"restart_search",
|
|
624
|
+
"repeat_original_arguments",
|
|
625
|
+
"choose_new_request_identity",
|
|
626
|
+
// A refusal that names the argument (invalid_argument with `property`) tells the caller exactly what to change.
|
|
627
|
+
"correct_argument",
|
|
628
|
+
// A resolver-owned input must move from caller operands into the declared
|
|
629
|
+
// resolver selection field.
|
|
630
|
+
"supply_resolver_selections",
|
|
631
|
+
// The graph names an upstream node whose Operation produces the resolver's
|
|
632
|
+
// occurrence, and it has not produced one yet. The caller runs that node
|
|
633
|
+
// first; the refusal used to share a code with a malformed resolver result,
|
|
634
|
+
// which is not fixable, so neither could carry a remedy.
|
|
635
|
+
"run_resolver_dependency",
|
|
636
|
+
// A replay carries the input the occurrence durably admitted, and this one
|
|
637
|
+
// carries a different one. The caller fixes it by presenting the original
|
|
638
|
+
// invocation, so it is a remedy; the coordinator minted the word at the
|
|
639
|
+
// replay guard and never declared it, which read as unguided to the scorer.
|
|
640
|
+
"use_original_input",
|
|
641
|
+
// The same mismatch on the Request binding rather than the input: the
|
|
642
|
+
// occurrence was admitted under one binding and the replay presents another.
|
|
643
|
+
"use_original_request_binding"
|
|
644
|
+
];
|
|
645
|
+
var CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS = Object.freeze(CALLER_RECOVERABLE_NEXT_ACTION_VALUES);
|
|
646
|
+
var NON_REMEDY_NEXT_ACTION_VALUES = [
|
|
647
|
+
"await_pending_approval",
|
|
648
|
+
"check_gateway",
|
|
649
|
+
"inspect_context_store",
|
|
650
|
+
"inspect_gateway",
|
|
651
|
+
"inspect_local_cache",
|
|
652
|
+
// The thin client prints these on its own behalf, before or after the wire.
|
|
653
|
+
// They were declared only as `CealClientLocalRemedy` keys and, for two of
|
|
654
|
+
// them, only as Go string literals -- a second and third vocabulary for one
|
|
655
|
+
// thing. `CealClientLocalRemedy` derives from this list now, so the client's
|
|
656
|
+
// remedies cannot drift from the words the protocol knows.
|
|
657
|
+
"inspect_local_client",
|
|
658
|
+
"inspect_local_command",
|
|
659
|
+
"inspect_local_output",
|
|
660
|
+
"inspect_local_route",
|
|
661
|
+
"inspect_receipt",
|
|
662
|
+
"inspect_request",
|
|
663
|
+
"none",
|
|
664
|
+
"recover",
|
|
665
|
+
"reconcile",
|
|
666
|
+
// Reconciliation of one named occurrence, not of the deployment: a legacy
|
|
667
|
+
// occurrence with no admitted input commitment cannot be replayed safely and
|
|
668
|
+
// no edit to the call changes that. `reconcile` is the deployment-wide word.
|
|
669
|
+
"reconcile_occurrence",
|
|
670
|
+
"reduce_request_size",
|
|
671
|
+
"repair_or_adopt_session",
|
|
672
|
+
"request.effect_approval.read",
|
|
673
|
+
"request.run",
|
|
674
|
+
"retry",
|
|
675
|
+
// The owner -- a correlation hook, an approval hook -- is not serving right
|
|
676
|
+
// now. The caller changes nothing and the same call succeeds once the owner
|
|
677
|
+
// is back, so this is not a remedy the scorer may count as guidance.
|
|
678
|
+
"retry_after_owner_recovery",
|
|
679
|
+
"retry_same_receipt_read",
|
|
680
|
+
"run_file_search_again",
|
|
681
|
+
"select_exact_approval"
|
|
682
|
+
];
|
|
683
|
+
var CEAL_NON_REMEDY_NEXT_ACTIONS = Object.freeze(NON_REMEDY_NEXT_ACTION_VALUES);
|
|
684
|
+
var DECLARED = /* @__PURE__ */ new Set([...CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS, ...CEAL_NON_REMEDY_NEXT_ACTIONS]);
|
|
685
|
+
var RECOVERABLE = new Set(CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS);
|
|
686
|
+
|
|
687
|
+
// packages/ceal-client-protocol/src/operation-contract-artifact.ts
|
|
688
|
+
var CATALOG_REVISION = new RegExp(CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_PATTERN, "u");
|
|
689
|
+
var SHA256 = new RegExp(CEAL_VERIFIED_ARTIFACT_DIGEST_PATTERN, "u");
|
|
690
|
+
var CEAL_OPERATION_CONTRACT_REQUIRED_FIELDS = Object.freeze(["input_schema", "result_schema"]);
|
|
691
|
+
var CEAL_OPERATION_CONTRACT_OPTIONAL_FIELDS = Object.freeze(["summary", "input_resolution"]);
|
|
692
|
+
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_JSON_SCHEMA = Object.freeze({
|
|
693
|
+
$schema: CEAL_JSON_SCHEMA_2020_12,
|
|
694
|
+
$id: "https://ceal.dev/schemas/verified-artifact-descriptor.v1.schema.json",
|
|
695
|
+
title: "Ceal verified artifact descriptor",
|
|
696
|
+
type: "object",
|
|
697
|
+
additionalProperties: false,
|
|
698
|
+
required: ["schema_version", "kind", "media_type", "byte_count", "sha256", "binding"],
|
|
699
|
+
properties: {
|
|
700
|
+
schema_version: { const: CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_SCHEMA_VERSION },
|
|
701
|
+
kind: { const: CEAL_VERIFIED_ARTIFACT_KIND },
|
|
702
|
+
media_type: { const: CEAL_VERIFIED_ARTIFACT_MEDIA_TYPE },
|
|
703
|
+
byte_count: { type: "integer", minimum: 1, maximum: CEAL_VERIFIED_ARTIFACT_MAX_BYTES },
|
|
704
|
+
sha256: { type: "string", minLength: CEAL_VERIFIED_ARTIFACT_DIGEST_LENGTH, maxLength: CEAL_VERIFIED_ARTIFACT_DIGEST_LENGTH, pattern: CEAL_VERIFIED_ARTIFACT_DIGEST_PATTERN },
|
|
705
|
+
binding: {
|
|
706
|
+
type: "object",
|
|
707
|
+
additionalProperties: false,
|
|
708
|
+
required: ["schema_version", "kind", "catalog_revision"],
|
|
709
|
+
properties: {
|
|
710
|
+
schema_version: { const: CEAL_VERIFIED_ARTIFACT_BINDING_SCHEMA_VERSION },
|
|
711
|
+
kind: { const: CEAL_VERIFIED_ARTIFACT_BINDING_KIND },
|
|
712
|
+
catalog_revision: { type: "string", minLength: CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_LENGTH, maxLength: CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_LENGTH, pattern: CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_PATTERN }
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
// packages/ceal-client-protocol/src/operation-occurrence.ts
|
|
719
|
+
var MAX_RECEIPT_REVISION = Number.MAX_SAFE_INTEGER - 1;
|
|
720
|
+
|
|
721
|
+
// packages/ceal-client-protocol/src/leased-resource-stream.ts
|
|
722
|
+
var CEAL_LEASED_CONSUMER_ATTACHMENT_STREAM_MAGIC = new Uint8Array([67, 69, 65, 76, 82, 83, 50, 0]);
|
|
723
|
+
var CEAL_LEASED_CONSUMER_ATTACHMENT_STREAM_RECORD_PREFIX_BYTES = 8;
|
|
724
|
+
var CEAL_LEASED_CONSUMER_ATTACHMENT_STREAM_MAX_HEADER_BYTES = 16 * 1024;
|
|
725
|
+
var CEAL_LEASED_CONSUMER_ATTACHMENT_STREAM_CHUNK_BYTES = 64 * 1024;
|
|
726
|
+
var CEAL_LEASED_CONSUMER_ATTACHMENT_STREAM_MAX_RECORD_BYTES = CEAL_LEASED_CONSUMER_ATTACHMENT_STREAM_RECORD_PREFIX_BYTES + CEAL_LEASED_CONSUMER_ATTACHMENT_STREAM_MAX_HEADER_BYTES + CEAL_LEASED_CONSUMER_ATTACHMENT_STREAM_CHUNK_BYTES;
|
|
727
|
+
var CEAL_ATTACHMENT_UNREAD_REASONS = vocabulary(["blocked", "unavailable", "too_large", "unsupported", "download_failed", "digest_mismatch"]);
|
|
728
|
+
|
|
729
|
+
// packages/ceal-client-protocol/src/request-validation-primitives.ts
|
|
730
|
+
import { createHash } from "node:crypto";
|
|
731
|
+
var DIGEST = /^[a-f0-9]{64}$/u;
|
|
732
|
+
function isCealDigest(value) {
|
|
733
|
+
return typeof value === "string" && DIGEST.test(value);
|
|
734
|
+
}
|
|
735
|
+
function cealRequestSha256(value) {
|
|
736
|
+
return createHash("sha256").update(cealCanonicalJson(value), "utf8").digest("hex");
|
|
737
|
+
}
|
|
738
|
+
function operationRecord(value, message) {
|
|
739
|
+
requireOperationRecord(value, message);
|
|
740
|
+
return value;
|
|
741
|
+
}
|
|
742
|
+
function invalid(message) {
|
|
743
|
+
throw new TypeError(message);
|
|
744
|
+
}
|
|
745
|
+
function requirePrefixedRef(value, prefix, message) {
|
|
746
|
+
if (typeof value !== "string" || !SAFE_REF.test(value) || !value.startsWith(prefix)) invalid(message);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// packages/ceal-client-protocol/src/request-authority.ts
|
|
750
|
+
var CEAL_REQUEST_SCHEMA_VERSION = "ceal.request.v1";
|
|
751
|
+
var CEAL_REQUEST_ARTIFACT_OWNERS = vocabulary(["gateway", "host", "connector"]);
|
|
752
|
+
var CEAL_REQUEST_HOST_BINDING_KEYS = vocabulary(["prompt_ref", "host_session_ref", "host_turn_ref", "host_source_ref"]);
|
|
753
|
+
var CEAL_REQUEST_ARTIFACT_RETENTION_CLASSES = vocabulary(["request", "source", "result"]);
|
|
754
|
+
var CANCELLATION_STATES = vocabulary(["requested", "cancelled"]);
|
|
755
|
+
var CEAL_REQUEST_KEYS = Object.freeze(["schema_version", "ceal_request_ref", "created_at", "authority", "prompt_ref", "host_session_ref", "host_turn_ref", "host_source_ref"]);
|
|
756
|
+
var CEAL_REQUEST_JSON_SCHEMA = {
|
|
757
|
+
type: "object",
|
|
758
|
+
maxProperties: 9,
|
|
759
|
+
additionalProperties: false,
|
|
760
|
+
properties: {
|
|
761
|
+
schema_version: { type: "string", const: CEAL_REQUEST_SCHEMA_VERSION, maxLength: 32 },
|
|
762
|
+
ceal_request_ref: { type: "string", minLength: 1, maxLength: 128, pattern: "^ceal-request:[A-Za-z0-9][A-Za-z0-9._:-]{0,114}$" },
|
|
763
|
+
created_at: { type: "string", minLength: 1, maxLength: 64 },
|
|
764
|
+
authority: {
|
|
765
|
+
type: "object",
|
|
766
|
+
maxProperties: 5,
|
|
767
|
+
additionalProperties: false,
|
|
768
|
+
properties: {
|
|
769
|
+
instance_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
770
|
+
profile_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
771
|
+
principal_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
772
|
+
execution_subject_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
773
|
+
authority_revision_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source }
|
|
774
|
+
},
|
|
775
|
+
required: ["instance_ref", "profile_ref", "principal_ref", "execution_subject_ref", "authority_revision_ref"]
|
|
776
|
+
},
|
|
777
|
+
prompt_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
778
|
+
host_session_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
779
|
+
host_turn_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
780
|
+
host_input_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
781
|
+
host_source_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source }
|
|
782
|
+
},
|
|
783
|
+
required: CEAL_REQUEST_KEYS
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
// packages/ceal-client-protocol/src/request-operands.ts
|
|
787
|
+
var CEAL_REQUEST_MAX_RESOLVER_SELECTIONS = 128;
|
|
788
|
+
var CEAL_REQUEST_MAX_SELECTOR_PROPERTIES = 16;
|
|
789
|
+
var CEAL_REQUEST_MAX_SELECTOR_VALUE_LENGTH = 1024;
|
|
790
|
+
var CEAL_REQUEST_RESOLVER_SELECTION_JSON_SCHEMA = {
|
|
791
|
+
type: "object",
|
|
792
|
+
maxProperties: 2,
|
|
793
|
+
additionalProperties: false,
|
|
794
|
+
properties: {
|
|
795
|
+
property: { type: "string", minLength: 1, maxLength: 128 },
|
|
796
|
+
selectors: {
|
|
797
|
+
type: "object",
|
|
798
|
+
maxProperties: CEAL_REQUEST_MAX_SELECTOR_PROPERTIES,
|
|
799
|
+
properties: {},
|
|
800
|
+
additionalProperties: { type: "string", minLength: 1, maxLength: CEAL_REQUEST_MAX_SELECTOR_VALUE_LENGTH }
|
|
801
|
+
}
|
|
802
|
+
},
|
|
803
|
+
required: ["property", "selectors"]
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
// packages/ceal-client-protocol/src/request-graph.ts
|
|
807
|
+
var CEAL_REQUEST_MAX_GRAPH_NODES = 128;
|
|
808
|
+
|
|
809
|
+
// packages/ceal-client-protocol/src/request-run.ts
|
|
810
|
+
var CEAL_REQUEST_NODE_STANDING_OCCURRENCE_SCHEMA_VERSION = "ceal.request_node_standing_occurrence.v1";
|
|
811
|
+
var CEAL_REQUEST_RUN_BATCH_SCHEMA_VERSION = "ceal.request_run_batch.v3";
|
|
812
|
+
var REQUEST_ACTION_SAFE_REF_SCHEMA = { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source };
|
|
813
|
+
var REQUEST_ACTION_DIGEST_SCHEMA = { type: "string", minLength: 64, maxLength: 64, pattern: "^[a-f0-9]{64}$" };
|
|
814
|
+
var REQUEST_NODE_STANDING_OCCURRENCE_SCHEMA = {
|
|
815
|
+
type: "object",
|
|
816
|
+
maxProperties: 9,
|
|
817
|
+
additionalProperties: false,
|
|
818
|
+
properties: {
|
|
819
|
+
schema_version: { type: "string", const: CEAL_REQUEST_NODE_STANDING_OCCURRENCE_SCHEMA_VERSION, maxLength: 64 },
|
|
820
|
+
node_ref: REQUEST_ACTION_SAFE_REF_SCHEMA,
|
|
821
|
+
operation_id: REQUEST_ACTION_SAFE_REF_SCHEMA,
|
|
822
|
+
correlation_ref: REQUEST_ACTION_SAFE_REF_SCHEMA,
|
|
823
|
+
occurrence_ref: REQUEST_ACTION_SAFE_REF_SCHEMA,
|
|
824
|
+
graph_revision: { type: "integer", minimum: 1, maximum: Number.MAX_SAFE_INTEGER },
|
|
825
|
+
graph_revision_digest: REQUEST_ACTION_DIGEST_SCHEMA,
|
|
826
|
+
admitted_input_sha256: REQUEST_ACTION_DIGEST_SCHEMA,
|
|
827
|
+
reason: { type: "string", minLength: 1, maxLength: 1024 }
|
|
828
|
+
},
|
|
829
|
+
required: ["schema_version", "node_ref", "operation_id", "correlation_ref", "occurrence_ref", "graph_revision", "graph_revision_digest", "admitted_input_sha256", "reason"]
|
|
830
|
+
};
|
|
831
|
+
var REQUEST_EXECUTION_PREPARE_REFUSAL_SCHEMA = {
|
|
832
|
+
type: "object",
|
|
833
|
+
additionalProperties: false,
|
|
834
|
+
maxProperties: 9,
|
|
835
|
+
properties: {
|
|
836
|
+
schema_version: { type: "string", const: CEAL_OPERATION_ERROR_SCHEMA_VERSION, maxLength: 64 },
|
|
837
|
+
code: REQUEST_ACTION_SAFE_REF_SCHEMA,
|
|
838
|
+
message: { type: "string", minLength: 1, maxLength: 2048 },
|
|
839
|
+
property: { type: "string", minLength: 1, maxLength: 256 },
|
|
840
|
+
next_action: REQUEST_ACTION_SAFE_REF_SCHEMA,
|
|
841
|
+
retryable: { type: "boolean" },
|
|
842
|
+
repaired_input: {},
|
|
843
|
+
unrepaired_pointers: { type: "array", maxItems: 128, items: { type: "string", minLength: 1, maxLength: 256 } },
|
|
844
|
+
provider_handoff: { type: "string", const: "not_offered", maxLength: 32 }
|
|
845
|
+
},
|
|
846
|
+
required: ["schema_version", "code", "message", "next_action", "retryable"]
|
|
847
|
+
};
|
|
848
|
+
var CEAL_REQUEST_EXECUTION_ORDERS = vocabulary(["graph_dependency", "caller"]);
|
|
849
|
+
var CEAL_REQUEST_CALL_BUDGET_JSON_SCHEMA = {
|
|
850
|
+
type: "object",
|
|
851
|
+
additionalProperties: false,
|
|
852
|
+
maxProperties: 2,
|
|
853
|
+
properties: {
|
|
854
|
+
gateway_calls_so_far: { type: "integer", minimum: 1, maximum: Number.MAX_SAFE_INTEGER },
|
|
855
|
+
floor_for_this_graph: { type: "integer", minimum: 1, maximum: Number.MAX_SAFE_INTEGER }
|
|
856
|
+
},
|
|
857
|
+
required: ["gateway_calls_so_far", "floor_for_this_graph"]
|
|
858
|
+
};
|
|
859
|
+
var CEAL_REQUEST_RUN_BATCH_JSON_SCHEMA = {
|
|
860
|
+
type: "object",
|
|
861
|
+
additionalProperties: false,
|
|
862
|
+
maxProperties: 5,
|
|
863
|
+
properties: {
|
|
864
|
+
schema_version: { type: "string", const: CEAL_REQUEST_RUN_BATCH_SCHEMA_VERSION, maxLength: 64 },
|
|
865
|
+
order: { enum: [...CEAL_REQUEST_EXECUTION_ORDERS] },
|
|
866
|
+
nodes: {
|
|
867
|
+
type: "array",
|
|
868
|
+
minItems: 1,
|
|
869
|
+
maxItems: CEAL_REQUEST_MAX_GRAPH_NODES,
|
|
870
|
+
items: {
|
|
871
|
+
// The runtime validator owns "exactly one of a result, a refusal
|
|
872
|
+
// and a standing run". The result envelope already names the
|
|
873
|
+
// Operation that ran.
|
|
874
|
+
type: "object",
|
|
875
|
+
additionalProperties: false,
|
|
876
|
+
maxProperties: 4,
|
|
877
|
+
properties: { node_ref: REQUEST_ACTION_SAFE_REF_SCHEMA, result: {}, refusal: REQUEST_EXECUTION_PREPARE_REFUSAL_SCHEMA, standing: REQUEST_ACTION_SAFE_REF_SCHEMA },
|
|
878
|
+
required: ["node_ref"]
|
|
879
|
+
}
|
|
880
|
+
},
|
|
881
|
+
standing_reading: { type: "string", minLength: 1, maxLength: 512 },
|
|
882
|
+
call_budget: CEAL_REQUEST_CALL_BUDGET_JSON_SCHEMA
|
|
883
|
+
},
|
|
884
|
+
required: ["schema_version", "order", "nodes", "call_budget"]
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
// packages/ceal-client-protocol/src/host-observation.ts
|
|
888
|
+
var CEAL_HOST_OBSERVATION_REF_PREFIX = "host-observation:";
|
|
889
|
+
var HOST_OBSERVATION_PRODUCER = /^[a-z][a-z0-9]{0,31}$/u;
|
|
890
|
+
var HOST_OBSERVATION_DIGEST = /^[a-f0-9]{8,64}$/u;
|
|
891
|
+
function cealHostObservationRef(producer, digest2) {
|
|
892
|
+
if (!HOST_OBSERVATION_PRODUCER.test(producer) || !HOST_OBSERVATION_DIGEST.test(digest2)) invalid("Ceal Host observation reference parts are invalid.");
|
|
893
|
+
return `${CEAL_HOST_OBSERVATION_REF_PREFIX}${producer}-${digest2}`;
|
|
894
|
+
}
|
|
895
|
+
function validateCealHostObservationDraft(value) {
|
|
896
|
+
const record7 = operationRecord(value, "Ceal Host observation draft is invalid.");
|
|
897
|
+
requirePrefixedRef(record7.observation_ref, CEAL_HOST_OBSERVATION_REF_PREFIX, "Ceal Host observation reference is invalid.");
|
|
898
|
+
requireClosedValue(record7.observation_kind, CEAL_HOST_OBSERVATION_KINDS, "Ceal Host observation kind is invalid.");
|
|
899
|
+
requireIsoTimestamp(record7.observed_at, "Ceal Host observation time is invalid.");
|
|
900
|
+
assertHostObservationValue(record7.value, record7.observation_kind);
|
|
901
|
+
}
|
|
902
|
+
var CEAL_REQUEST_MAX_OBSERVATION_VALUE_BYTES = 64 * 1024;
|
|
903
|
+
var CEAL_HOST_OBSERVATION_KINDS = vocabulary(["prompt", "session", "turn", "model", "token", "cost", "timing"]);
|
|
904
|
+
function isCealHostPromptObservationValue(value) {
|
|
905
|
+
return isOperationRecord(value) && Object.keys(value).length === 1 && isCealDigest(value.body_sha256);
|
|
906
|
+
}
|
|
907
|
+
var HOST_RESERVED_KEY = /^(?:admission|approval|audit|authorization|cancellation|capability|effect|grant|occurrence|operation|policy|profile|provider_readback|receipt|reconciliation|replay|target|write_request)(?:_|$)/iu;
|
|
908
|
+
function assertHostObservationValue(value, kind) {
|
|
909
|
+
if (value === void 0) invalid("Ceal Host observation value is invalid.");
|
|
910
|
+
if (kind === "prompt" && !isCealHostPromptObservationValue(value)) invalid("Ceal Host prompt observation must contain only its body digest.");
|
|
911
|
+
assertSafeJsonValue(value, { forbidAuthorityKeys: true, maxNodes: 512 });
|
|
912
|
+
if (byteLength(JSON.stringify(value) ?? "") > CEAL_REQUEST_MAX_OBSERVATION_VALUE_BYTES) invalid("Ceal Host observation value is oversized.");
|
|
913
|
+
inspectHostKeys(value);
|
|
914
|
+
}
|
|
915
|
+
function inspectHostKeys(value) {
|
|
916
|
+
if (Array.isArray(value)) {
|
|
917
|
+
for (const entry of value) inspectHostKeys(entry);
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
if (value === null || typeof value !== "object") return;
|
|
921
|
+
for (const [key, child] of Object.entries(value)) {
|
|
922
|
+
if (HOST_RESERVED_KEY.test(key)) invalid("Ceal Host observation cannot restate Gateway-owned facts.");
|
|
923
|
+
inspectHostKeys(child);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// packages/ceal-client-protocol/src/request-effect-approval.ts
|
|
928
|
+
var CEAL_EFFECT_APPROVAL_DECISION_CHECK_PHRASES = Object.freeze([
|
|
929
|
+
"observed after this approval was raised and before it expires",
|
|
930
|
+
"from this same Host session and Host source",
|
|
931
|
+
"not the Host input that opened the Request",
|
|
932
|
+
"an input that has decided no other approval"
|
|
933
|
+
]);
|
|
934
|
+
var CEAL_EFFECT_APPROVAL_DECISION_INSTRUCTION = `Submit exactly one phrase below in a Host input ${CEAL_EFFECT_APPROVAL_DECISION_CHECK_PHRASES.join(", ")}.`;
|
|
935
|
+
|
|
936
|
+
// packages/ceal-client-protocol/src/request-projection.ts
|
|
937
|
+
var CEAL_REQUEST_STATUSES = vocabulary(["pending", "partial", "completed", "cancelled", "failed", "unknown_effect", "reconciled", "incomplete", "contradiction"]);
|
|
938
|
+
var OWNER_WRITE_STATES = Object.freeze(["attempt_started", "provider_acknowledged", "verified", "outcome_unknown", "reconciled"]);
|
|
939
|
+
var OWNER_EFFECT_STATES = Object.freeze(["none", "applied", "failed", "unknown", "reconciled"]);
|
|
940
|
+
var OWNER_RECEIPT_STATES = Object.freeze(["pending", "available", "failed", "unknown", "reconciled"]);
|
|
941
|
+
|
|
942
|
+
// packages/ceal-client-protocol/src/request-graph-readiness.ts
|
|
943
|
+
var REQUEST_GRAPH_PRODUCER_TO_APPEND_SCHEMA = {
|
|
944
|
+
type: "object",
|
|
945
|
+
maxProperties: 4,
|
|
946
|
+
additionalProperties: false,
|
|
947
|
+
properties: {
|
|
948
|
+
operation_id: { type: "string", minLength: 1, maxLength: 128 },
|
|
949
|
+
edge_to_node_ref: { type: "string", minLength: 1, maxLength: 128 },
|
|
950
|
+
edge_from_node_refs: { type: "array", minItems: 1, maxItems: CEAL_REQUEST_MAX_GRAPH_NODES, items: { type: "string", minLength: 1, maxLength: 128 } },
|
|
951
|
+
remedy: { type: "string", minLength: 1, maxLength: 512 }
|
|
952
|
+
},
|
|
953
|
+
required: ["operation_id", "edge_to_node_ref", "remedy"]
|
|
954
|
+
};
|
|
955
|
+
var REQUEST_GRAPH_RESOLVER_CONSUMERS_SCHEMA = {
|
|
956
|
+
type: "object",
|
|
957
|
+
maxProperties: 2,
|
|
958
|
+
additionalProperties: false,
|
|
959
|
+
properties: {
|
|
960
|
+
consumer_node_refs: { type: "array", minItems: 1, maxItems: CEAL_REQUEST_MAX_GRAPH_NODES, items: { type: "string", minLength: 1, maxLength: 128 } },
|
|
961
|
+
remedy: { type: "string", minLength: 1, maxLength: 512 }
|
|
962
|
+
},
|
|
963
|
+
required: ["consumer_node_refs", "remedy"]
|
|
964
|
+
};
|
|
965
|
+
var REQUEST_GRAPH_RESOLVER_REUSE_SCHEMA = {
|
|
966
|
+
type: "object",
|
|
967
|
+
maxProperties: 3,
|
|
968
|
+
additionalProperties: false,
|
|
969
|
+
properties: {
|
|
970
|
+
reuse_node_ref: { type: "string", minLength: 1, maxLength: 128 },
|
|
971
|
+
consumer_node_refs: { type: "array", maxItems: CEAL_REQUEST_MAX_GRAPH_NODES, items: { type: "string", minLength: 1, maxLength: 128 } },
|
|
972
|
+
remedy: { type: "string", minLength: 1, maxLength: 512 }
|
|
973
|
+
},
|
|
974
|
+
required: ["reuse_node_ref", "consumer_node_refs", "remedy"]
|
|
975
|
+
};
|
|
976
|
+
var RESOLVER_OWNED_INPUT_SCHEMA = {
|
|
977
|
+
type: "object",
|
|
978
|
+
maxProperties: 5,
|
|
979
|
+
additionalProperties: false,
|
|
980
|
+
properties: {
|
|
981
|
+
property: { type: "string", minLength: 1, maxLength: 128 },
|
|
982
|
+
resolver_operation_id: { type: "string", minLength: 1, maxLength: 128 },
|
|
983
|
+
resolver_selection_template: CEAL_REQUEST_RESOLVER_SELECTION_JSON_SCHEMA,
|
|
984
|
+
producing_node_refs: { type: "array", maxItems: CEAL_REQUEST_MAX_RESOLVER_SELECTIONS, items: { type: "string", minLength: 1, maxLength: 128 } },
|
|
985
|
+
append_producer: REQUEST_GRAPH_PRODUCER_TO_APPEND_SCHEMA
|
|
986
|
+
},
|
|
987
|
+
required: ["property", "resolver_operation_id", "producing_node_refs"]
|
|
988
|
+
};
|
|
989
|
+
var USER_OPERANDS_TEMPLATE_SCHEMA = {
|
|
990
|
+
type: "object",
|
|
991
|
+
maxProperties: 2,
|
|
992
|
+
additionalProperties: false,
|
|
993
|
+
properties: {
|
|
994
|
+
required_properties: { type: "array", maxItems: MAX_OPERATION_INPUT_RESOLVER_BINDINGS, items: { type: "string", minLength: 1, maxLength: 128 } },
|
|
995
|
+
operands: { type: "object", properties: {}, maxProperties: MAX_OPERATION_INPUT_RESOLVER_BINDINGS, additionalProperties: {} }
|
|
996
|
+
},
|
|
997
|
+
required: ["required_properties", "operands"]
|
|
998
|
+
};
|
|
999
|
+
var CEAL_REQUEST_GRAPH_NODE_PREPARE_READINESS_JSON_SCHEMA = {
|
|
1000
|
+
type: "object",
|
|
1001
|
+
maxProperties: 9,
|
|
1002
|
+
additionalProperties: false,
|
|
1003
|
+
properties: {
|
|
1004
|
+
node_ref: { type: "string", minLength: 1, maxLength: 128 },
|
|
1005
|
+
operation_id: { type: "string", minLength: 1, maxLength: 128 },
|
|
1006
|
+
resolver_owned: { type: "array", maxItems: MAX_OPERATION_INPUT_RESOLVER_BINDINGS, items: RESOLVER_OWNED_INPUT_SCHEMA },
|
|
1007
|
+
gateway_derived: { type: "array", maxItems: MAX_OPERATION_INPUT_RESOLVER_BINDINGS, items: { type: "string", minLength: 1, maxLength: 128 } },
|
|
1008
|
+
result_bound: { type: "array", maxItems: MAX_OPERATION_INPUT_RESOLVER_BINDINGS, items: { type: "string", minLength: 1, maxLength: 128 } },
|
|
1009
|
+
user_operands_template: { type: "array", maxItems: CEAL_OPERATION_JSON_SCHEMA_BUDGET.max_alternatives, items: USER_OPERANDS_TEMPLATE_SCHEMA },
|
|
1010
|
+
unfed_resolver: REQUEST_GRAPH_RESOLVER_CONSUMERS_SCHEMA,
|
|
1011
|
+
redundant_resolver: REQUEST_GRAPH_RESOLVER_REUSE_SCHEMA,
|
|
1012
|
+
standing_occurrence: REQUEST_NODE_STANDING_OCCURRENCE_SCHEMA
|
|
1013
|
+
},
|
|
1014
|
+
required: ["node_ref", "operation_id", "resolver_owned", "gateway_derived"]
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
// packages/ceal-client-protocol/src/client-result-materialization.ts
|
|
1018
|
+
var REQUIRED_FIELDS = Object.freeze(["schema_version", "operation_id", "local_path", "content_json_pointer", "size_bytes", "sha256", "read_argv"]);
|
|
1019
|
+
var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS, "local_result_root", "model_disclosure"]);
|
|
1020
|
+
|
|
1021
|
+
// packages/ceal-client-protocol/src/conversation-contracts.ts
|
|
1022
|
+
var CEAL_APPROVAL_POLICY_KINDS = vocabulary(["requester_only", "named_users", "role_based"]);
|
|
1023
|
+
var CEAL_APPROVAL_TARGET_KINDS = vocabulary([
|
|
1024
|
+
"repo_change",
|
|
1025
|
+
"investigation",
|
|
1026
|
+
"skill_creation",
|
|
1027
|
+
"command_creation",
|
|
1028
|
+
"connector_creation",
|
|
1029
|
+
"generic"
|
|
1030
|
+
]);
|
|
1031
|
+
var CEAL_PROGRESS_PHASES = vocabulary(["request_review", "information_gathering", "work_execution", "result_check"]);
|
|
1032
|
+
|
|
1033
|
+
// packages/ceal-client-protocol/src/gateway-cache-origin-validation.ts
|
|
1034
|
+
var CEAL_MAX_CACHE_ORIGIN_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1035
|
+
|
|
1036
|
+
// packages/ceal-client-protocol/src/gateway-write-contract.ts
|
|
1037
|
+
var CEAL_GATEWAY_WRITE_CONTRACT_CLOSED_VOCABULARIES = Object.freeze({
|
|
1038
|
+
idempotency: CEAL_WRITE_IDEMPOTENCY_POSTURES,
|
|
1039
|
+
provider_readback: CEAL_WRITE_PROVIDER_READBACK_POSTURES,
|
|
1040
|
+
dry_run: Object.freeze(["supported", "unsupported"]),
|
|
1041
|
+
attribution: CEAL_WRITE_ATTRIBUTIONS,
|
|
1042
|
+
provenance_binding: Object.freeze(["gateway_attested_requester_event_v1"])
|
|
1043
|
+
});
|
|
1044
|
+
var REQUIRED_KEYS = Object.freeze(["side_effect_class", "idempotency", "provider_readback"]);
|
|
1045
|
+
|
|
1046
|
+
// packages/ceal-client-protocol/src/gateway-proof-claims.ts
|
|
1047
|
+
var CEAL_GATEWAY_PROOF_AXES = Object.freeze(["host_decision", "provider_execution", "production_audit"]);
|
|
1048
|
+
var CEAL_GATEWAY_PROOF_AXIS_NON_CLAIMS = Object.freeze({
|
|
1049
|
+
host_decision: null,
|
|
1050
|
+
provider_execution: "provider_execution_not_reached",
|
|
1051
|
+
production_audit: "production_audit_not_reached"
|
|
1052
|
+
});
|
|
1053
|
+
var CEAL_PROOF_LEVELS = vocabulary(["surface", "readiness", "worker_queued", "host_decision", "provider_roundtrip"]);
|
|
1054
|
+
var CEAL_GATEWAY_HOST_NON_CLAIM_ORDER = Object.freeze(["provider_execution_not_reached", "target_authorization_not_observed", "production_audit_not_reached"]);
|
|
1055
|
+
var CEAL_GATEWAY_ANNOUNCEMENT_POLICY_NON_CLAIMS = vocabulary([
|
|
1056
|
+
"policy_projection_does_not_authorize",
|
|
1057
|
+
"provider_roundtrip_not_established_by_discovery",
|
|
1058
|
+
"target_specific_scope_not_declared"
|
|
1059
|
+
]);
|
|
1060
|
+
|
|
1061
|
+
// packages/ceal-client-protocol/src/gateway-discovery-response-validation.ts
|
|
1062
|
+
var ANNOUNCEMENT_POLICY_CAPABILITY_BINDINGS = Object.freeze({
|
|
1063
|
+
"github.repository.get": [{ effect: "read", scopeStatementKind: "github_app_installation_repositories", providerAuthorityKind: "github_app" }],
|
|
1064
|
+
"collection.search": [{ effect: "read", scopeStatementKind: "github_app_installation_repositories", providerAuthorityKind: "github_app" }],
|
|
1065
|
+
"github.issue.get": [{ effect: "read", scopeStatementKind: "github_app_installation_repositories", providerAuthorityKind: "github_app" }],
|
|
1066
|
+
"github.pull_request.get": [{ effect: "read", scopeStatementKind: "github_app_installation_repositories", providerAuthorityKind: "github_app" }],
|
|
1067
|
+
"github.workflow_run.get": [{ effect: "read", scopeStatementKind: "github_app_installation_repositories", providerAuthorityKind: "github_app" }],
|
|
1068
|
+
"message.search": [{ effect: "read", scopeStatementKind: "slack_public_app_member_channels_only", providerAuthorityKind: "slack_app" }],
|
|
1069
|
+
"message.get": [{ effect: "read", scopeStatementKind: "slack_public_app_member_channels_only", providerAuthorityKind: "slack_app" }],
|
|
1070
|
+
"resource.resolve": [
|
|
1071
|
+
{ effect: "read", scopeStatementKind: "slack_public_app_member_channels_only", providerAuthorityKind: "slack_app" },
|
|
1072
|
+
{ effect: "read", scopeStatementKind: "notion_connected_logical_area", providerAuthorityKind: "notion_integration" }
|
|
1073
|
+
],
|
|
1074
|
+
"conversation.thread.get": [{ effect: "read", scopeStatementKind: "slack_public_app_member_channels_only", providerAuthorityKind: "slack_app" }],
|
|
1075
|
+
"notion.search": [{ effect: "read", scopeStatementKind: "notion_connected_logical_area", providerAuthorityKind: "notion_integration" }],
|
|
1076
|
+
"notion.page.get": [{ effect: "read", scopeStatementKind: "notion_connected_logical_area", providerAuthorityKind: "notion_integration" }],
|
|
1077
|
+
"calendar.availability": [{ effect: "read", scopeStatementKind: "google_workspace_calendar_read_only", providerAuthorityKind: "google_service_account" }],
|
|
1078
|
+
"calendar.event.search": [{ effect: "read", scopeStatementKind: "google_workspace_calendar_read_only", providerAuthorityKind: "google_service_account" }],
|
|
1079
|
+
"calendar.event.get": [{ effect: "read", scopeStatementKind: "google_workspace_calendar_read_only", providerAuthorityKind: "google_service_account" }],
|
|
1080
|
+
"file.search": [{ effect: "read", scopeStatementKind: "google_workspace_ceal_drive_or_direct_share_metadata", providerAuthorityKind: "google_service_account" }],
|
|
1081
|
+
"sheets.values.read": [{ effect: "read", scopeStatementKind: "google_workspace_ceal_drive_or_direct_share_sheet_ranges", providerAuthorityKind: "google_service_account" }],
|
|
1082
|
+
"sheets.tabs.list": [{ effect: "read", scopeStatementKind: "google_workspace_ceal_drive_or_direct_share_sheet_tabs", providerAuthorityKind: "google_service_account" }],
|
|
1083
|
+
"sheets.values.update": [{ effect: "write", scopeStatementKind: "google_workspace_ceal_drive_or_direct_share_editable_sheet_ranges", providerAuthorityKind: "google_service_account" }],
|
|
1084
|
+
"sheets.values.clear": [{ effect: "write", scopeStatementKind: "google_workspace_ceal_drive_or_direct_share_editable_sheet_clear_ranges", providerAuthorityKind: "google_service_account" }]
|
|
1085
|
+
});
|
|
1086
|
+
var ANNOUNCEMENT_SCOPE_STATEMENTS = Object.freeze({
|
|
1087
|
+
github_app_installation_repositories: "Repositories in the installed GitHub App installation.",
|
|
1088
|
+
slack_public_app_member_channels_only: "Public channels where the installed Slack app is a member; private channels, direct messages, multi-person direct messages, and requester membership are not declared by this connector.",
|
|
1089
|
+
notion_connected_logical_area: "Connected Notion logical area under provider-enforced sharing; descendant inventory is not declared.",
|
|
1090
|
+
google_workspace_calendar_read_only: "Approved Calendar availability and event reads only; Calendar mutation is not declared.",
|
|
1091
|
+
google_workspace_ceal_drive_or_direct_share_metadata: "Metadata search for files in the organization shared drive named Ceal Drive and files directly shared with the provider application; file-content read and mutation are not declared.",
|
|
1092
|
+
google_workspace_ceal_drive_or_direct_share_sheet_ranges: "Bounded values reads from governed Google Sheets in the organization shared drive named Ceal Drive and directly shared files; file mutation is not declared.",
|
|
1093
|
+
google_workspace_ceal_drive_or_direct_share_editable_sheet_ranges: "Bounded values updates in governed editable Google Sheets in the organization shared drive named Ceal Drive and directly shared files; Docs, Slides, and other Drive file mutation are not declared.",
|
|
1094
|
+
google_workspace_ceal_drive_or_direct_share_editable_sheet_clear_ranges: "Bounded values clears in governed editable Google Sheets in the organization shared drive named Ceal Drive and directly shared files; Docs, Slides, and other Drive file mutation are not declared."
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
// packages/ceal-client-protocol/src/gateway-write-identity.ts
|
|
1098
|
+
var CEAL_GATEWAY_WRITE_IDENTITY_ROLES = Object.freeze(["replay_identity", "lookup_handle", "collision_evidence"]);
|
|
1099
|
+
var CEAL_GATEWAY_WRITE_IDENTITY_FIELDS = Object.freeze({
|
|
1100
|
+
replay_identity: "idempotency_claim_sha256",
|
|
1101
|
+
lookup_handle: "write_request_sha256",
|
|
1102
|
+
collision_evidence: "normalized_mutation_sha256"
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
// packages/ceal-client-protocol/src/gateway-scoped-identity-projection-validation.ts
|
|
1106
|
+
var PROJECTION_REVISION_KEYS = Object.freeze(["graph_revision", "subject_key_revision", "projection_revision"]);
|
|
1107
|
+
|
|
1108
|
+
// packages/ceal-client-protocol/src/protocol-negotiation.ts
|
|
1109
|
+
var CEAL_SUPPORTED_GATEWAY_PROTOCOL_RANGE = Object.freeze({
|
|
1110
|
+
minimum: CEAL_PROTOCOL_VERSION,
|
|
1111
|
+
maximum: CEAL_PROTOCOL_VERSION
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
// packages/ceal-client-protocol/src/admin-projection-vocabulary.ts
|
|
1115
|
+
var ADMIN_PROJECTION_STATES = vocabulary(["ready", "empty", "partial", "unknown"]);
|
|
1116
|
+
var ADMIN_VIEW_KINDS = vocabulary(["request_detail", "resource_access_timeline", "weekly_usage"]);
|
|
1117
|
+
var ADMIN_GAP_KINDS = vocabulary(["unknown", "unavailable", "partial"]);
|
|
1118
|
+
var ADMIN_UNAVAILABLE_REASONS = vocabulary([
|
|
1119
|
+
"not_recorded",
|
|
1120
|
+
"legacy",
|
|
1121
|
+
"invalid_observation",
|
|
1122
|
+
"redacted",
|
|
1123
|
+
"owner_unavailable",
|
|
1124
|
+
"unit_unknown",
|
|
1125
|
+
"retention_gap",
|
|
1126
|
+
"source_owner_limit"
|
|
1127
|
+
]);
|
|
1128
|
+
var ADMIN_SOURCE_OWNERS = vocabulary([
|
|
1129
|
+
"request",
|
|
1130
|
+
"operation",
|
|
1131
|
+
"authority",
|
|
1132
|
+
"occurrence",
|
|
1133
|
+
"approval",
|
|
1134
|
+
"audit",
|
|
1135
|
+
"provider_readback",
|
|
1136
|
+
"host_observation",
|
|
1137
|
+
"person",
|
|
1138
|
+
"resource",
|
|
1139
|
+
"prompt",
|
|
1140
|
+
"usage"
|
|
1141
|
+
]);
|
|
1142
|
+
var ADMIN_COMPLETENESS_STATES = vocabulary(["complete", "partial", "unknown"]);
|
|
1143
|
+
var ADMIN_OPERATION_OUTCOMES = vocabulary(["completed", "failed", "denied", "cancelled", "unknown"]);
|
|
1144
|
+
var ADMIN_APPROVAL_DISPOSITIONS = CEAL_OPERATION_APPROVAL_DISPOSITIONS;
|
|
1145
|
+
var ADMIN_APPROVAL_POSTURES = vocabulary([...ADMIN_APPROVAL_DISPOSITIONS, "not_applicable"]);
|
|
1146
|
+
var ADMIN_LINEAGE_NODE_KINDS = vocabulary(["source_read", "synthesis_preview", "approval", "effect", "readback"]);
|
|
1147
|
+
|
|
1148
|
+
// packages/ceal-client-protocol/src/actor-kind.ts
|
|
1149
|
+
var CEAL_ACTOR_KINDS = vocabulary(["human", "bot", "app", "unknown"]);
|
|
1150
|
+
|
|
1151
|
+
// packages/ceal-client-protocol/src/leased-consumer-vocabulary.ts
|
|
1152
|
+
var CEAL_TERMINAL_DISPOSITIONS = vocabulary(["completed", "failed", "cancelled"]);
|
|
1153
|
+
var CEAL_LEASE_DISPOSITIONS = vocabulary([...CEAL_TERMINAL_DISPOSITIONS, "deferred"]);
|
|
1154
|
+
var CEAL_AUTOMATIC_FEEDBACK_INTENTS = vocabulary(["reaction", "progress_start", "progress_finish"]);
|
|
1155
|
+
var CEAL_LEASED_MESSAGE_DELIVERY_CAPABILITY_IDS = vocabulary(["message.create", "message.update", "message.delete"]);
|
|
1156
|
+
var CEAL_REPOSITORY_VISIBILITIES = vocabulary(["public", "private", "internal"]);
|
|
1157
|
+
var CEAL_RESULT_DELIVERY_OFFERS = vocabulary(["pending", "offered", "transport_lost"]);
|
|
1158
|
+
var CEAL_RESULT_DELIVERY_STATES = vocabulary(["unavailable", ...CEAL_RESULT_DELIVERY_OFFERS]);
|
|
1159
|
+
var CEAL_PROVIDER_OUTCOMES = vocabulary(["not_attempted", "outcome_unknown", "verified"]);
|
|
1160
|
+
var CEAL_ACTIVITY_GAP_CODES = vocabulary([
|
|
1161
|
+
"provider_page_budget",
|
|
1162
|
+
"related_item_budget",
|
|
1163
|
+
"provider_cursor_unavailable",
|
|
1164
|
+
"malformed_provider_page",
|
|
1165
|
+
"visibility_incomplete",
|
|
1166
|
+
"unsupported_event_kind"
|
|
1167
|
+
]);
|
|
1168
|
+
var CEAL_ACTIVITY_COVERAGE_STATUSES = vocabulary(["complete_for_target", "continuation_required", "blocked"]);
|
|
1169
|
+
var CEAL_ACTIVITY_EVENT_KINDS = vocabulary([
|
|
1170
|
+
"slack.message",
|
|
1171
|
+
"slack.reply",
|
|
1172
|
+
"github.commit.authored",
|
|
1173
|
+
"github.issue.opened",
|
|
1174
|
+
"github.pull_request.opened",
|
|
1175
|
+
"github.review.submitted",
|
|
1176
|
+
"github.issue_comment.created",
|
|
1177
|
+
"github.review_comment.created",
|
|
1178
|
+
"calendar.organized",
|
|
1179
|
+
"calendar.accepted_invite",
|
|
1180
|
+
"calendar.recorded"
|
|
1181
|
+
]);
|
|
1182
|
+
var CEAL_ACTIVITY_TIMESTAMP_BASES = vocabulary([
|
|
1183
|
+
"message_created",
|
|
1184
|
+
"commit_authored",
|
|
1185
|
+
"resource_created",
|
|
1186
|
+
"review_submitted",
|
|
1187
|
+
"comment_created",
|
|
1188
|
+
"event_start",
|
|
1189
|
+
"all_day_start"
|
|
1190
|
+
]);
|
|
1191
|
+
var CEAL_ARTIFACT_STAGE_TERMINALS = vocabulary(["chunk_accepted", "artifact_ready", "idempotency_replayed"]);
|
|
1192
|
+
var CEAL_READ_ITEM_KINDS = vocabulary(["conversation", "identity", "usergroup", "message", "file", "document"]);
|
|
1193
|
+
var CEAL_RESOURCE_RESOLVE_KINDS = vocabulary(["conversation", "identity", "usergroup", "permalink"]);
|
|
1194
|
+
var CEAL_OPAQUE_HANDLE_KINDS = vocabulary(["target", "message", "thread", "artifact", "document", "object"]);
|
|
1195
|
+
var CEAL_WRITE_OBJECT_KINDS = vocabulary(["document", "comment", "cell_range"]);
|
|
1196
|
+
var CEAL_CONVERSATION_KINDS = vocabulary(["channel", "dm", "group"]);
|
|
1197
|
+
var CEAL_FILE_TYPE_FAMILIES = vocabulary(["all", "image", "pdf"]);
|
|
1198
|
+
var CEAL_GITHUB_ISSUE_STATES = vocabulary(["open", "closed", "all"]);
|
|
1199
|
+
var CEAL_NOTION_UPDATABLE_BLOCK_TYPES = vocabulary([
|
|
1200
|
+
"paragraph",
|
|
1201
|
+
"heading_1",
|
|
1202
|
+
"heading_2",
|
|
1203
|
+
"heading_3",
|
|
1204
|
+
"bulleted_list_item",
|
|
1205
|
+
"numbered_list_item",
|
|
1206
|
+
"to_do",
|
|
1207
|
+
"code"
|
|
1208
|
+
]);
|
|
1209
|
+
var CEAL_NOTION_NEUTRAL_TYPES = vocabulary([
|
|
1210
|
+
"text",
|
|
1211
|
+
"number",
|
|
1212
|
+
"boolean",
|
|
1213
|
+
"date",
|
|
1214
|
+
"select",
|
|
1215
|
+
"multi_select",
|
|
1216
|
+
"people",
|
|
1217
|
+
"url",
|
|
1218
|
+
"email",
|
|
1219
|
+
"phone",
|
|
1220
|
+
"unsupported"
|
|
1221
|
+
]);
|
|
1222
|
+
var CEAL_NOTION_IDENTITY_GAP_REASONS = vocabulary([
|
|
1223
|
+
"identity_unavailable",
|
|
1224
|
+
"identity_stale",
|
|
1225
|
+
"identity_ambiguous",
|
|
1226
|
+
"identity_revoked",
|
|
1227
|
+
"identity_unlinked"
|
|
1228
|
+
]);
|
|
1229
|
+
var CEAL_NOTION_STRING_VALUE_TYPES = vocabulary(["text", "select", "url", "email", "phone"]);
|
|
1230
|
+
var CEAL_UNREAD_REASONS = vocabulary(["blocked", "unavailable", "too_large", "unsupported", "download_failed", "permission_denied"]);
|
|
1231
|
+
var CEAL_PLAN_ITEM_STATUSES = vocabulary(["pending", "active", "completed"]);
|
|
1232
|
+
var CEAL_FINAL_PRESENTATION_INTENTS = vocabulary(["final", "stop", "transient_notice"]);
|
|
1233
|
+
var CEAL_PRESENTATION_INTENTS = vocabulary(["progress", ...CEAL_FINAL_PRESENTATION_INTENTS]);
|
|
1234
|
+
|
|
1235
|
+
// packages/ceal-client-protocol/src/personal-client-vocabulary.ts
|
|
1236
|
+
var CEAL_PERSONAL_CLIENT_BINDING_KEYS = vocabulary([
|
|
1237
|
+
"profile_ref",
|
|
1238
|
+
"membership_ref",
|
|
1239
|
+
"registration_ref",
|
|
1240
|
+
"client_ref",
|
|
1241
|
+
"subject_ref",
|
|
1242
|
+
"instance_ref"
|
|
1243
|
+
]);
|
|
1244
|
+
var CEAL_ENROLLMENT_FAILURE_CODES = vocabulary(["enrollment_invalid", "enrollment_expired", "enrollment_used"]);
|
|
1245
|
+
var CEAL_REFRESH_FAILURE_CODES = vocabulary([
|
|
1246
|
+
"refresh_invalid",
|
|
1247
|
+
"refresh_expired",
|
|
1248
|
+
"refresh_inactive",
|
|
1249
|
+
"refresh_replayed",
|
|
1250
|
+
"refresh_revoked",
|
|
1251
|
+
"authority_replaced",
|
|
1252
|
+
"refresh_recovery_unavailable"
|
|
1253
|
+
]);
|
|
1254
|
+
|
|
1255
|
+
// packages/ceal-client-protocol/src/device-enrollment.ts
|
|
1256
|
+
var CEAL_DEVICE_ENROLLMENT_POLL_FAILURE_CODES = Object.freeze(["unsupported_feature", "recovery_required", "expired"]);
|
|
1257
|
+
var POLL_RESULT_KEYS = Object.freeze(["retry_after_ms", "schema_version", "status"]);
|
|
1258
|
+
|
|
1259
|
+
// packages/ceal-client-protocol/src/personal-client-session.ts
|
|
1260
|
+
var REFRESH_FAILURE_CODES = new Set(CEAL_REFRESH_FAILURE_CODES);
|
|
1261
|
+
|
|
1262
|
+
// packages/ceal-client-protocol/src/index.ts
|
|
1263
|
+
var MAX_REQUEST_BYTES = 32 * 1024;
|
|
1264
|
+
var MAX_ARGUMENT_BYTES = 16 * 1024;
|
|
1265
|
+
var HANDSHAKE_IDENTITY_KEYS = Object.freeze(["membership_ref", "registration_ref", "client_ref", "subject_ref", "instance_ref"]);
|
|
1266
|
+
var AUDIT_IDENTITY_KEYS = Object.freeze(["event_ref", "membership_ref", "registration_ref", "client_ref", "subject_ref", "instance_ref"]);
|
|
1267
|
+
var FAILURE_RESPONSE_KEYS = Object.freeze(["error", "ok", "proof_ref_or_unavailable", "protocol_version", "request_id"]);
|
|
1268
|
+
var MAX_RECOVERY_RETRY_AFTER_MS = 60 * 60 * 1e3;
|
|
1269
|
+
|
|
1270
|
+
// host/claude/src/claude-decoder.ts
|
|
1271
|
+
var MAX_HOOK_BYTES = 256 * 1024;
|
|
1272
|
+
var MAX_TEXT = 128 * 1024;
|
|
1273
|
+
var SESSION_SOURCES = ["startup", "resume", "clear", "compact", "fork"];
|
|
1274
|
+
var EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
1275
|
+
var STOP_FAILURES = ["rate_limit", "overloaded", "authentication_failed", "oauth_org_not_allowed", "account_on_hold", "billing_error", "invalid_request", "model_not_found", "server_error", "max_output_tokens", "unknown"];
|
|
1276
|
+
function decodeClaudeHookInput(event, raw) {
|
|
1277
|
+
const rawBytes = Buffer.byteLength(raw, "utf8");
|
|
1278
|
+
if (rawBytes > MAX_HOOK_BYTES) throw invalid2(`Claude hook input is too large (${refusalFact("bytes", rawBytes, `at most ${MAX_HOOK_BYTES} UTF-8 bytes`)}).`);
|
|
1279
|
+
let value;
|
|
1280
|
+
try {
|
|
1281
|
+
value = JSON.parse(raw);
|
|
1282
|
+
} catch {
|
|
1283
|
+
throw invalid2("Claude hook input is not valid JSON.");
|
|
1284
|
+
}
|
|
1285
|
+
if (!record(value) || value.hook_event_name !== event) throw invalid2(`Claude hook event identity does not match the configured event (${refusalFact("hook_event_name", record(value) ? value.hook_event_name : value, `expected ${event}`)}).`);
|
|
1286
|
+
const common = decodeCommon(value, event);
|
|
1287
|
+
if (event === "SessionStart") return decodeSessionStart(value, common);
|
|
1288
|
+
const promptId = requiredText(value.prompt_id, 256, "Claude turn hook is missing prompt_id.");
|
|
1289
|
+
if (event === "UserPromptSubmit") return decodeUserPrompt(value, common, promptId);
|
|
1290
|
+
return event === "Stop" ? decodeStop(value, common, promptId) : decodeStopFailure(value, common, promptId);
|
|
1291
|
+
}
|
|
1292
|
+
function decodeSessionStart(value, common) {
|
|
1293
|
+
if (!isSessionSource(value.source)) throw invalid2("Claude SessionStart source is invalid.");
|
|
1294
|
+
const model = optionalText(value.model, 256);
|
|
1295
|
+
return { ...common, hook_event_name: "SessionStart", source: value.source, ...model === void 0 ? {} : { model } };
|
|
1296
|
+
}
|
|
1297
|
+
function decodeUserPrompt(value, common, promptId) {
|
|
1298
|
+
return { ...common, hook_event_name: "UserPromptSubmit", prompt_id: promptId, prompt: requiredText(value.prompt, MAX_TEXT, "Claude prompt is invalid.") };
|
|
1299
|
+
}
|
|
1300
|
+
function decodeStop(value, common, promptId) {
|
|
1301
|
+
if (typeof value.stop_hook_active !== "boolean") throw invalid2("Claude Stop state is invalid.");
|
|
1302
|
+
return { ...common, hook_event_name: "Stop", prompt_id: promptId, stop_hook_active: value.stop_hook_active, last_assistant_message: requiredText(value.last_assistant_message, MAX_TEXT, "Claude Stop response is invalid.") };
|
|
1303
|
+
}
|
|
1304
|
+
function decodeStopFailure(value, common, promptId) {
|
|
1305
|
+
if (!isStopFailure(value.error)) throw invalid2("Claude StopFailure error is invalid.");
|
|
1306
|
+
const errorDetails = optionalText(value.error_details, 4096);
|
|
1307
|
+
const lastMessage = optionalText(value.last_assistant_message, MAX_TEXT);
|
|
1308
|
+
return { ...common, hook_event_name: "StopFailure", prompt_id: promptId, error: value.error, ...errorDetails === void 0 ? {} : { error_details: errorDetails }, ...lastMessage === void 0 ? {} : { last_assistant_message: lastMessage } };
|
|
1309
|
+
}
|
|
1310
|
+
function claudeLocalSessionRef(sessionId) {
|
|
1311
|
+
return `claude-session:${digest(sessionId)}`;
|
|
1312
|
+
}
|
|
1313
|
+
function claudeLocalTurnRef(sessionId, promptId) {
|
|
1314
|
+
return `claude-turn:${cealRequestSha256({ session_id: sessionId, prompt_id: promptId })}`;
|
|
1315
|
+
}
|
|
1316
|
+
function claudeLocalInputRef(sessionId, promptId) {
|
|
1317
|
+
return `claude-input:${cealRequestSha256({ session_id: sessionId, prompt_id: promptId })}`;
|
|
1318
|
+
}
|
|
1319
|
+
function claudeWorkspaceRef(cwd) {
|
|
1320
|
+
return `workspace:${digest(cwd)}`;
|
|
1321
|
+
}
|
|
1322
|
+
function decodeCommon(value, event) {
|
|
1323
|
+
const effort = decodeEffort(value.effort);
|
|
1324
|
+
const promptId = optionalText(value.prompt_id, 256);
|
|
1325
|
+
const permissionMode = optionalText(value.permission_mode, 64);
|
|
1326
|
+
return {
|
|
1327
|
+
session_id: requiredText(value.session_id, 256, "Claude session identity is invalid."),
|
|
1328
|
+
...promptId === void 0 ? {} : { prompt_id: promptId },
|
|
1329
|
+
transcript_path: requiredAbsolutePath(value.transcript_path, "Claude transcript path is invalid."),
|
|
1330
|
+
cwd: requiredAbsolutePath(value.cwd, "Claude working directory is invalid."),
|
|
1331
|
+
...permissionMode === void 0 ? {} : { permission_mode: permissionMode },
|
|
1332
|
+
...effort === void 0 ? {} : { effort },
|
|
1333
|
+
hook_event_name: event
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
function decodeEffort(value) {
|
|
1337
|
+
if (value === void 0) return void 0;
|
|
1338
|
+
if (!record(value) || !isEffortLevel(value.level)) throw invalid2("Claude effort is invalid.");
|
|
1339
|
+
return { level: value.level };
|
|
1340
|
+
}
|
|
1341
|
+
function isSessionSource(value) {
|
|
1342
|
+
return typeof value === "string" && SESSION_SOURCES.some((source) => source === value);
|
|
1343
|
+
}
|
|
1344
|
+
function isEffortLevel(value) {
|
|
1345
|
+
return typeof value === "string" && EFFORT_LEVELS.some((effort) => effort === value);
|
|
1346
|
+
}
|
|
1347
|
+
function isStopFailure(value) {
|
|
1348
|
+
return typeof value === "string" && STOP_FAILURES.some((failure) => failure === value);
|
|
1349
|
+
}
|
|
1350
|
+
function requiredAbsolutePath(value, message) {
|
|
1351
|
+
const text = requiredText(value, 4096, message);
|
|
1352
|
+
if (!text.startsWith("/") || text.includes("\0")) throw invalid2(message);
|
|
1353
|
+
return text;
|
|
1354
|
+
}
|
|
1355
|
+
function requiredText(value, max, message) {
|
|
1356
|
+
if (typeof value !== "string") throw invalid2(`${message} (${refusalFact("kind", typeof value, "string")})`);
|
|
1357
|
+
if (value.length === 0 || value.length > max) throw invalid2(`${message} (${refusalFact("characters", value.length, `from 1 through ${max}`)})`);
|
|
1358
|
+
const nul = value.indexOf("\0");
|
|
1359
|
+
if (nul >= 0) throw invalid2(`${message} (${refusalFact("nul_index", nul, "no NUL code point")})`);
|
|
1360
|
+
return value;
|
|
1361
|
+
}
|
|
1362
|
+
function optionalText(value, max) {
|
|
1363
|
+
if (value === void 0) return void 0;
|
|
1364
|
+
return requiredText(value, max, "Claude optional hook field is invalid.");
|
|
1365
|
+
}
|
|
1366
|
+
function digest(value) {
|
|
1367
|
+
return createHash2("sha256").update(value, "utf8").digest("hex");
|
|
1368
|
+
}
|
|
1369
|
+
function record(value) {
|
|
1370
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1371
|
+
}
|
|
1372
|
+
function invalid2(message) {
|
|
1373
|
+
return new ClaudeHostError("invalid_input", message);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// host/claude/src/claude-outbox.ts
|
|
1377
|
+
import { open as open2, mkdir as mkdir2, readdir, readFile, unlink as unlink2 } from "node:fs/promises";
|
|
1378
|
+
import { join as join2 } from "node:path";
|
|
1379
|
+
|
|
1380
|
+
// host/relay/src/producer-frame.ts
|
|
1381
|
+
var HOST_PRODUCER_FRAME_SCHEMA = "ceal.host_producer_frame.v1";
|
|
1382
|
+
var HOST_PRODUCER_MAX_JSON_BYTES = 262144;
|
|
1383
|
+
var SAFE_REF2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
1384
|
+
var DIGEST2 = /^[a-f0-9]{64}$/u;
|
|
1385
|
+
var PRODUCERS = /* @__PURE__ */ new Set(["codex", "claude"]);
|
|
1386
|
+
var HOST_PRODUCER_FRAME_KINDS = ["session_started", "turn_started", "user_input", "turn_terminal", "producer_status"];
|
|
1387
|
+
var FRAME_KINDS = new Set(HOST_PRODUCER_FRAME_KINDS);
|
|
1388
|
+
function isHostProducerFrameKind(value) {
|
|
1389
|
+
return typeof value === "string" && FRAME_KINDS.has(value);
|
|
1390
|
+
}
|
|
1391
|
+
var HostProducerFrameError = class extends Error {
|
|
1392
|
+
code;
|
|
1393
|
+
constructor(code3, message = code3) {
|
|
1394
|
+
super(message);
|
|
1395
|
+
this.code = code3;
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
function encodeHostProducerFrame(frame) {
|
|
1399
|
+
validateFrame(frame);
|
|
1400
|
+
const json = Buffer.from(JSON.stringify(frame), "utf8");
|
|
1401
|
+
if (json.byteLength > HOST_PRODUCER_MAX_JSON_BYTES) throw new HostProducerFrameError("frame_too_large", `frame_too_large: ${refusalFact("frame_bytes", json.byteLength, `at most ${HOST_PRODUCER_MAX_JSON_BYTES}`)}`);
|
|
1402
|
+
const result = Buffer.allocUnsafe(json.byteLength + 4);
|
|
1403
|
+
result.writeUInt32BE(json.byteLength, 0);
|
|
1404
|
+
json.copy(result, 4);
|
|
1405
|
+
return result;
|
|
1406
|
+
}
|
|
1407
|
+
function decodeHostProducerFrame(input) {
|
|
1408
|
+
if (input.byteLength < 4) throw new HostProducerFrameError("frame_truncated", `frame_truncated: ${refusalFact("frame_bytes", input.byteLength, "at least 4")}`);
|
|
1409
|
+
const length = input.readUInt32BE(0);
|
|
1410
|
+
if (length > HOST_PRODUCER_MAX_JSON_BYTES) throw new HostProducerFrameError("frame_too_large", `frame_too_large: ${refusalFact("declared_bytes", length, `at most ${HOST_PRODUCER_MAX_JSON_BYTES}`)}`);
|
|
1411
|
+
if (length === 0) throw new HostProducerFrameError("invalid_frame_length");
|
|
1412
|
+
if (input.byteLength !== length + 4) throw new HostProducerFrameError(input.byteLength < length + 4 ? "frame_truncated" : "invalid_frame_length");
|
|
1413
|
+
const bytes = input.subarray(4);
|
|
1414
|
+
const text = new TextDecoder("utf-8", { fatal: true });
|
|
1415
|
+
let value;
|
|
1416
|
+
try {
|
|
1417
|
+
value = JSON.parse(text.decode(bytes));
|
|
1418
|
+
} catch (error) {
|
|
1419
|
+
if (error instanceof TypeError) throw new HostProducerFrameError("invalid_frame_encoding");
|
|
1420
|
+
throw new HostProducerFrameError("frame_truncated");
|
|
1421
|
+
}
|
|
1422
|
+
validateFrame(value);
|
|
1423
|
+
return value;
|
|
1424
|
+
}
|
|
1425
|
+
function hostProducerPromptRef(promptSha256) {
|
|
1426
|
+
if (!DIGEST2.test(promptSha256)) throw new TypeError("Canonical Host prompt digest is invalid.");
|
|
1427
|
+
return `ceal-host-prompt:${promptSha256}`;
|
|
1428
|
+
}
|
|
1429
|
+
function validateFrame(value) {
|
|
1430
|
+
if (!record2(value)) throw new HostProducerFrameError("invalid_frame_shape");
|
|
1431
|
+
if (value.schema_version !== HOST_PRODUCER_FRAME_SCHEMA) throw new HostProducerFrameError("unsupported_frame_schema", `unsupported_frame_schema: ${refusalFact("schema_version", value.schema_version, HOST_PRODUCER_FRAME_SCHEMA)}`);
|
|
1432
|
+
if (!exactKeys(value, ["schema_version", "producer_id", "producer_installation_ref", "producer_epoch", "sequence", "frame_kind", "occurred_at", "payload"])) throw new HostProducerFrameError("invalid_frame_shape");
|
|
1433
|
+
if (!PRODUCERS.has(String(value.producer_id)) || !safeRef(value.producer_installation_ref)) throw new HostProducerFrameError("invalid_producer_identity");
|
|
1434
|
+
if (!positive(value.producer_epoch) || !positive(value.sequence) || !hostProducerFrameKind(value.frame_kind) || !timestamp(value.occurred_at) || !record2(value.payload)) throw new HostProducerFrameError("invalid_frame_shape");
|
|
1435
|
+
validatePayload(value.frame_kind, value.payload);
|
|
1436
|
+
}
|
|
1437
|
+
function validatePayload(kind, value) {
|
|
1438
|
+
const valid = kind === "session_started" ? validSession(value) : kind === "turn_started" ? validTurn(value) : kind === "user_input" ? validInput(value) : kind === "turn_terminal" ? validTerminal(value) : validStatus(value);
|
|
1439
|
+
if (!valid) throw new HostProducerFrameError("invalid_frame_shape");
|
|
1440
|
+
}
|
|
1441
|
+
function validSession(value) {
|
|
1442
|
+
return exactOptional(value, ["local_session_ref", "working_directory_ref"], ["model", "host_version", "observations"]) && safeRef(value.local_session_ref) && safeRef(value.working_directory_ref) && optionalText2(value.model, 256) && optionalText2(value.host_version, 256) && validObservations(value.observations);
|
|
1443
|
+
}
|
|
1444
|
+
function validTurn(value) {
|
|
1445
|
+
return exactOptional(value, ["local_session_ref", "local_turn_ref"], ["model", "observations"]) && safeRef(value.local_session_ref) && safeRef(value.local_turn_ref) && optionalText2(value.model, 256) && validObservations(value.observations);
|
|
1446
|
+
}
|
|
1447
|
+
function validInput(value) {
|
|
1448
|
+
return exactOptional(value, ["local_session_ref", "local_turn_ref", "local_input_ref", "origin_input", "observations"], ["decision", "local_turn_aliases", "context_expires_at"]) && safeRef(value.local_session_ref) && safeRef(value.local_turn_ref) && safeRef(value.local_input_ref) && validInputObservations(value.observations) && typeof value.origin_input === "boolean" && validInputOptionals(value);
|
|
1449
|
+
}
|
|
1450
|
+
function validInputObservations(value) {
|
|
1451
|
+
return validObservations(value) && Array.isArray(value) && value.filter((item) => record2(item) && item.observation_kind === "prompt").length === 1;
|
|
1452
|
+
}
|
|
1453
|
+
function validInputOptionals(value) {
|
|
1454
|
+
return validDecision(value.decision) && optionalRefs(value.local_turn_aliases) && (value.context_expires_at === void 0 || timestamp(value.context_expires_at));
|
|
1455
|
+
}
|
|
1456
|
+
function validTerminal(value) {
|
|
1457
|
+
return exactOptional(value, ["local_session_ref", "local_turn_ref", "terminal_state"], ["abort_reason", "duration_ms", "time_to_first_token_ms", "usage", "observations"]) && safeRef(value.local_session_ref) && safeRef(value.local_turn_ref) && validTerminalDisposition(value.terminal_state, value.abort_reason) && optionalNonNegative(value.duration_ms) && optionalNonNegative(value.time_to_first_token_ms) && (value.usage === void 0 || jsonValue(value.usage)) && validObservations(value.observations);
|
|
1458
|
+
}
|
|
1459
|
+
function validTerminalDisposition(state, reason) {
|
|
1460
|
+
return (state === "completed" || state === "aborted") && (reason === void 0 || state === "aborted" && safeRef(reason));
|
|
1461
|
+
}
|
|
1462
|
+
function validStatus(value) {
|
|
1463
|
+
return exactOptional(value, ["status", "reason_code"], ["detail"]) && ["ready", "degraded", "stopping"].includes(String(value.status)) && safeRef(value.reason_code) && optionalText2(value.detail, 4096);
|
|
1464
|
+
}
|
|
1465
|
+
function validDecision(value) {
|
|
1466
|
+
return value === void 0 || record2(value) && exactKeys(value, ["decision", "approval_ref"]) && (value.decision === "approved" || value.decision === "denied") && safeRef(value.approval_ref);
|
|
1467
|
+
}
|
|
1468
|
+
function optionalText2(value, maxLength) {
|
|
1469
|
+
return value === void 0 || typeof value === "string" && value.length > 0 && value.length <= maxLength;
|
|
1470
|
+
}
|
|
1471
|
+
function optionalNonNegative(value) {
|
|
1472
|
+
return value === void 0 || typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
1473
|
+
}
|
|
1474
|
+
function optionalRefs(value) {
|
|
1475
|
+
return value === void 0 || Array.isArray(value) && value.length <= 16 && value.every(safeRef);
|
|
1476
|
+
}
|
|
1477
|
+
function validObservations(value) {
|
|
1478
|
+
if (value === void 0) return true;
|
|
1479
|
+
if (!Array.isArray(value) || value.length > 32) return false;
|
|
1480
|
+
return value.every((item) => {
|
|
1481
|
+
try {
|
|
1482
|
+
validateCealHostObservationDraft(item);
|
|
1483
|
+
return true;
|
|
1484
|
+
} catch {
|
|
1485
|
+
return false;
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
function jsonValue(value, depth = 0) {
|
|
1490
|
+
if (depth > 16) return false;
|
|
1491
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
1492
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
1493
|
+
if (Array.isArray(value)) return value.length <= 256 && value.every((item) => jsonValue(item, depth + 1));
|
|
1494
|
+
return record2(value) && Object.keys(value).length <= 256 && Object.entries(value).every(([key, item]) => key.length > 0 && key.length <= 128 && jsonValue(item, depth + 1));
|
|
1495
|
+
}
|
|
1496
|
+
function record2(value) {
|
|
1497
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1498
|
+
}
|
|
1499
|
+
function safeRef(value) {
|
|
1500
|
+
return typeof value === "string" && SAFE_REF2.test(value);
|
|
1501
|
+
}
|
|
1502
|
+
function positive(value) {
|
|
1503
|
+
return Number.isSafeInteger(value) && Number(value) > 0;
|
|
1504
|
+
}
|
|
1505
|
+
function timestamp(value) {
|
|
1506
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
1507
|
+
}
|
|
1508
|
+
function hostProducerFrameKind(value) {
|
|
1509
|
+
return isHostProducerFrameKind(value);
|
|
1510
|
+
}
|
|
1511
|
+
function exactKeys(value, keys) {
|
|
1512
|
+
return JSON.stringify(Object.keys(value).sort()) === JSON.stringify([...keys].sort());
|
|
1513
|
+
}
|
|
1514
|
+
function exactOptional(value, required, optional) {
|
|
1515
|
+
const keys = Object.keys(value);
|
|
1516
|
+
return required.every((key) => keys.includes(key)) && keys.every((key) => required.includes(key) || optional.includes(key));
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// host/relay/src/durable-atomic-replacement.ts
|
|
1520
|
+
import { O_RDONLY } from "node:constants";
|
|
1521
|
+
import { lstat, mkdir, open, rename, unlink } from "node:fs/promises";
|
|
1522
|
+
import { dirname } from "node:path";
|
|
1523
|
+
var DEFAULT_PORT = {
|
|
1524
|
+
platform: process.platform,
|
|
1525
|
+
mkdir: (path, options) => mkdir(path, options),
|
|
1526
|
+
lstat: (path) => lstat(path),
|
|
1527
|
+
open_file: (path) => open(path, "wx", 384),
|
|
1528
|
+
rename,
|
|
1529
|
+
unlink,
|
|
1530
|
+
open_directory: (path) => open(path, O_RDONLY),
|
|
1531
|
+
unique_ref: () => `${process.pid}.${crypto.randomUUID()}`
|
|
1532
|
+
};
|
|
1533
|
+
async function durableAtomicReplace(target, data, port = DEFAULT_PORT) {
|
|
1534
|
+
const parent = dirname(target);
|
|
1535
|
+
await port.mkdir(parent, { recursive: true, mode: 448 });
|
|
1536
|
+
const parentStats = await port.lstat(parent);
|
|
1537
|
+
if (!parentStats.isDirectory() || parentStats.isSymbolicLink() || !ownerPrivate(parentStats, port.platform)) throw new TypeError(`Host Relay durable-state directory is unsafe: ${refusalFacts([{ name: "parent_stats", value: parentStats, expected: "an owner-private directory" }, { name: "platform", value: port.platform }])}.`);
|
|
1538
|
+
const temporary = `${target}.${port.unique_ref()}.new`;
|
|
1539
|
+
let staged;
|
|
1540
|
+
const failures = [];
|
|
1541
|
+
try {
|
|
1542
|
+
staged = await port.open_file(temporary);
|
|
1543
|
+
await staged.writeFile(data, typeof data === "string" ? { encoding: "utf8" } : void 0);
|
|
1544
|
+
await staged.sync();
|
|
1545
|
+
const closing = staged;
|
|
1546
|
+
staged = void 0;
|
|
1547
|
+
await closing.close();
|
|
1548
|
+
await port.rename(temporary, target);
|
|
1549
|
+
await syncParentDirectory(parent, port);
|
|
1550
|
+
} catch (error) {
|
|
1551
|
+
failures.push(error);
|
|
1552
|
+
}
|
|
1553
|
+
await closeStaged(staged, failures);
|
|
1554
|
+
await removeTemporary(temporary, port, failures);
|
|
1555
|
+
throwFailures(failures);
|
|
1556
|
+
}
|
|
1557
|
+
async function syncParentDirectory(path, port) {
|
|
1558
|
+
if (port.platform === "win32") return;
|
|
1559
|
+
const handle = await port.open_directory(path);
|
|
1560
|
+
let failure;
|
|
1561
|
+
try {
|
|
1562
|
+
await handle.sync();
|
|
1563
|
+
} catch (error) {
|
|
1564
|
+
if (!directorySyncUnsupported(error)) failure = error;
|
|
1565
|
+
}
|
|
1566
|
+
try {
|
|
1567
|
+
await handle.close();
|
|
1568
|
+
} catch (error) {
|
|
1569
|
+
if (failure === void 0) failure = error;
|
|
1570
|
+
else failure = new AggregateError([failure, error], "Host Relay directory sync and close failed.");
|
|
1571
|
+
}
|
|
1572
|
+
if (failure !== void 0) throw failure;
|
|
1573
|
+
}
|
|
1574
|
+
function ownerPrivate(stats, platform) {
|
|
1575
|
+
if (platform === "win32") return true;
|
|
1576
|
+
const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
|
|
1577
|
+
return stats.uid === uid && (stats.mode & 63) === 0;
|
|
1578
|
+
}
|
|
1579
|
+
async function closeStaged(staged, failures) {
|
|
1580
|
+
if (staged !== void 0) try {
|
|
1581
|
+
await staged.close();
|
|
1582
|
+
} catch (error) {
|
|
1583
|
+
failures.push(error);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
async function removeTemporary(path, port, failures) {
|
|
1587
|
+
try {
|
|
1588
|
+
await port.unlink(path);
|
|
1589
|
+
} catch (error) {
|
|
1590
|
+
if (errorCode(error) !== "ENOENT") failures.push(error);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
function throwFailures(failures) {
|
|
1594
|
+
if (failures.length === 1) throw failures[0];
|
|
1595
|
+
if (failures.length > 1) throw new AggregateError(failures, "Host Relay durable replacement and cleanup failed.");
|
|
1596
|
+
}
|
|
1597
|
+
function directorySyncUnsupported(error) {
|
|
1598
|
+
return ["EINVAL", "ENOSYS", "ENOTSUP"].includes(errorCode(error) ?? "");
|
|
1599
|
+
}
|
|
1600
|
+
function errorCode(error) {
|
|
1601
|
+
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
// host/relay/src/relay-quarantine-store.ts
|
|
1605
|
+
var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
|
|
1606
|
+
|
|
1607
|
+
// host/relay/src/relay-admission-store.ts
|
|
1608
|
+
var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
|
|
1609
|
+
|
|
1610
|
+
// host/relay/src/relay-request-binding-store.ts
|
|
1611
|
+
var MAX_STATE_BYTES = 8 * 1024 * 1024;
|
|
1612
|
+
|
|
1613
|
+
// host/relay/src/relay-ipc.ts
|
|
1614
|
+
import { createConnection, createServer } from "node:net";
|
|
1615
|
+
var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
|
|
1616
|
+
"schema_version",
|
|
1617
|
+
"producer_id",
|
|
1618
|
+
"producer_installation_ref",
|
|
1619
|
+
"local_session_ref",
|
|
1620
|
+
"local_turn_ref",
|
|
1621
|
+
"local_turn_aliases",
|
|
1622
|
+
"prompt_ref",
|
|
1623
|
+
"request_ref",
|
|
1624
|
+
"bound_at",
|
|
1625
|
+
"host_source_ref",
|
|
1626
|
+
"host_session_ref",
|
|
1627
|
+
"host_turn_ref",
|
|
1628
|
+
"host_input_ref"
|
|
1629
|
+
]);
|
|
1630
|
+
var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
|
|
1631
|
+
var HostRelayIpcError = class extends Error {
|
|
1632
|
+
code;
|
|
1633
|
+
constructor(code3, message = code3) {
|
|
1634
|
+
super(message);
|
|
1635
|
+
this.code = code3;
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
var HostRelayIpcAdmissionPort = class {
|
|
1639
|
+
endpoint;
|
|
1640
|
+
timeout_ms;
|
|
1641
|
+
constructor(endpoint, timeoutMs = 5e3) {
|
|
1642
|
+
this.endpoint = endpoint;
|
|
1643
|
+
this.timeout_ms = timeoutMs;
|
|
1644
|
+
}
|
|
1645
|
+
async admit(frame) {
|
|
1646
|
+
const response2 = await submitHostProducerFrame(this.endpoint, frame, this.timeout_ms);
|
|
1647
|
+
if (!relayAdmissionCode(response2.code)) throw new HostRelayIpcError(response2.code);
|
|
1648
|
+
return { code: response2.code, admitted_through_sequence: response2.admitted_through_sequence, delivered_through_sequence: response2.delivered_through_sequence };
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
function submitHostProducerFrame(endpoint, frame, timeoutMs = 5e3) {
|
|
1652
|
+
return exchange(endpoint, encodeHostProducerFrame(frame), timeoutMs, decodeResponseBuffer);
|
|
1653
|
+
}
|
|
1654
|
+
function exchange(endpoint, encoded, timeoutMs, decode) {
|
|
1655
|
+
return new Promise((resolve, reject) => {
|
|
1656
|
+
const socket = createConnection(endpoint);
|
|
1657
|
+
let buffer = Buffer.alloc(0);
|
|
1658
|
+
let settled = false;
|
|
1659
|
+
const finish = (error, response2) => {
|
|
1660
|
+
if (settled) return;
|
|
1661
|
+
settled = true;
|
|
1662
|
+
socket.destroy();
|
|
1663
|
+
if (error !== null) reject(error);
|
|
1664
|
+
else if (response2 !== void 0) resolve(response2);
|
|
1665
|
+
};
|
|
1666
|
+
socket.setTimeout(timeoutMs, () => finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC timed out.")));
|
|
1667
|
+
socket.once("error", () => finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC is unavailable.")));
|
|
1668
|
+
socket.once("connect", () => socket.write(encoded));
|
|
1669
|
+
socket.on("data", (chunk) => {
|
|
1670
|
+
try {
|
|
1671
|
+
buffer = Buffer.concat([buffer, Buffer.from(chunk)]);
|
|
1672
|
+
const decoded = decode(buffer);
|
|
1673
|
+
if (decoded !== null) finish(null, decoded);
|
|
1674
|
+
} catch (error) {
|
|
1675
|
+
finish(error instanceof Error ? error : new HostRelayIpcError("relay_unavailable"));
|
|
1676
|
+
}
|
|
1677
|
+
});
|
|
1678
|
+
socket.once("end", () => {
|
|
1679
|
+
if (!settled) finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC closed without a response."));
|
|
1680
|
+
});
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
function decodeHostRelayIpcResponse(input) {
|
|
1684
|
+
if (input.length < 4) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC response is truncated: ${refusalFact("response_bytes", input.byteLength, "at least 4")}.`);
|
|
1685
|
+
const length = input.readUInt32BE(0);
|
|
1686
|
+
if (length === 0 || length > 4096 || input.length !== length + 4) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC response length is invalid: ${refusalFacts([{ name: "declared_bytes", value: length, expected: "between 1 and 4096" }, { name: "received_bytes", value: input.byteLength, expected: `exactly ${length + 4}` }])}.`);
|
|
1687
|
+
let value;
|
|
1688
|
+
try {
|
|
1689
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.subarray(4)));
|
|
1690
|
+
} catch {
|
|
1691
|
+
throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC response is invalid.");
|
|
1692
|
+
}
|
|
1693
|
+
if (!validResponse(value)) throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC response shape is invalid.");
|
|
1694
|
+
return value;
|
|
1695
|
+
}
|
|
1696
|
+
function relayAdmissionCode(value) {
|
|
1697
|
+
return value === "admitted" || value === "already_admitted" || value === "producer_epoch_stale" || value === "sequence_gap" || value === "frame_identity_conflict" || value === "producer_queue_full" || value === "relay_queue_full" || value === "producer_already_connected" || value === "invalid_producer_identity";
|
|
1698
|
+
}
|
|
1699
|
+
function validResponse(value) {
|
|
1700
|
+
return record3(value) && value.schema_version === HOST_RELAY_IPC_RESPONSE_SCHEMA && hostRelayIpcCode(value.code) && nonnegative(value.admitted_through_sequence) && nonnegative(value.delivered_through_sequence) && Object.keys(value).length === 4;
|
|
1701
|
+
}
|
|
1702
|
+
function hostRelayIpcCode(value) {
|
|
1703
|
+
return typeof value === "string" && ["admitted", "already_admitted", "producer_epoch_stale", "sequence_gap", "frame_identity_conflict", "producer_queue_full", "relay_queue_full", "invalid_frame_encoding", "invalid_frame_length", "frame_truncated", "unsupported_frame_schema", "invalid_frame_shape", "invalid_producer_identity", "frame_too_large", "producer_already_connected", "relay_unavailable"].includes(value);
|
|
1704
|
+
}
|
|
1705
|
+
function nonnegative(value) {
|
|
1706
|
+
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
1707
|
+
}
|
|
1708
|
+
function record3(value) {
|
|
1709
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1710
|
+
}
|
|
1711
|
+
function decodeResponseBuffer(buffer) {
|
|
1712
|
+
if (buffer.length < 4) return null;
|
|
1713
|
+
const length = buffer.readUInt32BE(0);
|
|
1714
|
+
if (length === 0 || length > 4096) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC response length is invalid: ${refusalFact("declared_bytes", length, "between 1 and 4096")}.`);
|
|
1715
|
+
if (buffer.length < length + 4) return null;
|
|
1716
|
+
if (buffer.length !== length + 4) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC returned trailing bytes: ${refusalFacts([{ name: "declared_bytes", value: length, expected: "the received message length minus 4" }, { name: "received_bytes", value: buffer.byteLength, expected: `exactly ${length + 4}` }])}.`);
|
|
1717
|
+
return decodeHostRelayIpcResponse(buffer);
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
// host/invocation-context.ts
|
|
1721
|
+
var HOST_INVOCATION_IDENTITY_KEYS = ["host_session_ref", "host_turn_ref", "host_source_ref"];
|
|
1722
|
+
var HOST_INVOCATION_CONTEXT_REQUIRED_KEYS = ["schema_version", "identity", "prompt_ref", "expires_at"];
|
|
1723
|
+
function invocationContextKey(identity) {
|
|
1724
|
+
return cealRequestSha256({ host_source_ref: identity.host_source_ref, host_session_ref: identity.host_session_ref });
|
|
1725
|
+
}
|
|
1726
|
+
function hasHostInvocationIdentityKeys(value) {
|
|
1727
|
+
return exactKeys2(value, HOST_INVOCATION_IDENTITY_KEYS);
|
|
1728
|
+
}
|
|
1729
|
+
function hasHostInvocationContextKeys(value, optional = []) {
|
|
1730
|
+
return requiredKeys(value, HOST_INVOCATION_CONTEXT_REQUIRED_KEYS, optional);
|
|
1731
|
+
}
|
|
1732
|
+
function invocationContextFromTurn(identity, promptRef, expiresAt, descriptor) {
|
|
1733
|
+
const context = {
|
|
1734
|
+
schema_version: descriptor.schema_version,
|
|
1735
|
+
identity,
|
|
1736
|
+
prompt_ref: promptRef,
|
|
1737
|
+
expires_at: expiresAt,
|
|
1738
|
+
...descriptor.working_directory === void 0 ? {} : { working_directory: descriptor.working_directory }
|
|
1739
|
+
};
|
|
1740
|
+
descriptor.validate(context);
|
|
1741
|
+
return Object.freeze(context);
|
|
1742
|
+
}
|
|
1743
|
+
function exactKeys2(value, keys) {
|
|
1744
|
+
const actual = Object.keys(value);
|
|
1745
|
+
return actual.length === keys.length && keys.every((key) => Object.hasOwn(value, key)) && actual.every((key) => keys.includes(key));
|
|
1746
|
+
}
|
|
1747
|
+
function requiredKeys(value, required, optional) {
|
|
1748
|
+
const actual = Object.keys(value);
|
|
1749
|
+
const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
|
|
1750
|
+
return actual.length >= required.length && actual.length <= allowed.size && required.every((key) => Object.hasOwn(value, key)) && actual.every((key) => allowed.has(key));
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// host/claude/src/claude-invocation-context.ts
|
|
1754
|
+
function claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory) {
|
|
1755
|
+
return invocationContextFromTurn(identity, promptRef, expiresAt, { schema_version: CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION, validate: validateClaudeHostInvocationContext, working_directory: workingDirectory });
|
|
1756
|
+
}
|
|
1757
|
+
function validateClaudeHostInvocationContext(value) {
|
|
1758
|
+
if (!contextShape(value)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
|
|
1759
|
+
validateIdentity(value.identity);
|
|
1760
|
+
validateContextReferences(value);
|
|
1761
|
+
validateContextTimes(value);
|
|
1762
|
+
}
|
|
1763
|
+
function validateClaudeIdentity(value) {
|
|
1764
|
+
if (!validIdentity(value)) throw new ClaudeHostError("invalid_input", "Claude Host session identity is invalid.");
|
|
1765
|
+
}
|
|
1766
|
+
function contextShape(value) {
|
|
1767
|
+
return record4(value) && value.schema_version === CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION && hasHostInvocationContextKeys(value, ["request_ref", "bound_at", "working_directory"]);
|
|
1768
|
+
}
|
|
1769
|
+
function validateIdentity(value) {
|
|
1770
|
+
if (!validIdentity(value)) throw new ClaudeHostError("storage", "Claude Host invocation identity is malformed.");
|
|
1771
|
+
}
|
|
1772
|
+
function validateContextReferences(value) {
|
|
1773
|
+
if (!safeRef2(value.prompt_ref) || !optionalSafeRef(value.request_ref)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
|
|
1774
|
+
}
|
|
1775
|
+
function validateContextTimes(value) {
|
|
1776
|
+
if (!timestamp2(value.expires_at) || !optionalTimestamp(value.bound_at) || !optionalDirectory(value.working_directory)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
|
|
1777
|
+
if (typeof value.bound_at === "string" && Date.parse(value.bound_at) > Date.parse(String(value.expires_at))) throw new ClaudeHostError("storage", `Claude Host invocation context has invalid binding time (${refusalFact("bound_at", value.bound_at, `at or before ${String(value.expires_at)}`)}).`);
|
|
1778
|
+
}
|
|
1779
|
+
function validIdentity(value) {
|
|
1780
|
+
return record4(value) && hasHostInvocationIdentityKeys(value) && [value.host_session_ref, value.host_turn_ref, value.host_source_ref].every(safeRef2);
|
|
1781
|
+
}
|
|
1782
|
+
function optionalSafeRef(value) {
|
|
1783
|
+
return value === void 0 || safeRef2(value);
|
|
1784
|
+
}
|
|
1785
|
+
function optionalTimestamp(value) {
|
|
1786
|
+
return value === void 0 || timestamp2(value);
|
|
1787
|
+
}
|
|
1788
|
+
function optionalDirectory(value) {
|
|
1789
|
+
return value === void 0 || typeof value === "string" && value.startsWith("/") && !value.includes("\0") && value.length <= 4096;
|
|
1790
|
+
}
|
|
1791
|
+
function safeRef2(value) {
|
|
1792
|
+
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
|
|
1793
|
+
}
|
|
1794
|
+
function timestamp2(value) {
|
|
1795
|
+
return typeof value === "string" && value.length <= 64 && Number.isFinite(Date.parse(value));
|
|
1796
|
+
}
|
|
1797
|
+
function record4(value) {
|
|
1798
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// host/claude/src/claude-outbox.ts
|
|
1802
|
+
var SAFE_REF3 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
1803
|
+
var ClaudeOutboxStore = class {
|
|
1804
|
+
entries;
|
|
1805
|
+
directory;
|
|
1806
|
+
activeContexts;
|
|
1807
|
+
constructor(directory) {
|
|
1808
|
+
if (!directory.startsWith("/")) throw new ClaudeHostError("configuration", "Claude outbox directory must be absolute.");
|
|
1809
|
+
this.directory = directory;
|
|
1810
|
+
this.entries = join2(directory, "entries");
|
|
1811
|
+
this.activeContexts = join2(directory, "active-contexts");
|
|
1812
|
+
}
|
|
1813
|
+
async prepare() {
|
|
1814
|
+
await mkdir2(this.entries, { recursive: true, mode: 448 });
|
|
1815
|
+
await mkdir2(this.activeContexts, { recursive: true, mode: 448 });
|
|
1816
|
+
}
|
|
1817
|
+
async activateInvocationContext(identity, promptRef, expiresAt, workingDirectory) {
|
|
1818
|
+
const context = claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory);
|
|
1819
|
+
await this.prepare();
|
|
1820
|
+
await durableAtomicReplace(this.activeContextPath(identity), JSON.stringify(context));
|
|
1821
|
+
}
|
|
1822
|
+
async readInvocationContext(hostSourceRef, hostSessionRef, now = /* @__PURE__ */ new Date()) {
|
|
1823
|
+
validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now);
|
|
1824
|
+
await this.prepare();
|
|
1825
|
+
return readInvocationContextFile(this.activeContextPath({ host_source_ref: hostSourceRef, host_session_ref: hostSessionRef }), hostSourceRef, hostSessionRef, now);
|
|
1826
|
+
}
|
|
1827
|
+
async listInvocationContexts(now = /* @__PURE__ */ new Date()) {
|
|
1828
|
+
await this.prepare();
|
|
1829
|
+
const contexts = [];
|
|
1830
|
+
for (const name of await readdir(this.activeContexts)) {
|
|
1831
|
+
if (!name.endsWith(".json")) continue;
|
|
1832
|
+
try {
|
|
1833
|
+
const value = JSON.parse(await readFile(join2(this.activeContexts, name), "utf8"));
|
|
1834
|
+
validateClaudeHostInvocationContext(value);
|
|
1835
|
+
if (Date.parse(value.expires_at) > now.getTime()) contexts.push(value);
|
|
1836
|
+
} catch {
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
return contexts;
|
|
1840
|
+
}
|
|
1841
|
+
async put(batch) {
|
|
1842
|
+
validateBatch(batch);
|
|
1843
|
+
await this.prepare();
|
|
1844
|
+
const path = this.path(batch.batch_ref);
|
|
1845
|
+
try {
|
|
1846
|
+
const handle = await open2(path, "wx", 384);
|
|
1847
|
+
try {
|
|
1848
|
+
await handle.writeFile(JSON.stringify(batch));
|
|
1849
|
+
await handle.sync();
|
|
1850
|
+
} finally {
|
|
1851
|
+
await handle.close();
|
|
1852
|
+
}
|
|
1853
|
+
} catch (error) {
|
|
1854
|
+
if (code(error) !== "EEXIST") throw new ClaudeHostError("storage", `Unable to persist Claude hook batch (${refusalFact("error_code", code(error), "EEXIST for an idempotent retry")}).`);
|
|
1855
|
+
const existing = JSON.parse(await readFile(path, "utf8"));
|
|
1856
|
+
validateBatch(existing);
|
|
1857
|
+
if (JSON.stringify(existing) !== JSON.stringify(batch)) throw new ClaudeHostError("storage", "Claude hook batch identity conflicts with different bytes.");
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
async list() {
|
|
1861
|
+
await this.prepare();
|
|
1862
|
+
const batches = [];
|
|
1863
|
+
let malformed = 0;
|
|
1864
|
+
for (const name of await readdir(this.entries)) {
|
|
1865
|
+
if (!name.endsWith(".json")) continue;
|
|
1866
|
+
try {
|
|
1867
|
+
const value = JSON.parse(await readFile(join2(this.entries, name), "utf8"));
|
|
1868
|
+
validateBatch(value);
|
|
1869
|
+
batches.push(value);
|
|
1870
|
+
} catch {
|
|
1871
|
+
malformed += 1;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
batches.sort(compareBatch);
|
|
1875
|
+
return { entries: batches, malformed };
|
|
1876
|
+
}
|
|
1877
|
+
async remove(batch) {
|
|
1878
|
+
try {
|
|
1879
|
+
await unlink2(this.path(batch.batch_ref));
|
|
1880
|
+
} catch (error) {
|
|
1881
|
+
if (code(error) !== "ENOENT") throw error;
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
path(ref) {
|
|
1885
|
+
return join2(this.entries, `${cealRequestSha256(ref)}.json`);
|
|
1886
|
+
}
|
|
1887
|
+
activeContextPath(identity) {
|
|
1888
|
+
return join2(this.activeContexts, `${invocationContextKey(identity)}.json`);
|
|
1889
|
+
}
|
|
1890
|
+
};
|
|
1891
|
+
function validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now) {
|
|
1892
|
+
if (!SAFE_REF3.test(hostSourceRef) || !SAFE_REF3.test(hostSessionRef) || !(now instanceof Date) || !Number.isFinite(now.getTime())) throw new ClaudeHostError("invalid_input", "Claude Host context identity or clock is invalid.");
|
|
1893
|
+
}
|
|
1894
|
+
async function readInvocationContextFile(file, hostSourceRef, hostSessionRef, now) {
|
|
1895
|
+
try {
|
|
1896
|
+
const context = JSON.parse(await readFile(file, "utf8"));
|
|
1897
|
+
validateClaudeHostInvocationContext(context);
|
|
1898
|
+
return contextMatches(context, hostSourceRef, hostSessionRef) && Date.parse(context.expires_at) > now.getTime() ? context : null;
|
|
1899
|
+
} catch (error) {
|
|
1900
|
+
if (code(error) === "ENOENT") return null;
|
|
1901
|
+
if (error instanceof ClaudeHostError) throw error;
|
|
1902
|
+
throw new ClaudeHostError("storage", "Unable to read the Claude Host invocation context.");
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
function contextMatches(context, hostSourceRef, hostSessionRef) {
|
|
1906
|
+
return context.identity.host_source_ref === hostSourceRef && context.identity.host_session_ref === hostSessionRef;
|
|
1907
|
+
}
|
|
1908
|
+
function compareBatch(left, right) {
|
|
1909
|
+
return rank(left.hook_event) - rank(right.hook_event) || left.occurred_at.localeCompare(right.occurred_at) || left.batch_ref.localeCompare(right.batch_ref);
|
|
1910
|
+
}
|
|
1911
|
+
function rank(event) {
|
|
1912
|
+
return event === "SessionStart" ? 0 : event === "UserPromptSubmit" ? 1 : 2;
|
|
1913
|
+
}
|
|
1914
|
+
function validateBatch(value) {
|
|
1915
|
+
if (!validBatchHeader(value)) throw new ClaudeHostError("storage", "Claude hook batch is malformed.");
|
|
1916
|
+
for (const [index, draft] of value.frames.entries()) {
|
|
1917
|
+
if (!record5(draft) || !hostFrameKind(draft.frame_kind) || !record5(draft.payload)) throw new ClaudeHostError("storage", "Claude frame draft is malformed.");
|
|
1918
|
+
encodeHostProducerFrame({ schema_version: "ceal.host_producer_frame.v1", producer_id: "claude", producer_installation_ref: "validation", producer_epoch: 1, sequence: index + 1, frame_kind: draft.frame_kind, occurred_at: value.occurred_at, payload: draft.payload });
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
function validBatchHeader(value) {
|
|
1922
|
+
return record5(value) && value.schema_version === CLAUDE_OUTBOX_SCHEMA && safeRef3(value.batch_ref) && claudeEvent(value.hook_event) && safeRef3(value.local_session_ref) && optionalRef(value.local_turn_ref) && timestamp3(value.occurred_at) && Array.isArray(value.frames) && value.frames.length > 0 && value.frames.length <= 3;
|
|
1923
|
+
}
|
|
1924
|
+
function claudeEvent(value) {
|
|
1925
|
+
return typeof value === "string" && ["SessionStart", "UserPromptSubmit", "Stop", "StopFailure"].includes(value);
|
|
1926
|
+
}
|
|
1927
|
+
function hostFrameKind(value) {
|
|
1928
|
+
return isHostProducerFrameKind(value);
|
|
1929
|
+
}
|
|
1930
|
+
function safeRef3(value) {
|
|
1931
|
+
return typeof value === "string" && SAFE_REF3.test(value);
|
|
1932
|
+
}
|
|
1933
|
+
function optionalRef(value) {
|
|
1934
|
+
return value === void 0 || typeof value === "string" && SAFE_REF3.test(value);
|
|
1935
|
+
}
|
|
1936
|
+
function timestamp3(value) {
|
|
1937
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
1938
|
+
}
|
|
1939
|
+
function record5(value) {
|
|
1940
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1941
|
+
}
|
|
1942
|
+
function code(error) {
|
|
1943
|
+
return record5(error) && typeof error.code === "string" ? error.code : null;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// host/claude/src/claude-adapter.ts
|
|
1947
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1948
|
+
var ClaudeHookAdapter = class {
|
|
1949
|
+
store;
|
|
1950
|
+
now;
|
|
1951
|
+
constructor(options) {
|
|
1952
|
+
this.store = new ClaudeOutboxStore(options.outbox_dir);
|
|
1953
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
1954
|
+
this.optionsSourceRef = options.host_source_ref ?? "claude-host:local";
|
|
1955
|
+
}
|
|
1956
|
+
async handle(event, raw) {
|
|
1957
|
+
let input;
|
|
1958
|
+
try {
|
|
1959
|
+
input = decodeClaudeHookInput(event, raw);
|
|
1960
|
+
} catch (error) {
|
|
1961
|
+
return { event, disposition: "rejected", reason: error instanceof Error ? refusalFact("error", error) : "invalid_claude_hook_input" };
|
|
1962
|
+
}
|
|
1963
|
+
const occurredAt = timestamp4(this.now);
|
|
1964
|
+
const sessionRef = claudeLocalSessionRef(input.session_id);
|
|
1965
|
+
const turnRef = input.prompt_id === void 0 ? void 0 : claudeLocalTurnRef(input.session_id, input.prompt_id);
|
|
1966
|
+
const frames = mapFrames(input, occurredAt);
|
|
1967
|
+
const batchRef = `claude-batch:${cealRequestSha256({ event, session_ref: sessionRef, turn_ref: turnRef ?? null, occurred_at: occurredAt, frames })}`;
|
|
1968
|
+
const batch = { schema_version: CLAUDE_OUTBOX_SCHEMA, batch_ref: batchRef, hook_event: event, local_session_ref: sessionRef, ...turnRef === void 0 ? {} : { local_turn_ref: turnRef }, occurred_at: occurredAt, frames };
|
|
1969
|
+
await this.store.put(batch);
|
|
1970
|
+
await this.activatePromptContext(input, frames, sessionRef, turnRef);
|
|
1971
|
+
return { event, disposition: "queued", batch_ref: batchRef, local_session_ref: sessionRef, ...turnRef === void 0 ? {} : { local_turn_ref: turnRef } };
|
|
1972
|
+
}
|
|
1973
|
+
async activatePromptContext(input, frames, sessionRef, turnRef) {
|
|
1974
|
+
if (input.hook_event_name !== "UserPromptSubmit" || turnRef === void 0) return;
|
|
1975
|
+
const identity = { host_source_ref: this.optionsSourceRef, host_session_ref: sessionRef, host_turn_ref: turnRef };
|
|
1976
|
+
validateClaudeIdentity(identity);
|
|
1977
|
+
const inputFrame = frames.find((frame) => frame.frame_kind === "user_input");
|
|
1978
|
+
const expiresAt = inputFrame?.payload.context_expires_at;
|
|
1979
|
+
if (typeof expiresAt !== "string") throw new ClaudeHostError("storage", "Claude prompt context expiry is unavailable.");
|
|
1980
|
+
const observations = inputFrame?.payload.observations;
|
|
1981
|
+
const prompt = Array.isArray(observations) ? observations.find((observation) => isRecord(observation) && observation.observation_kind === "prompt") : void 0;
|
|
1982
|
+
const promptValue = isRecord(prompt) ? prompt.value : void 0;
|
|
1983
|
+
if (!isCealHostPromptObservationValue(promptValue)) throw new ClaudeHostError("storage", "Claude prompt observation is unavailable.");
|
|
1984
|
+
await this.store.activateInvocationContext(identity, hostProducerPromptRef(promptValue.body_sha256), expiresAt, input.cwd);
|
|
1985
|
+
}
|
|
1986
|
+
optionsSourceRef;
|
|
1987
|
+
};
|
|
1988
|
+
function mapFrames(input, at) {
|
|
1989
|
+
if (input.hook_event_name === "SessionStart") return sessionFrames(input, at);
|
|
1990
|
+
if (input.hook_event_name === "UserPromptSubmit") return inputFrames(input, at);
|
|
1991
|
+
return terminalFrames(input, at);
|
|
1992
|
+
}
|
|
1993
|
+
function sessionFrames(input, at) {
|
|
1994
|
+
const sessionRef = claudeLocalSessionRef(input.session_id);
|
|
1995
|
+
const observation = obs("session", sessionRef, at, { status: "started", host_kind: "claude", source: input.source, model: input.model ?? "unavailable", decoder_evidence_version: "claude-code-2.1.258" });
|
|
1996
|
+
return [{ frame_kind: "session_started", payload: compact({ local_session_ref: sessionRef, working_directory_ref: claudeWorkspaceRef(input.cwd), model: input.model, observations: [observation] }) }];
|
|
1997
|
+
}
|
|
1998
|
+
function inputFrames(input, at) {
|
|
1999
|
+
const sessionRef = claudeLocalSessionRef(input.session_id);
|
|
2000
|
+
const turnRef = claudeLocalTurnRef(input.session_id, input.prompt_id);
|
|
2001
|
+
const promptDigest = createHash3("sha256").update(input.prompt, "utf8").digest("hex");
|
|
2002
|
+
const turn = obs("turn", turnRef, at, { status: "started", prompt_id_sha256: createHash3("sha256").update(input.prompt_id, "utf8").digest("hex"), effort: input.effort?.level ?? "unavailable" });
|
|
2003
|
+
const prompt = obs("prompt", claudeLocalInputRef(input.session_id, input.prompt_id), at, { body_sha256: promptDigest });
|
|
2004
|
+
return [{ frame_kind: "turn_started", payload: { local_session_ref: sessionRef, local_turn_ref: turnRef, observations: [turn] } }, { frame_kind: "user_input", payload: compact({ local_session_ref: sessionRef, local_turn_ref: turnRef, local_input_ref: claudeLocalInputRef(input.session_id, input.prompt_id), origin_input: true, local_turn_aliases: [safeAlias(input.prompt_id)], context_expires_at: new Date(Date.parse(at) + 24 * 60 * 60 * 1e3).toISOString(), observations: [prompt] }) }];
|
|
2005
|
+
}
|
|
2006
|
+
function terminalFrames(input, at) {
|
|
2007
|
+
const sessionRef = claudeLocalSessionRef(input.session_id);
|
|
2008
|
+
const turnRef = claudeLocalTurnRef(input.session_id, input.prompt_id);
|
|
2009
|
+
const aborted = input.hook_event_name === "StopFailure";
|
|
2010
|
+
const reason = aborted ? `api_${input.error}` : void 0;
|
|
2011
|
+
const terminal = obs("turn", `${turnRef}:${aborted ? "failed" : "stopped"}`, at, compact({ status: aborted ? "aborted" : "completed", reason }));
|
|
2012
|
+
const timing = obs("timing", `${turnRef}:timing`, at, { status: "unavailable", reason: "claude_hook_does_not_expose_timing_or_usage" });
|
|
2013
|
+
return [{ frame_kind: "turn_terminal", payload: compact({ local_session_ref: sessionRef, local_turn_ref: turnRef, terminal_state: aborted ? "aborted" : "completed", abort_reason: reason, observations: [terminal, timing] }) }];
|
|
2014
|
+
}
|
|
2015
|
+
function obs(kind, identity, at, value) {
|
|
2016
|
+
return { observation_ref: cealHostObservationRef("claude", cealRequestSha256({ kind, identity, at, value })), observation_kind: kind, observed_at: at, value };
|
|
2017
|
+
}
|
|
2018
|
+
function safeAlias(value) {
|
|
2019
|
+
return /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value) ? value : `prompt:${createHash3("sha256").update(value).digest("hex")}`;
|
|
2020
|
+
}
|
|
2021
|
+
function isRecord(value) {
|
|
2022
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2023
|
+
}
|
|
2024
|
+
function compact(value) {
|
|
2025
|
+
return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== void 0));
|
|
2026
|
+
}
|
|
2027
|
+
function timestamp4(now) {
|
|
2028
|
+
const value = now();
|
|
2029
|
+
if (!(value instanceof Date) || !Number.isFinite(value.getTime())) throw new TypeError("Claude Host clock is invalid.");
|
|
2030
|
+
return value.toISOString();
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
// host/claude/src/claude-producer.ts
|
|
2034
|
+
import { mkdir as mkdir3, readFile as readFile2, rename as rename2, writeFile } from "node:fs/promises";
|
|
2035
|
+
import { join as join3 } from "node:path";
|
|
2036
|
+
var STATE_SCHEMA = "ceal.claude_producer_state.v1";
|
|
2037
|
+
var SAFE_REF4 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
2038
|
+
var ClaudeProducer = class {
|
|
2039
|
+
store;
|
|
2040
|
+
statePath;
|
|
2041
|
+
installation;
|
|
2042
|
+
epoch;
|
|
2043
|
+
options;
|
|
2044
|
+
tail = Promise.resolve();
|
|
2045
|
+
constructor(options) {
|
|
2046
|
+
if (!SAFE_REF4.test(options.producer_installation_ref)) throw new ClaudeHostError("configuration", "Claude producer installation identity is invalid.");
|
|
2047
|
+
this.options = options;
|
|
2048
|
+
this.epoch = options.producer_epoch ?? 1;
|
|
2049
|
+
if (!Number.isSafeInteger(this.epoch) || this.epoch <= 0) throw new ClaudeHostError("configuration", "Claude producer epoch is invalid.");
|
|
2050
|
+
this.store = new ClaudeOutboxStore(options.outbox_dir);
|
|
2051
|
+
this.statePath = join3(options.outbox_dir, "claude-producer-state.json");
|
|
2052
|
+
this.installation = options.producer_installation_ref;
|
|
2053
|
+
}
|
|
2054
|
+
drainOutbox() {
|
|
2055
|
+
const run = this.tail.then(() => this.drainLocked());
|
|
2056
|
+
this.tail = run.then(() => void 0, () => void 0);
|
|
2057
|
+
return run;
|
|
2058
|
+
}
|
|
2059
|
+
async drainLocked() {
|
|
2060
|
+
const read = await this.store.list();
|
|
2061
|
+
let admitted = 0;
|
|
2062
|
+
let retained = 0;
|
|
2063
|
+
for (const batch of read.entries) {
|
|
2064
|
+
const frames = await this.reserve(batch);
|
|
2065
|
+
let complete = true;
|
|
2066
|
+
for (const frame of frames) {
|
|
2067
|
+
const outcome = await this.options.relay.admit(frame);
|
|
2068
|
+
if (outcome.code !== "admitted" && outcome.code !== "already_admitted") {
|
|
2069
|
+
complete = false;
|
|
2070
|
+
break;
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
if (!complete) {
|
|
2074
|
+
retained += 1;
|
|
2075
|
+
break;
|
|
2076
|
+
}
|
|
2077
|
+
await this.store.remove(batch);
|
|
2078
|
+
await this.release(batch.batch_ref);
|
|
2079
|
+
admitted += 1;
|
|
2080
|
+
}
|
|
2081
|
+
return { admitted, retained: retained + Math.max(0, read.entries.length - admitted - retained), malformed: read.malformed };
|
|
2082
|
+
}
|
|
2083
|
+
async reserve(batch) {
|
|
2084
|
+
const state = await this.readState();
|
|
2085
|
+
const existing = state.pending[batch.batch_ref];
|
|
2086
|
+
if (existing !== void 0) return existing;
|
|
2087
|
+
const frames = batch.frames.map((draft, offset) => ({ schema_version: "ceal.host_producer_frame.v1", producer_id: "claude", producer_installation_ref: this.installation, producer_epoch: this.epoch, sequence: state.next_sequence + offset, frame_kind: draft.frame_kind, occurred_at: batch.occurred_at, payload: draft.payload }));
|
|
2088
|
+
await this.writeState({ ...state, next_sequence: state.next_sequence + frames.length, pending: { ...state.pending, [batch.batch_ref]: frames } });
|
|
2089
|
+
return frames;
|
|
2090
|
+
}
|
|
2091
|
+
async release(ref) {
|
|
2092
|
+
const state = await this.readState();
|
|
2093
|
+
if (state.pending[ref] === void 0) return;
|
|
2094
|
+
const pending = { ...state.pending };
|
|
2095
|
+
delete pending[ref];
|
|
2096
|
+
await this.writeState({ ...state, pending });
|
|
2097
|
+
}
|
|
2098
|
+
async readState() {
|
|
2099
|
+
try {
|
|
2100
|
+
return decodeState(JSON.parse(await readFile2(this.statePath, "utf8")), this.installation, this.epoch);
|
|
2101
|
+
} catch (error) {
|
|
2102
|
+
if (code2(error) !== "ENOENT") throw error;
|
|
2103
|
+
return { schema_version: STATE_SCHEMA, producer_installation_ref: this.installation, producer_epoch: this.epoch, next_sequence: 1, pending: {} };
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
async writeState(state) {
|
|
2107
|
+
await mkdir3(this.store.directory, { recursive: true, mode: 448 });
|
|
2108
|
+
const temp = `${this.statePath}.${process.pid}.new`;
|
|
2109
|
+
await writeFile(temp, JSON.stringify(state), { mode: 384 });
|
|
2110
|
+
await rename2(temp, this.statePath);
|
|
2111
|
+
}
|
|
2112
|
+
};
|
|
2113
|
+
function decodeState(value, installation, epoch) {
|
|
2114
|
+
if (!validStateHeader(value, installation)) throw new ClaudeHostError("storage", `Claude producer state header is malformed or belongs to another installation (${refusalFacts([
|
|
2115
|
+
{ name: "schema_version", value: record6(value) ? value.schema_version : void 0, expected: STATE_SCHEMA },
|
|
2116
|
+
{ name: "producer_installation_ref", value: record6(value) ? value.producer_installation_ref : void 0, expected: installation }
|
|
2117
|
+
])}).`);
|
|
2118
|
+
if (Number(value.producer_epoch) > epoch) throw new ClaudeHostError("storage", `Claude producer state epoch is ahead of this installation (${refusalFact("producer_epoch", value.producer_epoch, `at most ${epoch}`)}).`);
|
|
2119
|
+
const pending = {};
|
|
2120
|
+
for (const [ref, frames] of Object.entries(value.pending)) {
|
|
2121
|
+
if (!SAFE_REF4.test(ref) || !Array.isArray(frames) || frames.length === 0) throw new ClaudeHostError("storage", "Claude producer reservation is malformed.");
|
|
2122
|
+
pending[ref] = frames.map((frame) => decodeFrame(frame, installation, Number(value.producer_epoch)));
|
|
2123
|
+
}
|
|
2124
|
+
return { schema_version: STATE_SCHEMA, producer_installation_ref: installation, producer_epoch: epoch, next_sequence: Number(value.next_sequence), pending };
|
|
2125
|
+
}
|
|
2126
|
+
function validStateHeader(value, installation) {
|
|
2127
|
+
return record6(value) && value.schema_version === STATE_SCHEMA && value.producer_installation_ref === installation && positiveInteger(value.producer_epoch) && positiveInteger(value.next_sequence) && record6(value.pending);
|
|
2128
|
+
}
|
|
2129
|
+
function positiveInteger(value) {
|
|
2130
|
+
return Number.isSafeInteger(value) && Number(value) > 0;
|
|
2131
|
+
}
|
|
2132
|
+
function decodeFrame(value, installation, epoch) {
|
|
2133
|
+
const json = Buffer.from(JSON.stringify(value));
|
|
2134
|
+
const encoded = Buffer.allocUnsafe(json.length + 4);
|
|
2135
|
+
encoded.writeUInt32BE(json.length, 0);
|
|
2136
|
+
json.copy(encoded, 4);
|
|
2137
|
+
const frame = decodeHostProducerFrame(encoded);
|
|
2138
|
+
if (frame.producer_id !== "claude" || frame.producer_installation_ref !== installation || frame.producer_epoch > epoch) throw new ClaudeHostError("storage", `Claude producer frame identity is malformed (${refusalFacts([{ name: "producer_id", value: frame.producer_id, expected: "claude" }, { name: "producer_installation_ref", value: frame.producer_installation_ref, expected: installation }, { name: "producer_epoch", value: frame.producer_epoch, expected: `at most ${epoch}` }])}).`);
|
|
2139
|
+
return frame;
|
|
2140
|
+
}
|
|
2141
|
+
function record6(value) {
|
|
2142
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2143
|
+
}
|
|
2144
|
+
function code2(error) {
|
|
2145
|
+
return record6(error) && typeof error.code === "string" ? error.code : null;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
// host/claude/src/claude-producer-runtime.ts
|
|
2149
|
+
async function drainClaudeProducerOnce(options) {
|
|
2150
|
+
return new ClaudeProducer({ outbox_dir: options.outbox_dir, producer_installation_ref: options.producer_installation_ref, relay: new HostRelayIpcAdmissionPort(options.relay_endpoint, options.timeout_ms), ...options.producer_epoch === void 0 ? {} : { producer_epoch: options.producer_epoch } }).drainOutbox();
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
// host/claude/src/claude-hook-runner.ts
|
|
2154
|
+
import { homedir } from "node:os";
|
|
2155
|
+
import { join as join4 } from "node:path";
|
|
2156
|
+
|
|
2157
|
+
// host/session-start-affordance.ts
|
|
2158
|
+
var HOST_ENTRY_AFFORDANCE = "When the user asks to use Ceal or provides a link to information that may be available through installed organizational capabilities, first run the bare `ceal` shell command with no arguments; treat that link as a candidate Ceal resource rather than a public web-search target, and let the current Profile capability result supply the exact next steps.";
|
|
2159
|
+
function hostSessionStartEntryAffordance(hostName) {
|
|
2160
|
+
return `${HOST_ENTRY_AFFORDANCE} This text is the Ceal ${hostName} Host's own SessionStart hook running in this ${hostName} session. The Host owns this session's Request identity, and the generic client authenticates each call automatically; follow the required_action_sequence returned by bare \`ceal\`. Never run \`ceal request create\`, and never type, guess, or reuse --host-session-ref, --host-turn-ref, --prompt-ref, or any other session or turn reference.`;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
// host/producer-service.ts
|
|
2164
|
+
import { constants } from "node:fs";
|
|
2165
|
+
import { lstat as lstat2, open as open3 } from "node:fs/promises";
|
|
2166
|
+
|
|
2167
|
+
// host/absolute-path.ts
|
|
2168
|
+
import { posix, win32 } from "node:path";
|
|
2169
|
+
function isAbsoluteNormalizedNonRootPath(value, flavor = process.platform, allowRoot = false) {
|
|
2170
|
+
const pathApi = flavor === "win32" || flavor === "windows" ? win32 : posix;
|
|
2171
|
+
return typeof value === "string" && value.length <= 4096 && !value.includes("\0") && pathApi.isAbsolute(value) && pathApi.normalize(value) === value && (allowRoot || pathApi.parse(value).root !== value);
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
// host/producer-service.ts
|
|
2175
|
+
var MAX_CONFIG_BYTES = 32 * 1024;
|
|
2176
|
+
var SAFE_REF5 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
2177
|
+
var HOST_PRODUCER_CONFIG_FLAG = "--ceal-host-config";
|
|
2178
|
+
var HostProducerServiceError = class extends Error {
|
|
2179
|
+
code;
|
|
2180
|
+
constructor(code3, message = code3) {
|
|
2181
|
+
super(message === code3 ? code3 : `${code3}: ${message}`);
|
|
2182
|
+
this.name = "HostProducerServiceError";
|
|
2183
|
+
this.code = code3;
|
|
2184
|
+
}
|
|
2185
|
+
};
|
|
2186
|
+
var HOST_PRODUCER_CONFIG_KEYS = ["schema_version", "host_source_ref", "producer_installation_ref", "producer_epoch", "outbox_dir", "relay_endpoint", "reconcile_interval_ms", "retry_backoff_ms", "ipc_timeout_ms"];
|
|
2187
|
+
function parseHostProducerConfigFlag(args, platform = process.platform) {
|
|
2188
|
+
return parseProducerHostConfigFlag(args, HOST_PRODUCER_CONFIG_FLAG, platform, hostProducerError);
|
|
2189
|
+
}
|
|
2190
|
+
async function readHostProducerConfig(descriptor, configPath, platform = process.platform) {
|
|
2191
|
+
return decodeHostProducerConfigText(descriptor, await readProducerConfigText(configPath, platform, hostProducerError), platform);
|
|
2192
|
+
}
|
|
2193
|
+
function decodeHostProducerConfigText(descriptor, text, platform = process.platform) {
|
|
2194
|
+
const extraPaths = descriptor.extra_paths ?? [];
|
|
2195
|
+
const decoded = decodeProducerConfigCore(text, { schema: descriptor.schema, keys: [...HOST_PRODUCER_CONFIG_KEYS, ...extraPaths], platform, extraPaths, error: hostProducerError });
|
|
2196
|
+
const { extra_paths: extras, ...core } = decoded;
|
|
2197
|
+
return Object.freeze(descriptor.config({ schema_version: descriptor.schema, ...core }, extras));
|
|
2198
|
+
}
|
|
2199
|
+
function hostProducerError(code3, message = code3) {
|
|
2200
|
+
return new HostProducerServiceError(code3, message);
|
|
2201
|
+
}
|
|
2202
|
+
function parseProducerHostConfigFlag(args, flag, platform, error) {
|
|
2203
|
+
const prefix = `${flag}=`;
|
|
2204
|
+
const values = args.filter((argument) => argument.startsWith(prefix));
|
|
2205
|
+
if (values.length !== 1) throw error("invalid_arguments", refusalFact("config_flags", values, "exactly one"));
|
|
2206
|
+
const value = values[0]?.slice(prefix.length);
|
|
2207
|
+
assertProducerAbsolutePath(value, platform, "config", error);
|
|
2208
|
+
return value;
|
|
2209
|
+
}
|
|
2210
|
+
async function readProducerConfigText(configPath, platform, error) {
|
|
2211
|
+
assertProducerAbsolutePath(configPath, platform, "config", error);
|
|
2212
|
+
const pathStat = await configPathStat(configPath, error);
|
|
2213
|
+
validateConfigFile(pathStat, platform, error);
|
|
2214
|
+
const noFollow = typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
|
|
2215
|
+
const handle = await openConfigFile(configPath, constants.O_RDONLY | noFollow, error);
|
|
2216
|
+
try {
|
|
2217
|
+
validateOpenedFile(await handle.stat(), pathStat, platform, error);
|
|
2218
|
+
return await readBoundedConfig(handle, error);
|
|
2219
|
+
} finally {
|
|
2220
|
+
await handle.close();
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
function decodeProducerConfigCore(text, options) {
|
|
2224
|
+
const value = parseCanonicalJson(text, options.error);
|
|
2225
|
+
if (!isRecord2(value) || !hasExactKeys2(value, options.keys) || value.schema_version !== options.schema) throw options.error("invalid_config");
|
|
2226
|
+
const hostSourceRef = typeof value.host_source_ref === "string" ? value.host_source_ref : "";
|
|
2227
|
+
const installationRef = typeof value.producer_installation_ref === "string" ? value.producer_installation_ref : "";
|
|
2228
|
+
const producerEpoch = validateIdentity2(hostSourceRef, installationRef, value.producer_epoch, options.error);
|
|
2229
|
+
assertProducerAbsolutePath(value.outbox_dir, options.platform, "runtime", options.error);
|
|
2230
|
+
assertProducerRelayEndpoint(value.relay_endpoint, options.platform, options.error);
|
|
2231
|
+
const extraPaths = {};
|
|
2232
|
+
for (const key of options.extraPaths ?? []) {
|
|
2233
|
+
assertProducerAbsolutePath(value[key], options.platform, "runtime", options.error);
|
|
2234
|
+
extraPaths[key] = value[key];
|
|
2235
|
+
}
|
|
2236
|
+
const intervals = validateIntervals(value.reconcile_interval_ms, value.retry_backoff_ms, value.ipc_timeout_ms, options.error);
|
|
2237
|
+
return {
|
|
2238
|
+
extra_paths: Object.freeze(extraPaths),
|
|
2239
|
+
host_source_ref: hostSourceRef,
|
|
2240
|
+
producer_installation_ref: installationRef,
|
|
2241
|
+
producer_epoch: producerEpoch,
|
|
2242
|
+
outbox_dir: value.outbox_dir,
|
|
2243
|
+
relay_endpoint: value.relay_endpoint,
|
|
2244
|
+
reconcile_interval_ms: intervals[0],
|
|
2245
|
+
retry_backoff_ms: intervals[1],
|
|
2246
|
+
ipc_timeout_ms: intervals[2]
|
|
2247
|
+
};
|
|
2248
|
+
}
|
|
2249
|
+
function assertProducerRelayEndpoint(value, platform, error) {
|
|
2250
|
+
if (platform === "win32" && typeof value === "string" && /^\\\\\.\\pipe\\[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value)) return;
|
|
2251
|
+
assertProducerAbsolutePath(value, platform, "runtime", error);
|
|
2252
|
+
}
|
|
2253
|
+
function assertProducerAbsolutePath(value, platform, kind, error) {
|
|
2254
|
+
if (!isAbsoluteNormalizedNonRootPath(value, platform) || /[\r\n]/u.test(value)) throw error(kind === "config" ? "invalid_config_path" : "invalid_config", refusalFact("path", value, "absolute, normalized, non-root, and without line breaks"));
|
|
2255
|
+
}
|
|
2256
|
+
async function configPathStat(configPath, error) {
|
|
2257
|
+
try {
|
|
2258
|
+
return await lstat2(configPath);
|
|
2259
|
+
} catch {
|
|
2260
|
+
throw error("config_unavailable");
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
function validateConfigFile(value, platform, error) {
|
|
2264
|
+
if (!value.isFile() || value.isSymbolicLink()) throw error("invalid_config", refusalFacts([{ name: "config_is_file", value: value.isFile(), expected: "true" }, { name: "config_is_symlink", value: value.isSymbolicLink(), expected: "false" }]));
|
|
2265
|
+
if (value.size <= 0 || value.size > MAX_CONFIG_BYTES) throw error("invalid_config", refusalFact("config_bytes", value.size, `between 1 and ${MAX_CONFIG_BYTES}`));
|
|
2266
|
+
if (platform !== "win32" && (value.uid !== process.getuid?.() || (value.mode & 63) !== 0)) throw error("config_not_private", refusalFacts([{ name: "platform", value: platform, expected: "win32 or owner-private" }, { name: "config_uid", value: value.uid, expected: "the current user" }, { name: "config_mode", value: value.mode, expected: "owner-private" }]));
|
|
2267
|
+
}
|
|
2268
|
+
async function openConfigFile(configPath, flags, error) {
|
|
2269
|
+
try {
|
|
2270
|
+
return await open3(configPath, flags);
|
|
2271
|
+
} catch {
|
|
2272
|
+
throw error("config_unavailable");
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
function validateOpenedFile(opened, pathStat, platform, error) {
|
|
2276
|
+
if (!opened.isFile()) throw error("config_unavailable", refusalFacts([{ name: "opened_is_file", value: opened.isFile(), expected: "true" }, { name: "opened_mode", value: opened.mode.toString(8), expected: "a regular file mode" }]));
|
|
2277
|
+
if (platform !== "win32" && (opened.dev !== pathStat.dev || opened.ino !== pathStat.ino)) throw error("config_unavailable", refusalFacts([{ name: "platform", value: platform, expected: "win32, or the same file the path named" }, { name: "opened_device", value: opened.dev, expected: String(pathStat.dev) }, { name: "opened_inode", value: opened.ino, expected: String(pathStat.ino) }]));
|
|
2278
|
+
}
|
|
2279
|
+
async function readBoundedConfig(handle, error) {
|
|
2280
|
+
const buffer = Buffer.allocUnsafe(MAX_CONFIG_BYTES + 1);
|
|
2281
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.byteLength, 0);
|
|
2282
|
+
if (bytesRead === 0 || bytesRead > MAX_CONFIG_BYTES) throw error("invalid_config", refusalFact("bytes_read", bytesRead, `between 1 and ${MAX_CONFIG_BYTES}`));
|
|
2283
|
+
try {
|
|
2284
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, bytesRead));
|
|
2285
|
+
} catch {
|
|
2286
|
+
throw error("invalid_config");
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
function parseCanonicalJson(text, error) {
|
|
2290
|
+
try {
|
|
2291
|
+
const value = JSON.parse(text);
|
|
2292
|
+
const canonical = JSON.stringify(value);
|
|
2293
|
+
if (canonical !== text.trim()) throw new Error(`noncanonical: ${refusalFacts([{ name: "config_bytes", value: Buffer.byteLength(text.trim(), "utf8"), expected: `the ${Buffer.byteLength(canonical, "utf8")} bytes of its canonical form` }, { name: "first_difference_index", value: firstDifferenceIndex(text.trim(), canonical) }])}`);
|
|
2294
|
+
return value;
|
|
2295
|
+
} catch (parseError) {
|
|
2296
|
+
throw error("invalid_config", refusalFacts([{ name: "parse_error", value: parseError }, { name: "config_bytes", value: Buffer.byteLength(text, "utf8"), expected: "canonical JSON" }]));
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
function firstDifferenceIndex(left, right) {
|
|
2300
|
+
const shared = Math.min(left.length, right.length);
|
|
2301
|
+
for (let index = 0; index < shared; index += 1) if (left[index] !== right[index]) return index;
|
|
2302
|
+
return shared;
|
|
2303
|
+
}
|
|
2304
|
+
function validateIdentity2(sourceRef, installationRef, epoch, error) {
|
|
2305
|
+
if (!SAFE_REF5.test(sourceRef) || !SAFE_REF5.test(installationRef) || !positive2(epoch, 2147483647)) throw error("invalid_config");
|
|
2306
|
+
return epoch;
|
|
2307
|
+
}
|
|
2308
|
+
function validateIntervals(reconcile, retry, timeout, error) {
|
|
2309
|
+
if (!boundedMilliseconds(reconcile) || !boundedMilliseconds(retry) || !boundedMilliseconds(timeout)) throw error("invalid_config");
|
|
2310
|
+
return [reconcile, retry, timeout];
|
|
2311
|
+
}
|
|
2312
|
+
function hasExactKeys2(value, keys) {
|
|
2313
|
+
const observed = Object.keys(value);
|
|
2314
|
+
return observed.length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
2315
|
+
}
|
|
2316
|
+
function isRecord2(value) {
|
|
2317
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2318
|
+
}
|
|
2319
|
+
function positive2(value, maximum) {
|
|
2320
|
+
return Number.isSafeInteger(value) && Number(value) > 0 && Number(value) <= maximum;
|
|
2321
|
+
}
|
|
2322
|
+
function boundedMilliseconds(value) {
|
|
2323
|
+
return positive2(value, 6e4) && Number(value) >= 25;
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
// host/claude/src/claude-producer-service.ts
|
|
2327
|
+
var CLAUDE_PRODUCER_CONFIG_SCHEMA = "ceal.claude_producer_config.v1";
|
|
2328
|
+
var CLAUDE_PRODUCER_DESCRIPTOR = {
|
|
2329
|
+
schema: CLAUDE_PRODUCER_CONFIG_SCHEMA,
|
|
2330
|
+
config: (shared) => shared,
|
|
2331
|
+
runtime_options: (config) => Object.freeze({
|
|
2332
|
+
outbox_dir: config.outbox_dir,
|
|
2333
|
+
producer_installation_ref: config.producer_installation_ref,
|
|
2334
|
+
producer_epoch: config.producer_epoch,
|
|
2335
|
+
relay_endpoint: config.relay_endpoint,
|
|
2336
|
+
timeout_ms: config.ipc_timeout_ms
|
|
2337
|
+
}),
|
|
2338
|
+
drain: drainClaudeProducerOnce
|
|
2339
|
+
};
|
|
2340
|
+
|
|
2341
|
+
// host/claude/src/claude-hook-runner.ts
|
|
2342
|
+
var MAX_STDIN_BYTES = 256 * 1024;
|
|
2343
|
+
var CLAUDE_SESSION_START_ENTRY_AFFORDANCE = hostSessionStartEntryAffordance("Claude");
|
|
2344
|
+
function claudeSessionStartHookOutput() {
|
|
2345
|
+
return { suppressOutput: true, hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: CLAUDE_SESSION_START_ENTRY_AFFORDANCE } };
|
|
2346
|
+
}
|
|
2347
|
+
async function runClaudeHookFromStdin(event, stdin, options) {
|
|
2348
|
+
return new ClaudeHookAdapter(options).handle(event, await readStdin(stdin));
|
|
2349
|
+
}
|
|
2350
|
+
function claudeHookEventFromArgs(args) {
|
|
2351
|
+
const value = args.find((arg) => arg.startsWith("--ceal-hook-event="))?.slice("--ceal-hook-event=".length);
|
|
2352
|
+
return isClaudeHookEvent(value) ? value : null;
|
|
2353
|
+
}
|
|
2354
|
+
async function runClaudeHookProcess(args, stdin, options) {
|
|
2355
|
+
const event = claudeHookEventFromArgs(args);
|
|
2356
|
+
if (event === null) return 0;
|
|
2357
|
+
const result = await runClaudeHookFromStdin(event, stdin, options);
|
|
2358
|
+
if (event === "SessionStart") process.stdout.write(`${JSON.stringify(claudeSessionStartHookOutput())}
|
|
2359
|
+
`);
|
|
2360
|
+
if (result.disposition === "rejected") return 0;
|
|
2361
|
+
return 0;
|
|
2362
|
+
}
|
|
2363
|
+
async function runClaudeHookCli() {
|
|
2364
|
+
const args = process.argv.slice(2);
|
|
2365
|
+
const options = hasHostConfigArgument(args) ? await installedClaudeHookRunnerOptions(args) : standaloneClaudeHookRunnerOptions();
|
|
2366
|
+
return runClaudeHookProcess(args, process.stdin, options);
|
|
2367
|
+
}
|
|
2368
|
+
async function installedClaudeHookRunnerOptions(args) {
|
|
2369
|
+
const config = await readHostProducerConfig(CLAUDE_PRODUCER_DESCRIPTOR, parseHostProducerConfigFlag(args));
|
|
2370
|
+
const options = { outbox_dir: config.outbox_dir };
|
|
2371
|
+
Object.defineProperty(options, "host_source_ref", { value: config.host_source_ref, enumerable: false });
|
|
2372
|
+
return options;
|
|
2373
|
+
}
|
|
2374
|
+
function standaloneClaudeHookRunnerOptions() {
|
|
2375
|
+
return { outbox_dir: process.env.CEAL_CLAUDE_OUTBOX_DIR ?? join4(homedir(), ".claude", ".ceal", "claude-observation-outbox") };
|
|
2376
|
+
}
|
|
2377
|
+
function hasHostConfigArgument(args) {
|
|
2378
|
+
return args.some((argument) => argument === HOST_PRODUCER_CONFIG_FLAG || argument.startsWith(`${HOST_PRODUCER_CONFIG_FLAG}=`));
|
|
2379
|
+
}
|
|
2380
|
+
async function readStdin(stdin) {
|
|
2381
|
+
const chunks = [];
|
|
2382
|
+
let bytes = 0;
|
|
2383
|
+
for await (const chunk of stdin) {
|
|
2384
|
+
const value = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
2385
|
+
chunks.push(value);
|
|
2386
|
+
bytes += value.byteLength;
|
|
2387
|
+
if (bytes > MAX_STDIN_BYTES) return " ".repeat(MAX_STDIN_BYTES + 1);
|
|
2388
|
+
}
|
|
2389
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
// host/client-doctor.ts
|
|
2393
|
+
import { execFile } from "node:child_process";
|
|
2394
|
+
import { promisify } from "node:util";
|
|
2395
|
+
|
|
2396
|
+
// host/invocation-authentication-client-command.ts
|
|
2397
|
+
var DOCTOR_FLAGS = Object.freeze({
|
|
2398
|
+
"--expected-sha256": "expected_sha256",
|
|
2399
|
+
"--expected-source-commit": "expected_source_commit",
|
|
2400
|
+
"--expected-instance-ref": "expected_instance_ref"
|
|
2401
|
+
});
|
|
2402
|
+
|
|
2403
|
+
// host/client-doctor.ts
|
|
2404
|
+
var execFileAsync = promisify(execFile);
|
|
2405
|
+
|
|
2406
|
+
// host/claude/bin/ceal-claude-hook.ts
|
|
2407
|
+
async function main() {
|
|
2408
|
+
return runClaudeHookCli();
|
|
2409
|
+
}
|
|
2410
|
+
if (isDirectInvocation(import.meta.url)) process.exitCode = await main();
|
|
2411
|
+
export {
|
|
2412
|
+
main
|
|
2413
|
+
};
|