@corca-org/ceal-host-linux-arm64 1.0.19 → 1.0.21
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/bin/ceal-host +0 -0
- package/libexec/ceal-host/claude-hook.mjs +1327 -1211
- package/libexec/ceal-host/claude-invocation-authentication.mjs +1986 -1272
- package/libexec/ceal-host/claude-producer.mjs +1376 -1313
- package/libexec/ceal-host/codex-hook.mjs +1215 -1392
- package/libexec/ceal-host/codex-invocation-authentication.mjs +1924 -1364
- package/libexec/ceal-host/codex-producer.mjs +1222 -1286
- package/libexec/ceal-host/control.mjs +2818 -2076
- package/libexec/ceal-host/relay.mjs +1733 -1811
- package/package.json +1 -1
- package/share/ceal-host/bundle-manifest.json +12 -12
- package/share/ceal-host/bundle-manifest.sigstore.json +1 -1
|
@@ -3,7 +3,7 @@ import { createRequire as __cealCreateRequire } from "node:module";
|
|
|
3
3
|
const require = __cealCreateRequire(import.meta.url);
|
|
4
4
|
|
|
5
5
|
// host/producer-service.ts
|
|
6
|
-
import { posix as posix3, win32 as
|
|
6
|
+
import { posix as posix3, win32 as win324 } from "node:path";
|
|
7
7
|
|
|
8
8
|
// host/runtime-source-paths.ts
|
|
9
9
|
import { createHash } from "node:crypto";
|
|
@@ -29,22 +29,17 @@ function hostRuntimeSourcePaths(stateRoot, sourceRef, platform = process.platfor
|
|
|
29
29
|
const sourceRoot = path.join(sourcesRoot, sourceId);
|
|
30
30
|
const runtimeRoot = path.join(stateRoot, "run");
|
|
31
31
|
const endpoint = platform === "win32" ? `\\\\.\\pipe\\ceal-host-${createHash("sha256").update(`${stateRoot}\0${sourceRef}`).digest("hex").slice(0, 24)}` : path.join(runtimeRoot, `${sourceId}.sock`);
|
|
32
|
-
assertHostRuntimeIpcLength(endpoint, platform);
|
|
33
32
|
return { sourceId, sourcesRoot, sourceRoot, relayRoot: path.join(sourceRoot, "relay"), codexOutbox: path.join(sourceRoot, "producers", "codex", "outbox"), claudeOutbox: path.join(sourceRoot, "producers", "claude", "outbox"), endpoint, runtimeRoot };
|
|
34
33
|
}
|
|
35
|
-
function assertHostRuntimeIpcLength(endpoint, platform) {
|
|
36
|
-
if (platform === "win32") return;
|
|
37
|
-
const maximum = platform === "linux" ? 107 : 103;
|
|
38
|
-
if (Buffer.byteLength(endpoint, "utf8") > maximum) throw Object.assign(new TypeError(`ipc_endpoint_too_long: Host IPC path exceeds ${maximum} bytes; use a shorter Host state root.`), { code: "ipc_endpoint_too_long" });
|
|
39
|
-
}
|
|
40
34
|
function assertHostProducerSourcePaths(stateRoot, config, producer, platform = process.platform) {
|
|
41
35
|
const paths = hostRuntimeSourcePaths(stateRoot, config.host_source_ref, platform);
|
|
42
|
-
if (config.outbox_dir !== (producer === "codex" ? paths.codexOutbox : paths.claudeOutbox) || config.relay_endpoint !== paths.endpoint) throw new TypeError("Host producer outbox and IPC must belong to its configured source.");
|
|
36
|
+
if (config.outbox_dir !== (producer === "codex" ? paths.codexOutbox : paths.claudeOutbox) || platform !== "win32" && config.relay_endpoint !== paths.endpoint) throw new TypeError("Host producer outbox and IPC must belong to its configured source.");
|
|
37
|
+
return paths.endpoint;
|
|
43
38
|
}
|
|
44
39
|
|
|
45
40
|
// host/producer-service.ts
|
|
46
|
-
import { constants } from "node:fs";
|
|
47
|
-
import { lstat
|
|
41
|
+
import { constants as constants2 } from "node:fs";
|
|
42
|
+
import { lstat as lstat2 } from "node:fs/promises";
|
|
48
43
|
|
|
49
44
|
// packages/ceal-client-protocol/src/canonical-json.ts
|
|
50
45
|
function cealCanonicalJson(value) {
|
|
@@ -73,16 +68,34 @@ function isPlainRecord(value) {
|
|
|
73
68
|
return prototype === Object.prototype || prototype === null;
|
|
74
69
|
}
|
|
75
70
|
|
|
71
|
+
// packages/ceal-client-protocol/src/client-wire-contract.ts
|
|
72
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
73
|
+
|
|
76
74
|
// packages/ceal-client-protocol/client-wire-contract.json
|
|
77
75
|
var client_wire_contract_default = {
|
|
78
76
|
schema_version: "ceal.client_wire_contract.v1",
|
|
77
|
+
doctor: {
|
|
78
|
+
schema_version: "ceal.doctor.v1"
|
|
79
|
+
},
|
|
80
|
+
client_commands: {
|
|
81
|
+
doctor: ["doctor"],
|
|
82
|
+
version: ["version"],
|
|
83
|
+
access_group_list: ["access-group", "list"]
|
|
84
|
+
},
|
|
85
|
+
client_environment: {
|
|
86
|
+
environment_prefix: "CEAL",
|
|
87
|
+
context_override_suffixes: ["GATEWAY_ORIGIN", "GATEWAY_TRANSPORT_ORIGIN", "ACCESS_TOKEN", "CONTEXT_STORE_PATH"]
|
|
88
|
+
},
|
|
79
89
|
canonical_json: {
|
|
80
90
|
ordering: "utf16_code_unit_ascending"
|
|
81
91
|
},
|
|
82
92
|
artifact_descriptor: {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
operation_contract_bundle_schema_name: "ceal.operation_contract_bundle",
|
|
94
|
+
operation_contract_bundle_fields: ["schema_version", "catalog_revision", "operations", "failure_guidance"],
|
|
95
|
+
schema_name: "ceal.verified_artifact_descriptor",
|
|
96
|
+
fields: ["schema_version", "kind", "media_type", "byte_count", "sha256", "binding"],
|
|
97
|
+
binding_schema_name: "ceal.verified_artifact_binding",
|
|
98
|
+
binding_fields: ["schema_version", "kind", "catalog_revision"],
|
|
86
99
|
kind: "verified_artifact",
|
|
87
100
|
binding_kind: "catalog",
|
|
88
101
|
media_type: "application/json",
|
|
@@ -98,14 +111,16 @@ var client_wire_contract_default = {
|
|
|
98
111
|
length: 72,
|
|
99
112
|
pattern: "^catalog:[a-f0-9]{64}$"
|
|
100
113
|
},
|
|
101
|
-
envelope_max_bytes:
|
|
114
|
+
envelope_max_bytes: 407
|
|
102
115
|
},
|
|
103
116
|
command_request: {
|
|
104
|
-
|
|
117
|
+
schema_name: "ceal.command_request",
|
|
118
|
+
fields: ["schema_version", "argv", "operation", "client_nonce", "context_token"],
|
|
105
119
|
absolute_transport_max_bytes: 65536
|
|
106
120
|
},
|
|
107
121
|
command_resolution: {
|
|
108
|
-
|
|
122
|
+
schema_name: "ceal.gateway_command_resolution",
|
|
123
|
+
fields: ["schema_version", "kind", "ok", "execution_started", "program", "route", "authority", "routes", "operations", "required_action_sequence", "semantic_options", "result_delivery", "operation", "invocation", "entrypoints", "common_concepts", "withheld", "catalog_revision", "operation_id", "input", "transport", "output", "unresolved_inputs", "unresolved_refusal", "attempted_route", "did_you_mean", "error"],
|
|
109
124
|
authority: {
|
|
110
125
|
anonymous: {
|
|
111
126
|
state: "anonymous",
|
|
@@ -145,7 +160,8 @@ var client_wire_contract_default = {
|
|
|
145
160
|
}
|
|
146
161
|
},
|
|
147
162
|
command_response: {
|
|
148
|
-
|
|
163
|
+
schema_name: "ceal.command_response",
|
|
164
|
+
fields: ["schema_version", "exit_code", "document", "operation_id", "visible_output_max_bytes", "local_delivery", "verified_artifact"],
|
|
149
165
|
descriptor_field: "verified_artifact",
|
|
150
166
|
required_fields: [
|
|
151
167
|
"schema_version",
|
|
@@ -169,29 +185,41 @@ var client_wire_contract_default = {
|
|
|
169
185
|
"required_action_sequence",
|
|
170
186
|
"withheld"
|
|
171
187
|
],
|
|
172
|
-
prompt_field: "prompt",
|
|
173
|
-
prompt_schema_version: "ceal.prompt.v1",
|
|
174
|
-
focused_prompt_document_fields: [
|
|
175
|
-
"catalog_revision",
|
|
176
|
-
"prompt"
|
|
177
|
-
],
|
|
178
|
-
focused_prompt_continuation_document_fields: [
|
|
179
|
-
"catalog_revision",
|
|
180
|
-
"remaining_work",
|
|
181
|
-
"prompt"
|
|
182
|
-
],
|
|
183
188
|
discovery_command: "discovery",
|
|
184
|
-
|
|
189
|
+
discovery_manifest_schema_name: "ceal.discovery_manifest",
|
|
190
|
+
discovery_manifest_fields: ["schema_version", "skill", "authority_scope", "connector_kinds", "personal_connector_kinds", "guide_markdown", "renderer_version", "digest"],
|
|
185
191
|
discovery_manifest_document_fields: [
|
|
186
192
|
"discovery_manifest"
|
|
187
193
|
],
|
|
188
194
|
exit_code_max: 125,
|
|
189
195
|
bare_document_max_bytes: 65536,
|
|
190
|
-
complete_max_bytes:
|
|
196
|
+
complete_max_bytes: 66046
|
|
197
|
+
},
|
|
198
|
+
host_invocation_authentication: {
|
|
199
|
+
request_schema_name: "ceal.host_invocation_authentication_request",
|
|
200
|
+
request_fields: [
|
|
201
|
+
"schema_version",
|
|
202
|
+
"client_nonce"
|
|
203
|
+
],
|
|
204
|
+
result_schema_name: "ceal.host_invocation_authentication_result",
|
|
205
|
+
result_fields: [
|
|
206
|
+
"schema_version",
|
|
207
|
+
"context_token",
|
|
208
|
+
"expires_at"
|
|
209
|
+
],
|
|
210
|
+
client_nonce: {
|
|
211
|
+
character_class_source: "A-Za-z0-9_-",
|
|
212
|
+
encoded_characters: 43
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
gateway_host_invocation_context_request: {
|
|
216
|
+
schema_name: "ceal.gateway_host_invocation_context_request",
|
|
217
|
+
fields: ["schema_version", "client_nonce", "context_expires_at", "request_work_context", "current_prompt", "visible_output_max_bytes"]
|
|
191
218
|
},
|
|
192
219
|
resource_stream: {
|
|
193
220
|
path: "/api/ceal/v2/resources",
|
|
194
|
-
|
|
221
|
+
request_schema_name: "ceal.resource_stream_request",
|
|
222
|
+
request_fields: ["schema_version", "resource_ref", "client_nonce", "context_token"],
|
|
195
223
|
ref_pattern: "^resource:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
|
|
196
224
|
media_type_pattern: "^[a-z0-9][a-z0-9!#$&^_.+-]{0,63}/[a-z0-9][a-z0-9!#$&^_.+-]{0,63}$",
|
|
197
225
|
filename_max_bytes: 512,
|
|
@@ -208,88 +236,6 @@ var client_wire_contract_default = {
|
|
|
208
236
|
}
|
|
209
237
|
};
|
|
210
238
|
|
|
211
|
-
// packages/ceal-client-protocol/src/client-wire-contract.ts
|
|
212
|
-
var CEAL_CLIENT_WIRE_CONTRACT = Object.freeze(client_wire_contract_default);
|
|
213
|
-
var commandResolution = CEAL_CLIENT_WIRE_CONTRACT.command_resolution;
|
|
214
|
-
var commandResolutionAuthority = commandResolution.authority;
|
|
215
|
-
var commandResolutionSemanticOptions = commandResolution.semantic_options;
|
|
216
|
-
var CEAL_GATEWAY_COMMAND_RESOLUTION_SCHEMA_VERSION = commandResolution.schema_version;
|
|
217
|
-
var CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES = CEAL_CLIENT_WIRE_CONTRACT.encoded_json_document.max_bytes;
|
|
218
|
-
var artifact = CEAL_CLIENT_WIRE_CONTRACT.artifact_descriptor;
|
|
219
|
-
var request = CEAL_CLIENT_WIRE_CONTRACT.command_request;
|
|
220
|
-
var response = CEAL_CLIENT_WIRE_CONTRACT.command_response;
|
|
221
|
-
var CEAL_OPERATION_CONTRACT_BUNDLE_SCHEMA_VERSION = artifact.operation_contract_bundle_schema_version;
|
|
222
|
-
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_SCHEMA_VERSION = artifact.schema_version;
|
|
223
|
-
var CEAL_VERIFIED_ARTIFACT_BINDING_SCHEMA_VERSION = artifact.binding_schema_version;
|
|
224
|
-
var CEAL_VERIFIED_ARTIFACT_KIND = artifact.kind;
|
|
225
|
-
var CEAL_VERIFIED_ARTIFACT_BINDING_KIND = artifact.binding_kind;
|
|
226
|
-
var CEAL_VERIFIED_ARTIFACT_MEDIA_TYPE = artifact.media_type;
|
|
227
|
-
var CEAL_VERIFIED_ARTIFACT_DIGEST_LENGTH = artifact.digest.length;
|
|
228
|
-
var CEAL_VERIFIED_ARTIFACT_DIGEST_PATTERN = artifact.digest.pattern;
|
|
229
|
-
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_LENGTH = artifact.catalog_revision.length;
|
|
230
|
-
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_PATTERN = artifact.catalog_revision.pattern;
|
|
231
|
-
var CEAL_VERIFIED_ARTIFACT_MAX_BYTES = CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES;
|
|
232
|
-
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_MAX_BYTES = artifact.envelope_max_bytes;
|
|
233
|
-
var CEAL_COMMAND_REQUEST_SCHEMA_VERSION = request.schema_version;
|
|
234
|
-
var CEAL_COMMAND_REQUEST_MAX_BYTES = request.absolute_transport_max_bytes;
|
|
235
|
-
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_MAX_BYTES = response.bare_document_max_bytes;
|
|
236
|
-
var CEAL_COMMAND_RESPONSE_PROMPT_FIELD = response.prompt_field;
|
|
237
|
-
var CEAL_COMMAND_RESPONSE_PROMPT_SCHEMA_VERSION = response.prompt_schema_version;
|
|
238
|
-
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_OPTIONAL_FIELDS = Object.freeze([...response.bare_document_optional_fields]);
|
|
239
|
-
var CEAL_COMMAND_RESPONSE_PROMPT_DOCUMENT_FIELDS = Object.freeze([...response.bare_document_fields, response.prompt_field]);
|
|
240
|
-
var CEAL_COMMAND_RESPONSE_FOCUSED_PROMPT_DOCUMENT_FIELDS = Object.freeze([...response.focused_prompt_document_fields]);
|
|
241
|
-
var CEAL_COMMAND_RESPONSE_FOCUSED_PROMPT_CONTINUATION_DOCUMENT_FIELDS = Object.freeze([...response.focused_prompt_continuation_document_fields]);
|
|
242
|
-
var CEAL_COMMAND_RESPONSE_DISCOVERY_COMMAND = response.discovery_command;
|
|
243
|
-
var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_SCHEMA_VERSION = response.discovery_manifest_schema_version;
|
|
244
|
-
var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_DOCUMENT_FIELDS = Object.freeze([...response.discovery_manifest_document_fields]);
|
|
245
|
-
|
|
246
|
-
// packages/ceal-client-protocol/src/vocabulary.ts
|
|
247
|
-
function vocabulary(words) {
|
|
248
|
-
return Object.freeze(words);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// packages/ceal-client-protocol/src/gateway-response-types.ts
|
|
252
|
-
var CEAL_OPERATION_READINESS_VALUES = vocabulary(["ready", "degraded", "unavailable", "unknown"]);
|
|
253
|
-
var CEAL_TARGET_REQUIREMENTS = vocabulary(["required", "optional", "none"]);
|
|
254
|
-
var CEAL_AUDIT_OUTCOMES = vocabulary(["succeeded", "denied", "failed"]);
|
|
255
|
-
var CEAL_GATEWAY_PROVIDER_STEP_OUTCOMES = vocabulary(["completed", "rejected", "throttled", "failed"]);
|
|
256
|
-
var CEAL_GATEWAY_READ_REUSE_DISPOSITIONS = vocabulary(["response_cache_hit", "corpus_hit", "corpus_hit_after_revalidation", "corpus_partial_provider_supplement", "provider_direct", "provider_continuation", "reuse_unavailable"]);
|
|
257
|
-
var CEAL_GATEWAY_READ_REUSE_UNAVAILABLE_REASONS = vocabulary(["not_reported", "storage_unavailable", "coverage_incomplete", "freshness_refresh_required", "not_reusable", "bypassed"]);
|
|
258
|
-
var CEAL_GATEWAY_DERIVED_STORE_FAILURE_CLASSES = vocabulary(["storage_unavailable", "decode_mismatch", "write_conflict", "quota", "store_error"]);
|
|
259
|
-
var CEAL_POLICY_DECISIONS = vocabulary(["allowed", "denied", "not_evaluated"]);
|
|
260
|
-
var CEAL_CONNECTOR_ROUTE_PHASES = vocabulary(["scope_observation", "target_selection", "route_resolution"]);
|
|
261
|
-
var CEAL_CONNECTOR_ROUTE_CAUSES = vocabulary(["provider_throttled", "provider_unavailable", "binding_invalid", "scope_limit_exceeded"]);
|
|
262
|
-
var CEAL_WRITE_SOURCE_KINDS = vocabulary(["authenticated_registered_client", "provider_authenticated_event"]);
|
|
263
|
-
var CEAL_REFRESH_DELIVERIES = vocabulary(["initial", "recovery", "replay", "terminal_failure", "recovery_unavailable"]);
|
|
264
|
-
var CEAL_CLIENT_OPERATIONS = vocabulary(["handshake", "discover", "call", "readback"]);
|
|
265
|
-
var CEAL_WRITE_IDEMPOTENCY_POSTURES = vocabulary(["required", "optional", "not_required"]);
|
|
266
|
-
var CEAL_WRITE_PROVIDER_READBACK_POSTURES = vocabulary(["required", "best_effort", "not_available"]);
|
|
267
|
-
var CEAL_WRITE_ATTRIBUTIONS = vocabulary(["subject", "requester_event", "connector_integration"]);
|
|
268
|
-
var CEAL_WRITE_RECEIPT_PROVIDER_STATES = vocabulary(["outcome_unknown", "verified", "not_dispatched"]);
|
|
269
|
-
var CEAL_WRITE_RECEIPT_PROVIDER_READBACKS = vocabulary(["outcome_unknown", "verified", "not_applied"]);
|
|
270
|
-
var CEAL_PROTOCOL_VERSION = "1.4.0";
|
|
271
|
-
var CEAL_OPERATION_ACCESS_SCHEMA_VERSION = "ceal.operation_access.v1";
|
|
272
|
-
var CEAL_GATEWAY_CONTINUATION_CURSOR_PHASES = vocabulary(["scan", "delivery", "terminal"]);
|
|
273
|
-
var CEAL_GATEWAY_ACTIVE_CALL_PHASES = vocabulary([
|
|
274
|
-
"audited_call_pending",
|
|
275
|
-
"continuation_read_pending",
|
|
276
|
-
"continuation_write_pending",
|
|
277
|
-
"provider_pacing_pending",
|
|
278
|
-
"corpus_replay_pending",
|
|
279
|
-
"corpus_append_pending",
|
|
280
|
-
"corpus_fence_pending",
|
|
281
|
-
"corpus_content_pending",
|
|
282
|
-
"corpus_transaction_pending"
|
|
283
|
-
]);
|
|
284
|
-
var CEAL_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "phase", "schema_version"]);
|
|
285
|
-
var CEAL_CLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["cause", ...CEAL_CONNECTOR_ROUTE_FAILURE_KEYS]);
|
|
286
|
-
var CEAL_UNCLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "error_class", "phase", "schema_version"]);
|
|
287
|
-
var CEAL_GATEWAY_ORGANIZATION_ROUTE_FACT_CALL_KEYS = Object.freeze([
|
|
288
|
-
"connector_ref",
|
|
289
|
-
"connector_revision",
|
|
290
|
-
"policy_decision_revision"
|
|
291
|
-
]);
|
|
292
|
-
|
|
293
239
|
// packages/ceal-client-protocol/src/operation-next-actions.ts
|
|
294
240
|
var CEAL_REPEAT_REQUEST_NEXT_ACTION = "repeat_request";
|
|
295
241
|
var CEAL_INSPECT_RECEIPT_NEXT_ACTION = "inspect_receipt";
|
|
@@ -356,7 +302,10 @@ var CALLER_RECOVERABLE_NEXT_ACTION_VALUES = [
|
|
|
356
302
|
"follow_required_action_sequence",
|
|
357
303
|
// The command needs caller-owned local materialization (for example an
|
|
358
304
|
// @path file) before the Gateway can receive the argument.
|
|
359
|
-
"materialize_local_argument"
|
|
305
|
+
"materialize_local_argument",
|
|
306
|
+
// A peer's derived schema identifier changed with its field set; install or
|
|
307
|
+
// upgrade the CLI and Host Relay to the Gateway's release before retrying.
|
|
308
|
+
"upgrade_ceal_client"
|
|
360
309
|
];
|
|
361
310
|
var CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS = Object.freeze(CALLER_RECOVERABLE_NEXT_ACTION_VALUES);
|
|
362
311
|
var NON_REMEDY_NEXT_ACTION_VALUES = [
|
|
@@ -469,47 +418,53 @@ var CEAL_OPERATION_WITHHELD_NEXT_ACTIONS = Object.freeze({
|
|
|
469
418
|
personal_authorization_required: "connect_personal_account"
|
|
470
419
|
});
|
|
471
420
|
|
|
472
|
-
// packages/ceal-client-protocol/src/
|
|
473
|
-
|
|
421
|
+
// packages/ceal-client-protocol/src/vocabulary.ts
|
|
422
|
+
function vocabulary(words) {
|
|
423
|
+
return Object.freeze(words);
|
|
424
|
+
}
|
|
474
425
|
|
|
475
|
-
// packages/ceal-client-protocol/src/
|
|
476
|
-
var
|
|
477
|
-
var
|
|
478
|
-
var
|
|
479
|
-
var
|
|
480
|
-
var
|
|
481
|
-
var
|
|
482
|
-
var
|
|
483
|
-
var
|
|
484
|
-
var
|
|
485
|
-
var
|
|
486
|
-
var
|
|
487
|
-
var
|
|
488
|
-
var
|
|
489
|
-
var
|
|
490
|
-
var
|
|
491
|
-
var
|
|
492
|
-
var
|
|
493
|
-
var
|
|
494
|
-
var
|
|
495
|
-
var
|
|
496
|
-
var
|
|
497
|
-
var
|
|
498
|
-
var
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
"
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
426
|
+
// packages/ceal-client-protocol/src/gateway-response-types.ts
|
|
427
|
+
var CEAL_OPERATION_READINESS_VALUES = vocabulary(["ready", "degraded", "unavailable", "unknown"]);
|
|
428
|
+
var CEAL_TARGET_REQUIREMENTS = vocabulary(["required", "optional", "none"]);
|
|
429
|
+
var CEAL_AUDIT_OUTCOMES = vocabulary(["succeeded", "denied", "failed"]);
|
|
430
|
+
var CEAL_GATEWAY_PROVIDER_STEP_OUTCOMES = vocabulary(["completed", "rejected", "throttled", "failed"]);
|
|
431
|
+
var CEAL_GATEWAY_READ_REUSE_DISPOSITIONS = vocabulary(["response_cache_hit", "corpus_hit", "corpus_hit_after_revalidation", "corpus_partial_provider_supplement", "provider_direct", "provider_continuation", "reuse_unavailable"]);
|
|
432
|
+
var CEAL_GATEWAY_READ_REUSE_UNAVAILABLE_REASONS = vocabulary(["not_reported", "storage_unavailable", "coverage_incomplete", "freshness_refresh_required", "not_reusable", "bypassed"]);
|
|
433
|
+
var CEAL_GATEWAY_DERIVED_STORE_FAILURE_CLASSES = vocabulary(["storage_unavailable", "decode_mismatch", "write_conflict", "quota", "store_error"]);
|
|
434
|
+
var CEAL_POLICY_DECISIONS = vocabulary(["allowed", "denied", "not_evaluated"]);
|
|
435
|
+
var CEAL_CONNECTOR_ROUTE_PHASES = vocabulary(["scope_observation", "target_selection", "route_resolution"]);
|
|
436
|
+
var CEAL_CONNECTOR_ROUTE_CAUSES = vocabulary(["provider_throttled", "provider_unavailable", "binding_invalid", "scope_limit_exceeded"]);
|
|
437
|
+
var CEAL_WRITE_SOURCE_KINDS = vocabulary(["authenticated_registered_client", "provider_authenticated_event"]);
|
|
438
|
+
var CEAL_REFRESH_DELIVERIES = vocabulary(["initial", "recovery", "replay", "terminal_failure", "recovery_unavailable"]);
|
|
439
|
+
var CEAL_CLIENT_OPERATIONS = vocabulary(["handshake", "discover", "call", "readback"]);
|
|
440
|
+
var CEAL_WRITE_IDEMPOTENCY_POSTURES = vocabulary(["required", "optional", "not_required"]);
|
|
441
|
+
var CEAL_WRITE_PROVIDER_READBACK_POSTURES = vocabulary(["required", "best_effort", "not_available"]);
|
|
442
|
+
var CEAL_WRITE_ATTRIBUTIONS = vocabulary(["subject", "requester_event", "connector_integration"]);
|
|
443
|
+
var CEAL_WRITE_RECEIPT_PROVIDER_STATES = vocabulary(["outcome_unknown", "verified", "not_dispatched"]);
|
|
444
|
+
var CEAL_WRITE_RECEIPT_PROVIDER_READBACKS = vocabulary(["outcome_unknown", "verified", "not_applied"]);
|
|
445
|
+
var CEAL_PROTOCOL_VERSION = "1.4.0";
|
|
446
|
+
var CEAL_OPERATION_ACCESS_SCHEMA_VERSION = "ceal.operation_access.v1";
|
|
447
|
+
var CEAL_GATEWAY_CALL_RESULT_SCHEMA_VERSION = "ceal.gateway_call_result.v1";
|
|
448
|
+
var CEAL_GATEWAY_CONTINUATION_CURSOR_PHASES = vocabulary(["scan", "delivery", "terminal"]);
|
|
449
|
+
var CEAL_GATEWAY_ACTIVE_CALL_PHASES = vocabulary([
|
|
450
|
+
"audited_call_pending",
|
|
451
|
+
"continuation_read_pending",
|
|
452
|
+
"continuation_write_pending",
|
|
453
|
+
"provider_pacing_pending",
|
|
454
|
+
"corpus_replay_pending",
|
|
455
|
+
"corpus_append_pending",
|
|
456
|
+
"corpus_fence_pending",
|
|
457
|
+
"corpus_content_pending",
|
|
458
|
+
"corpus_transaction_pending"
|
|
459
|
+
]);
|
|
460
|
+
var CEAL_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "phase", "schema_version"]);
|
|
461
|
+
var CEAL_CLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["cause", ...CEAL_CONNECTOR_ROUTE_FAILURE_KEYS]);
|
|
462
|
+
var CEAL_UNCLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "error_class", "phase", "schema_version"]);
|
|
463
|
+
var CEAL_GATEWAY_AUDIT_CALL_DETAIL_SCHEMA_VERSION = "ceal.gateway_audit_call_detail.v1";
|
|
464
|
+
var CEAL_GATEWAY_ORGANIZATION_ROUTE_FACT_CALL_KEYS = Object.freeze([
|
|
465
|
+
"connector_ref",
|
|
466
|
+
"connector_revision",
|
|
467
|
+
"policy_decision_revision"
|
|
513
468
|
]);
|
|
514
469
|
|
|
515
470
|
// packages/ceal-client-protocol/src/gateway-validation-primitives.ts
|
|
@@ -551,10 +506,10 @@ function assertSafeJsonArray(value, options, depth, count) {
|
|
|
551
506
|
if (value.length > CEAL_WIRE_SAFE_ARRAY_MAX_ITEMS) invalidByContext(options);
|
|
552
507
|
for (const item of value) assertSafeJsonValue(item, options, depth + 1, count);
|
|
553
508
|
}
|
|
554
|
-
function assertSafeJsonRecord(
|
|
555
|
-
const entries = Object.entries(
|
|
509
|
+
function assertSafeJsonRecord(record4, options, depth, count) {
|
|
510
|
+
const entries = Object.entries(record4);
|
|
556
511
|
if (entries.length > CEAL_WIRE_SAFE_ARRAY_MAX_ITEMS) invalidByContext(options);
|
|
557
|
-
const sourceUrlColumn = options.allowHttpsUrl ? compactSourceUrlColumn(
|
|
512
|
+
const sourceUrlColumn = options.allowHttpsUrl ? compactSourceUrlColumn(record4.fields) : null;
|
|
558
513
|
for (const [key, child] of entries) {
|
|
559
514
|
if (key === "credential_material_included" && child !== false) invalidByContext(options);
|
|
560
515
|
if (!isSafeNegativeMaterialAssertion(key, child)) assertSafeJsonKey(key, options);
|
|
@@ -723,51 +678,6 @@ function cut(text) {
|
|
|
723
678
|
return text.length <= REFUSAL_VALUE_MAX_CHARS ? text : `${text.slice(0, REFUSAL_VALUE_MAX_CHARS)}...(+${text.length - REFUSAL_VALUE_MAX_CHARS} chars)`;
|
|
724
679
|
}
|
|
725
680
|
|
|
726
|
-
// packages/ceal-client-protocol/src/operation-access-class.ts
|
|
727
|
-
var CEAL_OPERATION_ACCESS_CLASSES = vocabulary(["read", "write"]);
|
|
728
|
-
|
|
729
|
-
// packages/ceal-client-protocol/src/domain-vocabulary.ts
|
|
730
|
-
var CEAL_CONNECTOR_STATUSES = vocabulary(["active", "disabled", "disconnected"]);
|
|
731
|
-
var CEAL_CONNECTOR_CUSTODIES = vocabulary(["organization", "personal"]);
|
|
732
|
-
var CEAL_ACCESS_LEVELS = vocabulary(["none", "read", "read_write"]);
|
|
733
|
-
var CEAL_PERSONAL_ACCESS_CEILINGS = vocabulary(["disabled", "read", "read_write"]);
|
|
734
|
-
var CEAL_TARGET_REF_PATTERN = "^[^\\p{Cc}]+$";
|
|
735
|
-
var TARGET_REF = new RegExp(CEAL_TARGET_REF_PATTERN, "u");
|
|
736
|
-
var CEAL_TARGET_VISIBILITIES = vocabulary(["public", "private"]);
|
|
737
|
-
var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "runtime"]);
|
|
738
|
-
var CEAL_PERSISTED_PRINCIPAL_KINDS = vocabulary(["human", "service", "runtime"]);
|
|
739
|
-
var CEAL_ORGANIZATION_ROLES = vocabulary(["member", "admin"]);
|
|
740
|
-
var CEAL_CALENDAR_PERIODS = vocabulary(["day", "week", "month"]);
|
|
741
|
-
var LABEL_EXPECTATION = `public-safe display text of at most ${CEAL_PUBLIC_DISPLAY_NAME_MAX_BYTES} bytes`;
|
|
742
|
-
|
|
743
|
-
// packages/ceal-client-protocol/src/safe-json-budget.ts
|
|
744
|
-
var SAFE_JSON_MIN_BYTES_PER_NODE = 4;
|
|
745
|
-
function safeJsonNodeBudgetForBytes(maxBytes) {
|
|
746
|
-
if (!Number.isSafeInteger(maxBytes) || maxBytes < SAFE_JSON_MIN_BYTES_PER_NODE) {
|
|
747
|
-
throw new RangeError(`safe-JSON byte cap is invalid: ${refusalFact("maxBytes", maxBytes, `at least ${SAFE_JSON_MIN_BYTES_PER_NODE} bytes`)}`);
|
|
748
|
-
}
|
|
749
|
-
return Math.floor(maxBytes / SAFE_JSON_MIN_BYTES_PER_NODE);
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
// packages/ceal-client-protocol/src/protocol-bounds.ts
|
|
753
|
-
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES = 64 * 1024;
|
|
754
|
-
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_NODES = safeJsonNodeBudgetForBytes(CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES);
|
|
755
|
-
var OPERATION_SCHEMA_LITERAL_MAX_BYTES = CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES / 4;
|
|
756
|
-
var CEAL_OPERATION_JSON_SCHEMA_BUDGET = Object.freeze({
|
|
757
|
-
max_depth: 8,
|
|
758
|
-
max_properties: 96,
|
|
759
|
-
max_string_length: OPERATION_SCHEMA_LITERAL_MAX_BYTES / 4,
|
|
760
|
-
max_alternatives: 8,
|
|
761
|
-
max_enum_values: 64,
|
|
762
|
-
max_literal_bytes: OPERATION_SCHEMA_LITERAL_MAX_BYTES,
|
|
763
|
-
/** @basis our_policy -- bounds explanatory schema prose carried in one catalog. */
|
|
764
|
-
max_description_characters: 1024,
|
|
765
|
-
/** @basis our_policy -- bounds regular-expression compilation input from one schema. */
|
|
766
|
-
max_pattern_characters: 256,
|
|
767
|
-
/** @basis our_policy -- bounds traversal work inside one already byte-bounded schema literal. */
|
|
768
|
-
max_literal_nodes: 512
|
|
769
|
-
});
|
|
770
|
-
|
|
771
681
|
// packages/ceal-client-protocol/src/operation-validation-primitives.ts
|
|
772
682
|
var ISO_TIMESTAMP = /^(\d{4})-(\d{2})-(\d{2})[Tt](\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?([Zz]|([+-])(\d{2}):(\d{2}))$/u;
|
|
773
683
|
var CEAL_CALENDAR_DATE_PATTERN_SOURCE = "^\\d{4}-\\d{2}-\\d{2}$";
|
|
@@ -835,19 +745,330 @@ function requireIsoTimestamp(value, message) {
|
|
|
835
745
|
if (!isCealIsoTimestamp(value)) throw new TypeError(message);
|
|
836
746
|
}
|
|
837
747
|
|
|
838
|
-
// packages/ceal-client-protocol/src/
|
|
839
|
-
var
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
var
|
|
843
|
-
var
|
|
844
|
-
var
|
|
845
|
-
var
|
|
748
|
+
// packages/ceal-client-protocol/src/failure-guidance-table.ts
|
|
749
|
+
var CEAL_FAILURE_REDACTION_CLASSES = vocabulary(["model_safe", "withheld"]);
|
|
750
|
+
var CEAL_GATEWAY_STARTING_FAILURE_CODE = "gateway_starting";
|
|
751
|
+
var CEAL_OWNER_BOOTSTRAP_ABSENT_FAILURE_CODE = "bootstrap_absent";
|
|
752
|
+
var CEAL_OWNER_BOOTSTRAP_UNAVAILABLE_FAILURE_CODE = "bootstrap_unavailable";
|
|
753
|
+
var LOCAL_COMMAND_INVALID = "The local command or argument is invalid.";
|
|
754
|
+
var LOCAL_TRANSPORT_INCOMPLETE = "The local Ceal transport could not complete; report this as a gap.";
|
|
755
|
+
var GATEWAY_TRANSPORT_UNAVAILABLE = "The Gateway transport is unavailable; ask an administrator to restore Gateway availability.";
|
|
756
|
+
var HOST_AUTHENTICATION_UNAVAILABLE = "The Host could not authenticate this invocation; report this as a gap.";
|
|
757
|
+
var SESSION_CONTEXT_UNAVAILABLE = "The local Gateway session context is unavailable.";
|
|
758
|
+
function guidance(message, next_action, http_status, retryable = false, redaction_class = "model_safe") {
|
|
759
|
+
return Object.freeze({ message, next_action, http_status, retryable, redaction_class });
|
|
760
|
+
}
|
|
761
|
+
var CEAL_FAILURE_GUIDANCE_TABLE = Object.freeze({
|
|
762
|
+
invalid_command: guidance(LOCAL_COMMAND_INVALID, "correct_argument", 400),
|
|
763
|
+
invalid_argument: guidance(LOCAL_COMMAND_INVALID, "correct_argument", 400),
|
|
764
|
+
command_not_found: guidance(LOCAL_COMMAND_INVALID, "correct_argument", 404),
|
|
765
|
+
unknown_operation: guidance("The requested Operation is absent from the current verified catalog; report this as a gap unless a catalog suggestion names a replacement.", "answer_with_gap", 404),
|
|
766
|
+
local_route_unavailable: guidance("The requested local route is unavailable or its descriptor is invalid; report this as a gap.", "answer_with_gap", 404),
|
|
767
|
+
local_route_ambiguous: guidance("The requested local route is ambiguous; no local executable was selected. Report this as a gap.", "answer_with_gap", 400),
|
|
768
|
+
local_execution_unavailable: guidance("The selected local route executable is unavailable; report this as a gap.", "answer_with_gap", 500),
|
|
769
|
+
local_pickup_failed: guidance("The Host Request pickup failed; the Gateway command was not sent. Report this as a gap.", "answer_with_gap", 502),
|
|
770
|
+
invocation_authentication_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 401),
|
|
771
|
+
local_execution_cancellation_unknown: guidance("The selected local route cancellation outcome is unknown; a retry can repeat an effect the cancelled route already completed. Report this as a gap and do not retry.", "answer_with_gap", 409),
|
|
772
|
+
gateway_origin_required: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
773
|
+
access_token_required: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
774
|
+
context_pair_required: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
775
|
+
context_not_selected: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
776
|
+
context_store_invalid: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
777
|
+
context_store_conflict: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
778
|
+
refresh_invalid: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
779
|
+
refresh_expired: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
780
|
+
refresh_replayed: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
781
|
+
refresh_revoked: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
782
|
+
refresh_recovery_unavailable: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
783
|
+
refresh_custody_invalid: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
784
|
+
session_store_unwritable: guidance("The Ceal client could not write its local session store; the operating system refused the write (EACCES, EPERM, or EROFS). rerun_outside_agent_sandbox reruns it with escalated execution.", "rerun_outside_agent_sandbox", 500),
|
|
785
|
+
local_output_exists: guidance("The selected local output path already exists; nothing was written to it.", "choose_new_output_path", 400),
|
|
786
|
+
local_output_outside_result_root: guidance("The selected local output path lies outside this run's result root; only a path inside the result root named here is writable.", "choose_new_output_path", 400),
|
|
787
|
+
local_output_unwritable: guidance("The Ceal client could not write the result file at its local path; the operating system refused the write. When that step followed the Gateway call, the call may already have completed a write effect that a rerun repeats. rerun_outside_agent_sandbox reruns it with escalated execution.", "rerun_outside_agent_sandbox", 500),
|
|
788
|
+
local_output_unavailable: guidance("The Gateway result could not be materialized at the selected local path; report this as a gap.", "answer_with_gap", 500),
|
|
789
|
+
local_materialization_required: guidance("The Gateway result could not be materialized at the selected local path; report this as a gap.", "answer_with_gap", 500),
|
|
790
|
+
contract_artifact_cache_invalid: guidance("The verified contract artifact could not be stored in the private local cache; report this as a gap.", "answer_with_gap", 500),
|
|
791
|
+
response_encoding_failed: guidance("The local response could not be encoded; report this as a gap.", "answer_with_gap", 500),
|
|
792
|
+
request_timeout: guidance(GATEWAY_TRANSPORT_UNAVAILABLE, "ask_admin", 504),
|
|
793
|
+
request_failed: guidance(GATEWAY_TRANSPORT_UNAVAILABLE, "ask_admin", 502),
|
|
794
|
+
gateway_unavailable: guidance(GATEWAY_TRANSPORT_UNAVAILABLE, "ask_admin", 503, true),
|
|
795
|
+
contract_mismatch: guidance("Install or upgrade the Ceal CLI and Host Relay to the same release as the Gateway, then retry.", "upgrade_ceal_client", 409),
|
|
796
|
+
// `gateway_starting` is the one retryable envelope the serving gate answers on every route while starting
|
|
797
|
+
// (`cli/internal/client/gateway_startup_wait.go` re-drives it); the sentence is the client default that renders
|
|
798
|
+
// when the wait ends still unserved, moved from the `localFailureText` default branch.
|
|
799
|
+
[CEAL_GATEWAY_STARTING_FAILURE_CODE]: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 503, true),
|
|
800
|
+
gateway_internal_error: guidance("The Gateway failed without exposing internal state; report this as a gap.", "answer_with_gap", 500),
|
|
801
|
+
// The serving gate emits this code with no message; the sentence is the owner-normalized form of the
|
|
802
|
+
// `gateway_internal_error` guidance (decided with the operator 2026-10-13).
|
|
803
|
+
internal_error: guidance("The Gateway failed without exposing internal state.", "answer_with_gap", 500),
|
|
804
|
+
// Owned by the Gateway canonical adapter (`gateway/core/src/gateway-canonical-operation-http-adapter.ts`):
|
|
805
|
+
// the client variant of this sentence in `localFailureText` was retired when the client started reading this table.
|
|
806
|
+
request_body_too_large: guidance("The Gateway request body exceeds its bounded limit.", "reduce_request_size", 413),
|
|
807
|
+
// Restored verbatim from the retired `localFailureText` `unknown_outcome`
|
|
808
|
+
// case: the outcome is unknown, so a read may be retried while a write
|
|
809
|
+
// must receipt-check first. Collapsing both to "do not repeat the call"
|
|
810
|
+
// forbids the safe read retry the sentence exists to allow.
|
|
811
|
+
unknown_outcome: guidance("The transport outcome is unknown: a read may be retried; for a write, check write_receipt.get before repeating.", "answer_with_gap", 500),
|
|
812
|
+
// Restored verbatim from the retired `localFailureText`
|
|
813
|
+
// `refresh_unknown_outcome` case: a refresh that ends unknown leaves the
|
|
814
|
+
// session context unavailable, so the caller repairs or adopts the
|
|
815
|
+
// session instead of reporting a transport gap.
|
|
816
|
+
refresh_unknown_outcome: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
817
|
+
request_cancelled: guidance("The caller cancelled the request before it completed.", "retry", 400, true),
|
|
818
|
+
expired: guidance("The activation transaction or its administrator approval request expired before completion; repeat the original activation arguments to start a new transaction.", "repeat_original_arguments", 410, true),
|
|
819
|
+
denied: guidance("An administrator denied this device's activation request; ask an administrator whether to reconsider it.", "ask_admin", 403),
|
|
820
|
+
device_approval_pending_limit: guidance("Device approval requests are already waiting for an administrator decision; ask an administrator to decide a pending request before adding another device.", "ask_admin", 429),
|
|
821
|
+
session_already_adopted: guidance("The requested enrollment was not started because this machine already has a live session with the Gateway. Report this as a gap if that local session needs replacement.", "answer_with_gap", 409),
|
|
822
|
+
pending_adoption_limit: guidance("This device's pending activation file is full; another activation is refused while an administrator decision is pending. Report this as a gap.", "answer_with_gap", 429),
|
|
823
|
+
recovery_required: guidance("The Gateway could not safely complete the activation transaction automatically; ask an administrator to recover this transaction.", "ask_admin", 500),
|
|
824
|
+
unsupported_feature: guidance("This Ceal client does not support the Gateway's required activation protocol; ask an administrator to update the client.", "ask_admin", 501),
|
|
825
|
+
// Activation commit codes rendered through the `localFailureText` default before phase 2b; `retryable` is moved from the
|
|
826
|
+
// emitting sites in `cli/internal/client/gateway_installation_activation.go` and `activation.go:483`.
|
|
827
|
+
activation_state_commit_failed: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500, true),
|
|
828
|
+
activation_response_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 502, true),
|
|
829
|
+
activation_rejected: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 502),
|
|
830
|
+
activation_failed: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
831
|
+
activation_binding: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
832
|
+
activation_crypto: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
833
|
+
activation_state_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
834
|
+
// Long-tail local codes rendered through the `localFailureText` default before phase 2b; sentences moved from that branch.
|
|
835
|
+
invalid_response: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
836
|
+
invalid_configuration: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
837
|
+
activation_state_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
838
|
+
activation_code_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
839
|
+
activation_code_file_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
840
|
+
activation_code_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 404),
|
|
841
|
+
bootstrap_file_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
842
|
+
bootstrap_not_active: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 409),
|
|
843
|
+
bootstrap_unauthorized: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 401),
|
|
844
|
+
// The Gateway owner-bootstrap store file was never created, so the Gateway
|
|
845
|
+
// holds no owner bootstrap to exchange (`gateway/src/host/owner-bootstrap-http-composition.ts`
|
|
846
|
+
// `absent`, stored at `gateway/src/shared/installation/owner-bootstrap-store.ts:162`);
|
|
847
|
+
// the sentence is the owner-normalized form of that store error. Non-retryable:
|
|
848
|
+
// repeating the exchange cannot create the store; only an administrator
|
|
849
|
+
// placing a bootstrap (fresh gateway setup) changes the outcome.
|
|
850
|
+
[CEAL_OWNER_BOOTSTRAP_ABSENT_FAILURE_CODE]: guidance("The Gateway holds no owner bootstrap to exchange; report this as a gap.", "answer_with_gap", 503),
|
|
851
|
+
// The Gateway owner-bootstrap store file exists but cannot be read, so the
|
|
852
|
+
// exchange cannot proceed (`gateway/src/host/owner-bootstrap-http-composition.ts`
|
|
853
|
+
// `unavailable`, stored at `gateway/src/shared/installation/owner-bootstrap-store.ts:163`);
|
|
854
|
+
// the sentence is the owner-normalized form of that store error. Retryable:
|
|
855
|
+
// unlike `absent`, a later exchange may succeed once the store is readable.
|
|
856
|
+
[CEAL_OWNER_BOOTSTRAP_UNAVAILABLE_FAILURE_CODE]: guidance("The Gateway owner bootstrap store is unavailable; retry the exchange.", "retry", 503, true),
|
|
857
|
+
context_selection_ambiguous: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
858
|
+
session_authority_unverified: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 401),
|
|
859
|
+
session_authority_mismatch: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 409),
|
|
860
|
+
local_credential_lost: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 401),
|
|
861
|
+
local_capability_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
862
|
+
host_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 503),
|
|
863
|
+
host_activation_failed: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
864
|
+
doctor_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 503),
|
|
865
|
+
admin_sign_in_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 502),
|
|
866
|
+
// `invalid_request` renders through the client default today (local encode/path faults); Gateway-answered
|
|
867
|
+
// envelopes carry the Gateway message verbatim and never read this sentence.
|
|
868
|
+
invalid_request: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
869
|
+
// Gateway canonical-route codes, sentences moved from their minting adapters; statuses moved from the
|
|
870
|
+
// `valid*Failure` decoder maps in `cli/internal/client/gateway_api.go` and `transport.go`.
|
|
871
|
+
invalid_json: guidance("The Gateway request body must be valid JSON.", "answer_with_gap", 400),
|
|
872
|
+
// Per-route variants name their route; the table holds the one stable guidance and the route's `allow`
|
|
873
|
+
// header stays the measured fact (decided with the operator 2026-10-13).
|
|
874
|
+
method_not_allowed: guidance("The HTTP method is not allowed for this Gateway route.", "answer_with_gap", 405),
|
|
875
|
+
request_target_too_large: guidance("The Gateway request target exceeds its bounded limit.", "reduce_request_size", 414),
|
|
876
|
+
route_not_found: guidance("The Gateway route is not present.", "answer_with_gap", 404),
|
|
877
|
+
artifact_unavailable: guidance("The Gateway artifact is unavailable.", "answer_with_gap", 503),
|
|
878
|
+
discovery_unavailable: guidance("The Gateway discovery manifest is unavailable.", "answer_with_gap", 503),
|
|
879
|
+
serialization_failed: guidance("The Gateway response could not be serialized.", "answer_with_gap", 500),
|
|
880
|
+
request_header_too_large: guidance("A consumed Gateway request header exceeds its bounded limit.", "answer_with_gap", 431),
|
|
881
|
+
request_work_context_missing: guidance("The Gateway command requires a Request work context.", "answer_with_gap", 400),
|
|
882
|
+
request_work_context_invalid: guidance("The Gateway Request work context encoding is invalid.", "answer_with_gap", 400),
|
|
883
|
+
request_work_context_refused: guidance("The Host-only discovery manifest takes no Request work context.", "answer_with_gap", 400),
|
|
884
|
+
// Moved from `gateway/src/operations/http-composition.ts` (`unauthorized`) and `command-runtime.ts:827`
|
|
885
|
+
// (`artifact_stale`); both travel with Gateway messages verbatim online, so these sentences are the
|
|
886
|
+
// owner fallback guidance. `unauthorized` re-authenticates through the session, hence its action.
|
|
887
|
+
unauthorized: guidance("Unauthorized.", "repair_or_adopt_session", 401),
|
|
888
|
+
artifact_stale: guidance("The requested contract artifact is not the current catalog artifact.", "answer_with_gap", 409),
|
|
889
|
+
// Host mint classifications. The CLI emits `invocation_authentication_unavailable` with the classification
|
|
890
|
+
// in its detail fact; rows without their own sentence share that code's moved sentence, while `next_action`
|
|
891
|
+
// is moved from `canonicalHostAuthenticationNextActions` (generated from `CEAL_HOST_AUTHENTICATION_POLICY`)
|
|
892
|
+
// and `retryable` from `hostAuthenticationFailureForCause` (`failure.nextAction == "retry"`). The three rows
|
|
893
|
+
// with their own sentences carry the retired policy remedies as Gateway-owned guidance. Statuses are the
|
|
894
|
+
// Gateway-owned mapping decided with the operator 2026-10-13. This covers every wire cause the maps at
|
|
895
|
+
// `host/invocation-authentication-command.ts:217-242` emit, through their policy classifications.
|
|
896
|
+
host_arguments_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 400),
|
|
897
|
+
host_configuration_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 500),
|
|
898
|
+
host_session_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 401),
|
|
899
|
+
// Moved from the retired `CEAL_HOST_AUTHENTICATION_POLICY` remedy: the
|
|
900
|
+
// missing exact-turn fact names the fresh Host turn, not a reinstall.
|
|
901
|
+
host_context_unavailable: guidance("The Host has not activated an invocation context for this session yet. Wait for the Host turn hook to register the session, then retry this command.", "retry", 401, true),
|
|
902
|
+
host_invocation_context_stale: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "retry", 401, true),
|
|
903
|
+
host_invocation_context_expired: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "retry", 401, true),
|
|
904
|
+
host_source_refused: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 403),
|
|
905
|
+
host_registration_credential_refused: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "ask_admin", 403),
|
|
906
|
+
host_mint_gateway_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "ask_admin", 503),
|
|
907
|
+
host_mint_gateway_rate_limited: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 429),
|
|
908
|
+
host_mint_gateway_refused: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 502),
|
|
909
|
+
host_mint_gateway_response_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 502),
|
|
910
|
+
// Moved from the retired `CEAL_HOST_AUTHENTICATION_POLICY` remedy: the
|
|
911
|
+
// producer-frame fault names the administrator repair step.
|
|
912
|
+
host_producer_frame_invalid: guidance("Ask an administrator to inspect and repair the producer frame, then start a fresh Host turn.", "ask_admin", 400),
|
|
913
|
+
// Moved from the retired `CEAL_HOST_AUTHENTICATION_POLICY` remedy: the
|
|
914
|
+
// sandboxed Relay socket names the run-alone retry.
|
|
915
|
+
host_relay_permission_denied: guidance("An agent sandbox denied the Host Relay socket; run `ceal` as the only command, with no pipe, redirect, or `&&`, so the Host allow rule runs it outside the sandbox.", "rerun_outside_agent_sandbox", 403),
|
|
916
|
+
host_relay_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 503),
|
|
917
|
+
host_mint_transient_retryable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "retry", 503, true),
|
|
918
|
+
host_mint_failed: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 500),
|
|
919
|
+
host_relay_failed: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 502),
|
|
920
|
+
host_refusal_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 500)
|
|
921
|
+
});
|
|
922
|
+
var FAILURE_GUIDANCE_ENTRY_KEYS = Object.freeze(["message", "next_action", "http_status", "retryable", "redaction_class"]);
|
|
846
923
|
|
|
847
|
-
// packages/ceal-client-protocol/src/
|
|
848
|
-
var
|
|
849
|
-
var
|
|
850
|
-
var
|
|
924
|
+
// packages/ceal-client-protocol/src/client-wire-contract.ts
|
|
925
|
+
var CEAL_CLIENT_WIRE_CONTRACT = Object.freeze(client_wire_contract_default);
|
|
926
|
+
var CEAL_CLIENT_DOCTOR_SCHEMA_VERSION = CEAL_CLIENT_WIRE_CONTRACT.doctor.schema_version;
|
|
927
|
+
var clientCommands = CEAL_CLIENT_WIRE_CONTRACT.client_commands;
|
|
928
|
+
var CEAL_CLIENT_DOCTOR_COMMAND = Object.freeze([...clientCommands.doctor]);
|
|
929
|
+
var CEAL_CLIENT_VERSION_COMMAND = Object.freeze([...clientCommands.version]);
|
|
930
|
+
var CEAL_CLIENT_ACCESS_GROUP_LIST_COMMAND = Object.freeze([...clientCommands.access_group_list]);
|
|
931
|
+
function deriveCealClientWireSchemaVersion(schema) {
|
|
932
|
+
if (!/^ceal\.[a-z][a-z0-9_]*$/u.test(schema.schema_name) || !schema.fields.includes("schema_version") || new Set(schema.fields).size !== schema.fields.length || schema.fields.some((field) => typeof field !== "string" || !/^[a-z][a-z0-9_]*$/u.test(field))) {
|
|
933
|
+
throw new TypeError("Client wire schema name or field set is invalid.");
|
|
934
|
+
}
|
|
935
|
+
const fields = schema.fields.filter((field) => field !== "schema_version").sort();
|
|
936
|
+
const hash8 = createHash2("sha256").update(cealCanonicalJson(fields), "utf8").digest("hex").slice(0, 8);
|
|
937
|
+
return `${schema.schema_name}.v1.${hash8}`;
|
|
938
|
+
}
|
|
939
|
+
var commandResolution = CEAL_CLIENT_WIRE_CONTRACT.command_resolution;
|
|
940
|
+
var commandResolutionAuthority = commandResolution.authority;
|
|
941
|
+
var commandResolutionSemanticOptions = commandResolution.semantic_options;
|
|
942
|
+
var CEAL_GATEWAY_COMMAND_RESOLUTION_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(commandResolution);
|
|
943
|
+
var CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES = CEAL_CLIENT_WIRE_CONTRACT.encoded_json_document.max_bytes;
|
|
944
|
+
var artifact = CEAL_CLIENT_WIRE_CONTRACT.artifact_descriptor;
|
|
945
|
+
var request = CEAL_CLIENT_WIRE_CONTRACT.command_request;
|
|
946
|
+
var response = CEAL_CLIENT_WIRE_CONTRACT.command_response;
|
|
947
|
+
var CEAL_OPERATION_CONTRACT_BUNDLE_SCHEMA_VERSION = deriveCealClientWireSchemaVersion({ schema_name: artifact.operation_contract_bundle_schema_name, fields: artifact.operation_contract_bundle_fields });
|
|
948
|
+
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(artifact);
|
|
949
|
+
var CEAL_VERIFIED_ARTIFACT_BINDING_SCHEMA_VERSION = deriveCealClientWireSchemaVersion({ schema_name: artifact.binding_schema_name, fields: artifact.binding_fields });
|
|
950
|
+
var CEAL_VERIFIED_ARTIFACT_KIND = artifact.kind;
|
|
951
|
+
var CEAL_VERIFIED_ARTIFACT_BINDING_KIND = artifact.binding_kind;
|
|
952
|
+
var CEAL_VERIFIED_ARTIFACT_MEDIA_TYPE = artifact.media_type;
|
|
953
|
+
var CEAL_VERIFIED_ARTIFACT_DIGEST_LENGTH = artifact.digest.length;
|
|
954
|
+
var CEAL_VERIFIED_ARTIFACT_DIGEST_PATTERN = artifact.digest.pattern;
|
|
955
|
+
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_LENGTH = artifact.catalog_revision.length;
|
|
956
|
+
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_PATTERN = artifact.catalog_revision.pattern;
|
|
957
|
+
var CEAL_VERIFIED_ARTIFACT_MAX_BYTES = CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES;
|
|
958
|
+
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_MAX_BYTES = artifact.envelope_max_bytes;
|
|
959
|
+
var CEAL_COMMAND_REQUEST_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(request);
|
|
960
|
+
var CEAL_COMMAND_REQUEST_MAX_BYTES = request.absolute_transport_max_bytes;
|
|
961
|
+
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_MAX_BYTES = response.bare_document_max_bytes;
|
|
962
|
+
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_OPTIONAL_FIELDS = Object.freeze([...response.bare_document_optional_fields]);
|
|
963
|
+
var CEAL_COMMAND_RESPONSE_DISCOVERY_COMMAND = response.discovery_command;
|
|
964
|
+
var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_SCHEMA_VERSION = deriveCealClientWireSchemaVersion({ schema_name: response.discovery_manifest_schema_name, fields: response.discovery_manifest_fields });
|
|
965
|
+
var CEAL_COMMAND_RESPONSE_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(response);
|
|
966
|
+
var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_DOCUMENT_FIELDS = Object.freeze([...response.discovery_manifest_document_fields]);
|
|
967
|
+
var CEAL_RESOURCE_STREAM_REQUEST_SCHEMA_VERSION = deriveCealClientWireSchemaVersion({ schema_name: CEAL_CLIENT_WIRE_CONTRACT.resource_stream.request_schema_name, fields: CEAL_CLIENT_WIRE_CONTRACT.resource_stream.request_fields });
|
|
968
|
+
var CEAL_RESOURCE_STREAM_REQUEST_FIELDS = Object.freeze([...CEAL_CLIENT_WIRE_CONTRACT.resource_stream.request_fields]);
|
|
969
|
+
|
|
970
|
+
// packages/ceal-client-protocol/src/operation-withheld-cause.ts
|
|
971
|
+
var CEAL_OPERATION_WITHHELD_CAUSES = vocabulary(["provider_permission", "access_level", "personal_authorization_required"]);
|
|
972
|
+
|
|
973
|
+
// packages/ceal-client-protocol/src/operation-contract-types.ts
|
|
974
|
+
var CEAL_JSON_SCHEMA_2020_12 = "https://json-schema.org/draft/2020-12/schema";
|
|
975
|
+
var CEAL_OPERATION_DESCRIPTOR_SCHEMA_VERSION = "ceal.operation_descriptor.v1";
|
|
976
|
+
var CEAL_OPERATION_ERROR_SCHEMA_VERSION = "ceal.operation_error.v1";
|
|
977
|
+
var CEAL_ARTIFACT_STAGE_TERMINALS = vocabulary(["chunk_accepted", "artifact_ready", "idempotency_replayed"]);
|
|
978
|
+
var CEAL_RETRY_AFTER_MAX_MS = 60 * 60 * 1e3;
|
|
979
|
+
var CEAL_JSON_SCHEMA_TYPES = vocabulary(["object", "array", "string", "integer", "number", "boolean", "null"]);
|
|
980
|
+
var OBSERVATION_POSTURES = vocabulary(["direct_readback", "approved_no_readback", "operator_mediated", "excluded"]);
|
|
981
|
+
var EFFECT_DOMAINS = vocabulary(["none", "gateway_state", "provider", "external_world"]);
|
|
982
|
+
var SAFETY_CLASSES = vocabulary(["routine", "sensitive", "high_impact"]);
|
|
983
|
+
var IDEMPOTENCY_CLASSES = vocabulary(["idempotent", "conditionally_idempotent", "non_idempotent"]);
|
|
984
|
+
var REVERSIBILITY_CLASSES = vocabulary(["none", "reversible", "compensatable", "irreversible"]);
|
|
985
|
+
var COMPENSATION_OWNERS = vocabulary(["none", "connector", "operator"]);
|
|
986
|
+
var CANCELLATION_BOUNDARIES = vocabulary(["before_admission", "before_handoff", "after_handoff_reconcile"]);
|
|
987
|
+
var HANDOFF_CLASSES = vocabulary(["not_applicable", "submission", "effect"]);
|
|
988
|
+
var AUTHORITATIVE_READBACK_CLASSES = vocabulary(["required", "unavailable"]);
|
|
989
|
+
var PROVIDER_READBACK_CLASSES = vocabulary(["required", "not_available", "not_applicable"]);
|
|
990
|
+
var RECONCILIATION_CLASSES = vocabulary(["none", "automatic", "operator"]);
|
|
991
|
+
var NO_READBACK_APPROVAL_CLASSES = vocabulary(["not_applicable", "explicit", "operator"]);
|
|
992
|
+
var REPLAY_PREREQUISITES = vocabulary(["never", "fresh_admission", "reconciled_non_application", "operator_approval"]);
|
|
993
|
+
var UNKNOWN_OUTCOMES = vocabulary(["reconcile", "operator_action", "excluded"]);
|
|
994
|
+
var RECOVERY_OWNERS = vocabulary(["gateway", "connector", "operator"]);
|
|
995
|
+
var CEAL_OPERATION_ADMISSION_STATES = vocabulary(["proposed", "refused", "admitted"]);
|
|
996
|
+
var CEAL_OPERATION_EXECUTION_STATES = vocabulary(["not_started", "started", "completed"]);
|
|
997
|
+
var CEAL_OPERATION_PROVIDER_HANDOFF_STATES = vocabulary(["not_offered", "offered", "accepted", "rejected", "unknown"]);
|
|
998
|
+
var CEAL_OPERATION_EFFECT_STATES = vocabulary(["none", "applied", "failed", "unknown", "compensated"]);
|
|
999
|
+
var CEAL_OPERATION_OBSERVATION_STATES = vocabulary(["not_started", "not_required", "pending", "observed", "unavailable"]);
|
|
1000
|
+
var CEAL_OPERATION_RECOVERY_KINDS = vocabulary(["none", "reconcile", "compensate", "operator_action"]);
|
|
1001
|
+
var CEAL_OPERATION_RECOVERY_STATUSES = vocabulary(["not_started", "active", "completed"]);
|
|
1002
|
+
var CEAL_OPERATION_PROVIDER_READBACK_AVAILABILITY = vocabulary(["not_started", "not_applicable", "not_available", "pending", "available"]);
|
|
1003
|
+
var CEAL_OPERATION_OFFERED_HANDOFF_STATES = vocabulary(["accepted", "rejected", "unknown"]);
|
|
1004
|
+
var CEAL_OPERATION_RECEIPT_AUTHORITY_KEYS = Object.freeze(["instance_ref", "access_group_ref", "principal_ref", "execution_subject_ref", "authority_revision_ref"]);
|
|
1005
|
+
var CEAL_OPERATION_APPROVAL_DISPOSITIONS = vocabulary([
|
|
1006
|
+
"approval_not_required_by_policy",
|
|
1007
|
+
"interactive_pending",
|
|
1008
|
+
"interactive_granted",
|
|
1009
|
+
"interactive_denied",
|
|
1010
|
+
"interactive_expired",
|
|
1011
|
+
"interactive_unavailable"
|
|
1012
|
+
]);
|
|
1013
|
+
|
|
1014
|
+
// packages/ceal-client-protocol/src/operation-access-class.ts
|
|
1015
|
+
var CEAL_OPERATION_ACCESS_CLASSES = vocabulary(["read", "write"]);
|
|
1016
|
+
|
|
1017
|
+
// packages/ceal-client-protocol/src/domain-vocabulary.ts
|
|
1018
|
+
var CEAL_CONNECTOR_STATUSES = vocabulary(["active", "disabled", "disconnected"]);
|
|
1019
|
+
var CEAL_CONNECTOR_CUSTODIES = vocabulary(["organization", "personal"]);
|
|
1020
|
+
var CEAL_ACCESS_LEVELS = vocabulary(["none", "read", "read_write"]);
|
|
1021
|
+
var CEAL_PERSONAL_ACCESS_CEILINGS = vocabulary(["disabled", "read", "read_write"]);
|
|
1022
|
+
var CEAL_TARGET_REF_PATTERN = "^[^\\p{Cc}]+$";
|
|
1023
|
+
var TARGET_REF = new RegExp(CEAL_TARGET_REF_PATTERN, "u");
|
|
1024
|
+
var CEAL_TARGET_VISIBILITIES = vocabulary(["public", "private"]);
|
|
1025
|
+
var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "runtime"]);
|
|
1026
|
+
var CEAL_PERSISTED_PRINCIPAL_KINDS = vocabulary(["human", "service", "runtime"]);
|
|
1027
|
+
var CEAL_ORGANIZATION_ROLES = vocabulary(["member", "admin"]);
|
|
1028
|
+
var CEAL_CALENDAR_PERIODS = vocabulary(["day", "week", "month"]);
|
|
1029
|
+
var LABEL_EXPECTATION = `public-safe display text of at most ${CEAL_PUBLIC_DISPLAY_NAME_MAX_BYTES} bytes`;
|
|
1030
|
+
|
|
1031
|
+
// packages/ceal-client-protocol/src/safe-json-budget.ts
|
|
1032
|
+
var SAFE_JSON_MIN_BYTES_PER_NODE = 4;
|
|
1033
|
+
function safeJsonNodeBudgetForBytes(maxBytes) {
|
|
1034
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < SAFE_JSON_MIN_BYTES_PER_NODE) {
|
|
1035
|
+
throw new RangeError(`safe-JSON byte cap is invalid: ${refusalFact("maxBytes", maxBytes, `at least ${SAFE_JSON_MIN_BYTES_PER_NODE} bytes`)}`);
|
|
1036
|
+
}
|
|
1037
|
+
return Math.floor(maxBytes / SAFE_JSON_MIN_BYTES_PER_NODE);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// packages/ceal-client-protocol/src/protocol-bounds.ts
|
|
1041
|
+
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES = 64 * 1024;
|
|
1042
|
+
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_NODES = safeJsonNodeBudgetForBytes(CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES);
|
|
1043
|
+
var OPERATION_SCHEMA_LITERAL_MAX_BYTES = CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES / 4;
|
|
1044
|
+
var CEAL_OPERATION_JSON_SCHEMA_BUDGET = Object.freeze({
|
|
1045
|
+
max_depth: 8,
|
|
1046
|
+
max_properties: 96,
|
|
1047
|
+
max_string_length: OPERATION_SCHEMA_LITERAL_MAX_BYTES / 4,
|
|
1048
|
+
max_alternatives: 8,
|
|
1049
|
+
max_enum_values: 64,
|
|
1050
|
+
max_literal_bytes: OPERATION_SCHEMA_LITERAL_MAX_BYTES,
|
|
1051
|
+
/** @basis our_policy -- bounds explanatory schema prose carried in one catalog. */
|
|
1052
|
+
max_description_characters: 1024,
|
|
1053
|
+
/** @basis our_policy -- bounds regular-expression compilation input from one schema. */
|
|
1054
|
+
max_pattern_characters: 256,
|
|
1055
|
+
/** @basis our_policy -- bounds traversal work inside one already byte-bounded schema literal. */
|
|
1056
|
+
max_literal_nodes: 512
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
// packages/ceal-client-protocol/src/operation-json-schema.ts
|
|
1060
|
+
var JSON_DESCRIPTION_CONTROL_CHARACTER = new RegExp("\\p{Cc}", "u");
|
|
1061
|
+
|
|
1062
|
+
// packages/ceal-client-protocol/src/operation-contract-validation.ts
|
|
1063
|
+
var CEAL_OPERATION_AUTHORITY_POLICY_KEYS = Object.freeze(["access_group", "instance_binding", "target", "admission_recheck"]);
|
|
1064
|
+
var MAX_OPERATION_INPUT_RESOLVER_BINDINGS = 96;
|
|
1065
|
+
var CEAL_OPERATION_DESCRIPTOR_REQUIRED_ENVELOPE_KEYS = Object.freeze(["schema_version", "id", "input_contract", "result_contract", "policy"]);
|
|
1066
|
+
var CEAL_OPERATION_DESCRIPTOR_OPTIONAL_ENVELOPE_KEYS = Object.freeze(["input_resolution", "summary"]);
|
|
1067
|
+
|
|
1068
|
+
// packages/ceal-client-protocol/src/operation-catalog.ts
|
|
1069
|
+
var CEAL_OPERATION_CATALOG_SCHEMA_VERSION = "ceal.catalog.v3";
|
|
1070
|
+
var CLIENT_AUDIENCES = Object.freeze(["ceal", "cealctl", "agent"]);
|
|
1071
|
+
var CLIENT_AUDIENCE_SET = new Set(CLIENT_AUDIENCES);
|
|
851
1072
|
|
|
852
1073
|
// packages/ceal-client-protocol/src/schema-literals.ts
|
|
853
1074
|
var CEAL_SCHEMA_LITERALS = [
|
|
@@ -876,12 +1097,12 @@ var CEAL_SCHEMA_LITERALS = [
|
|
|
876
1097
|
"ceal.enrollment_result.v1",
|
|
877
1098
|
"ceal.first_activation_causal_join.v1",
|
|
878
1099
|
"ceal.gateway_announcement_policy.v1",
|
|
879
|
-
|
|
1100
|
+
CEAL_GATEWAY_AUDIT_CALL_DETAIL_SCHEMA_VERSION,
|
|
880
1101
|
"ceal.gateway_audit_event.v1",
|
|
881
1102
|
"ceal.gateway_audit_readback.v1",
|
|
882
1103
|
"ceal.gateway_authorization_snapshot.v1",
|
|
883
1104
|
"ceal.gateway_cache_origin.v1",
|
|
884
|
-
|
|
1105
|
+
CEAL_GATEWAY_CALL_RESULT_SCHEMA_VERSION,
|
|
885
1106
|
CEAL_GATEWAY_COMMAND_RESOLUTION_SCHEMA_VERSION,
|
|
886
1107
|
"ceal.gateway_connector_route_failure.v1",
|
|
887
1108
|
"ceal.gateway_discovery.v1",
|
|
@@ -906,7 +1127,7 @@ var CEAL_SCHEMA_LITERALS = [
|
|
|
906
1127
|
"ceal.host_observation.v1",
|
|
907
1128
|
"ceal.host_registration_request.v1",
|
|
908
1129
|
"ceal.host_registration_result.v1",
|
|
909
|
-
|
|
1130
|
+
CEAL_OPERATION_DESCRIPTOR_SCHEMA_VERSION,
|
|
910
1131
|
"ceal.operation_error.v1",
|
|
911
1132
|
"ceal.operation_failure.v1",
|
|
912
1133
|
"ceal.operation_failure.compact.v1",
|
|
@@ -1011,11 +1232,22 @@ var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_JSON_SCHEMA = Object.freeze({
|
|
|
1011
1232
|
var CEAL_OPAQUE_HANDLE_KINDS = vocabulary(["target", "message", "thread", "artifact", "document", "block", "object"]);
|
|
1012
1233
|
var CEAL_OPAQUE_REF_KINDS = vocabulary([...CEAL_OPAQUE_HANDLE_KINDS, "result", "operation-receipt", "reply-receipt", "field", "notion-data-source"]);
|
|
1013
1234
|
|
|
1235
|
+
// packages/ceal-client-protocol/src/client-environment-generated.ts
|
|
1236
|
+
var CEAL_CLIENT_CONTEXT_OVERRIDE_ENVIRONMENTS = Object.freeze(["CEAL_GATEWAY_ORIGIN", "CEAL_GATEWAY_TRANSPORT_ORIGIN", "CEAL_ACCESS_TOKEN", "CEAL_CONTEXT_STORE_PATH"]);
|
|
1237
|
+
|
|
1238
|
+
// packages/ceal-client-protocol/src/client-process-contract.ts
|
|
1239
|
+
var CEAL_CLIENT_VERSION_SCHEMA_SUFFIX = ".version.v1";
|
|
1240
|
+
var CEAL_CLIENT_VERSION_SCHEMA_VERSION = `ceal${CEAL_CLIENT_VERSION_SCHEMA_SUFFIX}`;
|
|
1241
|
+
|
|
1014
1242
|
// packages/ceal-client-protocol/src/bare-document.ts
|
|
1015
1243
|
var REQUIRED_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_fields;
|
|
1016
1244
|
var OPTIONAL_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_optional_fields;
|
|
1017
1245
|
|
|
1246
|
+
// packages/ceal-client-protocol/src/actor-kind.ts
|
|
1247
|
+
var CEAL_ACTOR_KINDS = vocabulary(["human", "bot", "app", "unknown"]);
|
|
1248
|
+
|
|
1018
1249
|
// packages/ceal-client-protocol/src/resource-row.ts
|
|
1250
|
+
var CEAL_RESOURCE_ROW_TITLE_MATCH_DISPOSITIONS = vocabulary(["exact", "not_exact", "unknown"]);
|
|
1019
1251
|
var CEAL_RESOURCE_SEARCH_ORDERS = vocabulary(["relevance", "modified", "viewed", "created"]);
|
|
1020
1252
|
var CEAL_RESOURCE_SEARCH_DIRECTIONS = vocabulary(["ascending", "descending"]);
|
|
1021
1253
|
var CEAL_RESOURCE_SEARCH_FILTERS = vocabulary(["query", "modified_since", "since", "until", "authored_by_me", "authored_by", "include_replies", "kinds", "container_ref", "container_refs", "container_names", "addressed_to_me", "unread"]);
|
|
@@ -1028,6 +1260,8 @@ var CEAL_ACTIVITY_TIMESTAMP_BASES = vocabulary([
|
|
|
1028
1260
|
"resource_created",
|
|
1029
1261
|
"review_submitted",
|
|
1030
1262
|
"comment_created",
|
|
1263
|
+
"reaction_created",
|
|
1264
|
+
"ceal_write_completed",
|
|
1031
1265
|
"event_start",
|
|
1032
1266
|
"all_day_start",
|
|
1033
1267
|
"file_created",
|
|
@@ -1062,6 +1296,10 @@ var CEAL_ACTIVITY_EVENT_KINDS = vocabulary([
|
|
|
1062
1296
|
"github.review.submitted",
|
|
1063
1297
|
"github.issue_comment.created",
|
|
1064
1298
|
"github.review_comment.created",
|
|
1299
|
+
"github.reaction.created",
|
|
1300
|
+
"github.reaction.deleted",
|
|
1301
|
+
"github.pull_request.review_requested",
|
|
1302
|
+
"github.pull_request.review_request_removed",
|
|
1065
1303
|
"calendar.organized",
|
|
1066
1304
|
"calendar.accepted_invite",
|
|
1067
1305
|
"calendar.recorded",
|
|
@@ -1083,6 +1321,10 @@ var CEAL_ACTIVITY_EVENT_TIMESTAMP_BASIS_MAP = Object.freeze({
|
|
|
1083
1321
|
"github.pull_request.opened": "resource_created",
|
|
1084
1322
|
"github.issue.edited": "resource_modified",
|
|
1085
1323
|
"github.pull_request.edited": "resource_modified",
|
|
1324
|
+
"github.reaction.created": "reaction_created",
|
|
1325
|
+
"github.reaction.deleted": "ceal_write_completed",
|
|
1326
|
+
"github.pull_request.review_requested": "ceal_write_completed",
|
|
1327
|
+
"github.pull_request.review_request_removed": "ceal_write_completed",
|
|
1086
1328
|
"calendar.organized": "resource_created",
|
|
1087
1329
|
"drive.file.created": "file_created",
|
|
1088
1330
|
"drive.file.modified": "file_modified_by_me",
|
|
@@ -1099,12 +1341,14 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
|
|
|
1099
1341
|
next: { required: true, timestamp: false },
|
|
1100
1342
|
kind: { required: true, timestamp: false },
|
|
1101
1343
|
matched_kinds: { required: false, timestamp: false },
|
|
1344
|
+
title_match: { required: false, timestamp: false },
|
|
1102
1345
|
title: { required: true, timestamp: false },
|
|
1103
1346
|
container_label: { required: true, timestamp: false },
|
|
1104
1347
|
container_ref: { required: false, timestamp: false },
|
|
1105
1348
|
source_url: { required: false, timestamp: false },
|
|
1106
1349
|
source: { required: true, timestamp: false },
|
|
1107
1350
|
owner: { required: false, timestamp: false },
|
|
1351
|
+
actor_kind: { required: false, timestamp: false },
|
|
1108
1352
|
counterparts: { required: false, timestamp: false },
|
|
1109
1353
|
attachments: { required: false, timestamp: false },
|
|
1110
1354
|
created_at: { required: false, timestamp: true },
|
|
@@ -1112,9 +1356,18 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
|
|
|
1112
1356
|
modified_at: { required: false, timestamp: true },
|
|
1113
1357
|
modified_at_local: { required: false, timestamp: true },
|
|
1114
1358
|
viewed_by_me_at: { required: false, timestamp: true },
|
|
1359
|
+
modified_by_me_at: { required: false, timestamp: true },
|
|
1115
1360
|
own_ceal_write: { required: false, timestamp: false },
|
|
1116
1361
|
owned_by_me: { required: false, timestamp: false },
|
|
1362
|
+
mime_type: { required: false, timestamp: false },
|
|
1363
|
+
size_bytes: { required: false, timestamp: false },
|
|
1364
|
+
version: { required: false, timestamp: false },
|
|
1365
|
+
trashed: { required: false, timestamp: false },
|
|
1117
1366
|
visibility: { required: false, timestamp: false },
|
|
1367
|
+
archived: { required: false, timestamp: false },
|
|
1368
|
+
tag: { required: false, timestamp: false },
|
|
1369
|
+
labels: { required: false, timestamp: false },
|
|
1370
|
+
labels_incomplete: { required: false, timestamp: false },
|
|
1118
1371
|
start: { required: false, timestamp: false },
|
|
1119
1372
|
end: { required: false, timestamp: false },
|
|
1120
1373
|
status: { required: false, timestamp: false },
|
|
@@ -1187,18 +1440,29 @@ var CEAL_REFRESH_FAILURE_CODES = vocabulary([
|
|
|
1187
1440
|
// The serving gate answers this while the Gateway is still starting and the
|
|
1188
1441
|
// refresh was never attempted; the same attempt stays retryable. It is never
|
|
1189
1442
|
// stored by the session owner, only answered at the serving edge.
|
|
1190
|
-
|
|
1443
|
+
CEAL_GATEWAY_STARTING_FAILURE_CODE
|
|
1444
|
+
]);
|
|
1445
|
+
var CEAL_CLIENT_REVOKE_FAILURE_CODES = vocabulary([
|
|
1446
|
+
"refresh_invalid",
|
|
1447
|
+
"refresh_expired",
|
|
1448
|
+
"refresh_replayed",
|
|
1449
|
+
"refresh_revoked"
|
|
1191
1450
|
]);
|
|
1451
|
+
var CEAL_PERSONAL_CLIENT_ACCESS_TOKEN_PREFIX = "ceal_personal_";
|
|
1452
|
+
var CEAL_PERSONAL_CLIENT_REFRESH_TOKEN_PREFIX = "ceal_refresh_";
|
|
1453
|
+
var PERSONAL_CLIENT_TOKEN_SUFFIX_PATTERN = "[A-Za-z0-9_-]{43}";
|
|
1454
|
+
var CEAL_PERSONAL_CLIENT_ACCESS_TOKEN = new RegExp(`^${CEAL_PERSONAL_CLIENT_ACCESS_TOKEN_PREFIX}${PERSONAL_CLIENT_TOKEN_SUFFIX_PATTERN}$`, "u");
|
|
1455
|
+
var CEAL_PERSONAL_CLIENT_REFRESH_TOKEN = new RegExp(`^${CEAL_PERSONAL_CLIENT_REFRESH_TOKEN_PREFIX}${PERSONAL_CLIENT_TOKEN_SUFFIX_PATTERN}$`, "u");
|
|
1192
1456
|
|
|
1193
1457
|
// packages/ceal-client-protocol/src/request-validation-primitives.ts
|
|
1194
|
-
import { createHash as
|
|
1458
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1195
1459
|
var DIGEST = /^[a-f0-9]{64}$/u;
|
|
1196
1460
|
var CEAL_SHA256_DIGEST_PATTERN = DIGEST.source;
|
|
1197
1461
|
function isCealDigest(value) {
|
|
1198
1462
|
return typeof value === "string" && DIGEST.test(value);
|
|
1199
1463
|
}
|
|
1200
1464
|
function cealRequestSha256(value) {
|
|
1201
|
-
return
|
|
1465
|
+
return createHash3("sha256").update(cealCanonicalJson(value), "utf8").digest("hex");
|
|
1202
1466
|
}
|
|
1203
1467
|
function operationRecord(value, message) {
|
|
1204
1468
|
requireOperationRecord(value, message);
|
|
@@ -1214,40 +1478,71 @@ function requirePrefixedRef(value, prefix, message) {
|
|
|
1214
1478
|
// packages/ceal-client-protocol/src/request-authority.ts
|
|
1215
1479
|
var CEAL_REQUEST_SCHEMA_VERSION = "ceal.request.v1";
|
|
1216
1480
|
var CEAL_REQUEST_ARTIFACT_OWNERS = vocabulary(["gateway", "host", "connector"]);
|
|
1217
|
-
var
|
|
1481
|
+
var CEAL_HOST_INVOCATION_IDENTITY_KEYS = vocabulary(["host_session_ref", "host_turn_ref", "host_source_ref"]);
|
|
1218
1482
|
var CEAL_REQUEST_ARTIFACT_RETENTION_CLASSES = vocabulary(["request", "source", "result"]);
|
|
1219
1483
|
var CANCELLATION_STATES = vocabulary(["requested", "cancelled"]);
|
|
1220
|
-
var CEAL_REQUEST_KEYS = Object.freeze(["schema_version", "ceal_request_ref", "created_at", "authority"
|
|
1484
|
+
var CEAL_REQUEST_KEYS = Object.freeze(["schema_version", "ceal_request_ref", "created_at", "authority"]);
|
|
1485
|
+
var CEAL_REQUEST_COMMON_PROPERTIES = Object.freeze({
|
|
1486
|
+
schema_version: { type: "string", const: CEAL_REQUEST_SCHEMA_VERSION, maxLength: 32 },
|
|
1487
|
+
ceal_request_ref: { type: "string", minLength: 1, maxLength: 128, pattern: "^ceal-request:[A-Za-z0-9][A-Za-z0-9._:-]{0,114}$" },
|
|
1488
|
+
created_at: { type: "string", minLength: 1, maxLength: 64 },
|
|
1489
|
+
authority: {
|
|
1490
|
+
type: "object",
|
|
1491
|
+
maxProperties: 5,
|
|
1492
|
+
additionalProperties: false,
|
|
1493
|
+
properties: {
|
|
1494
|
+
instance_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1495
|
+
access_group_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1496
|
+
principal_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1497
|
+
execution_subject_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1498
|
+
authority_revision_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source }
|
|
1499
|
+
},
|
|
1500
|
+
required: ["instance_ref", "access_group_ref", "principal_ref", "execution_subject_ref", "authority_revision_ref"]
|
|
1501
|
+
}
|
|
1502
|
+
});
|
|
1503
|
+
var CEAL_REQUEST_HOST_PROPERTIES = Object.freeze({
|
|
1504
|
+
prompt_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1505
|
+
host_session_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1506
|
+
host_turn_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1507
|
+
host_input_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1508
|
+
host_source_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source }
|
|
1509
|
+
});
|
|
1510
|
+
var CEAL_REQUEST_CALLER_PROPERTIES = Object.freeze({
|
|
1511
|
+
caller_call_group_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source, description: "Gateway-minted call-group identity for a non-Host caller." },
|
|
1512
|
+
caller_call_input_sha256: { type: "string", minLength: 64, maxLength: 64, pattern: "^[a-f0-9]{64}$", description: "Commitment to the exact input for active non-Host identity-reuse checks; a migrated legacy call group commits to its legacy group key because the original input may no longer exist." }
|
|
1513
|
+
});
|
|
1514
|
+
var CEAL_REQUEST_OPTIONAL_PROPERTIES = Object.freeze({
|
|
1515
|
+
requester_timezone: { type: "string", minLength: 1, maxLength: 128, description: "IANA timezone observed for the requester on this Request." },
|
|
1516
|
+
device_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source, description: "Device that submitted this Request through its client session, including Host turns when the authenticated Host registration carries the device; absent for older records." }
|
|
1517
|
+
});
|
|
1518
|
+
var CEAL_REQUEST_HOST_REQUIRED_KEYS = Object.freeze([...CEAL_REQUEST_KEYS, ...CEAL_HOST_INVOCATION_IDENTITY_KEYS, "prompt_ref"]);
|
|
1519
|
+
var CEAL_REQUEST_CALLER_REQUIRED_KEYS = Object.freeze([...CEAL_REQUEST_KEYS, "caller_call_group_ref", "caller_call_input_sha256"]);
|
|
1221
1520
|
var CEAL_REQUEST_JSON_SCHEMA = {
|
|
1222
1521
|
type: "object",
|
|
1223
|
-
|
|
1522
|
+
description: "A Request has exactly one creation identity: the complete Host tuple and prompt reference, or a caller call-group identity. Caller Requests retain no prompt reference or prompt body.",
|
|
1523
|
+
maxProperties: 13,
|
|
1224
1524
|
additionalProperties: false,
|
|
1225
1525
|
properties: {
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1526
|
+
...CEAL_REQUEST_COMMON_PROPERTIES,
|
|
1527
|
+
...CEAL_REQUEST_HOST_PROPERTIES,
|
|
1528
|
+
...CEAL_REQUEST_CALLER_PROPERTIES,
|
|
1529
|
+
...CEAL_REQUEST_OPTIONAL_PROPERTIES
|
|
1530
|
+
},
|
|
1531
|
+
required: CEAL_REQUEST_KEYS,
|
|
1532
|
+
anyOf: [
|
|
1533
|
+
{
|
|
1230
1534
|
type: "object",
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
instance_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1235
|
-
access_group_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1236
|
-
principal_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1237
|
-
execution_subject_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1238
|
-
authority_revision_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source }
|
|
1239
|
-
},
|
|
1240
|
-
required: ["instance_ref", "access_group_ref", "principal_ref", "execution_subject_ref", "authority_revision_ref"]
|
|
1535
|
+
required: CEAL_REQUEST_HOST_REQUIRED_KEYS,
|
|
1536
|
+
properties: { ...CEAL_REQUEST_COMMON_PROPERTIES, ...CEAL_REQUEST_HOST_PROPERTIES, ...CEAL_REQUEST_OPTIONAL_PROPERTIES },
|
|
1537
|
+
additionalProperties: false
|
|
1241
1538
|
},
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
},
|
|
1250
|
-
required: CEAL_REQUEST_KEYS
|
|
1539
|
+
{
|
|
1540
|
+
type: "object",
|
|
1541
|
+
required: CEAL_REQUEST_CALLER_REQUIRED_KEYS,
|
|
1542
|
+
properties: { ...CEAL_REQUEST_COMMON_PROPERTIES, ...CEAL_REQUEST_CALLER_PROPERTIES, ...CEAL_REQUEST_OPTIONAL_PROPERTIES },
|
|
1543
|
+
additionalProperties: false
|
|
1544
|
+
}
|
|
1545
|
+
]
|
|
1251
1546
|
};
|
|
1252
1547
|
|
|
1253
1548
|
// packages/ceal-client-protocol/src/request-operands.ts
|
|
@@ -1358,16 +1653,12 @@ var CEAL_REQUEST_RUN_BATCH_JSON_SCHEMA = {
|
|
|
1358
1653
|
};
|
|
1359
1654
|
|
|
1360
1655
|
// packages/ceal-client-protocol/src/host-observation.ts
|
|
1361
|
-
var CEAL_HOST_INPUT_KINDS = vocabulary(["human", "host_automatic"]);
|
|
1362
|
-
function isCealHostInputKind(value) {
|
|
1363
|
-
return typeof value === "string" && CEAL_HOST_INPUT_KINDS.some((kind) => kind === value);
|
|
1364
|
-
}
|
|
1365
1656
|
var CEAL_HOST_OBSERVATION_REF_PREFIX = "host-observation:";
|
|
1366
1657
|
var HOST_OBSERVATION_PRODUCER = /^[a-z][a-z0-9]{0,31}$/u;
|
|
1367
1658
|
var HOST_OBSERVATION_DIGEST = /^[a-f0-9]{8,64}$/u;
|
|
1368
|
-
function cealHostObservationRef(producer,
|
|
1369
|
-
if (!HOST_OBSERVATION_PRODUCER.test(producer) || !HOST_OBSERVATION_DIGEST.test(
|
|
1370
|
-
return `${CEAL_HOST_OBSERVATION_REF_PREFIX}${producer}-${
|
|
1659
|
+
function cealHostObservationRef(producer, digest) {
|
|
1660
|
+
if (!HOST_OBSERVATION_PRODUCER.test(producer) || !HOST_OBSERVATION_DIGEST.test(digest)) invalid("Ceal Host observation reference parts are invalid.");
|
|
1661
|
+
return `${CEAL_HOST_OBSERVATION_REF_PREFIX}${producer}-${digest}`;
|
|
1371
1662
|
}
|
|
1372
1663
|
var HOST_TOKEN_OBSERVATION_BREAKDOWN_KEYS = vocabulary(["cached_input", "cache_write_input", "reasoning_output"]);
|
|
1373
1664
|
function createHostTokenObservationValue(counts, source) {
|
|
@@ -1407,14 +1698,14 @@ function safeCount(value) {
|
|
|
1407
1698
|
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
1408
1699
|
}
|
|
1409
1700
|
function validateCealHostObservationDraft(value) {
|
|
1410
|
-
const
|
|
1411
|
-
requirePrefixedRef(
|
|
1412
|
-
requireClosedValue(
|
|
1413
|
-
requireIsoTimestamp(
|
|
1414
|
-
assertHostObservationValue(
|
|
1701
|
+
const record4 = operationRecord(value, "Ceal Host observation draft is invalid.");
|
|
1702
|
+
requirePrefixedRef(record4.observation_ref, CEAL_HOST_OBSERVATION_REF_PREFIX, "Ceal Host observation reference is invalid.");
|
|
1703
|
+
requireClosedValue(record4.observation_kind, CEAL_HOST_OBSERVATION_KINDS, "Ceal Host observation kind is invalid.");
|
|
1704
|
+
requireIsoTimestamp(record4.observed_at, "Ceal Host observation time is invalid.");
|
|
1705
|
+
assertHostObservationValue(record4.value, record4.observation_kind);
|
|
1415
1706
|
}
|
|
1416
1707
|
var CEAL_REQUEST_MAX_OBSERVATION_VALUE_BYTES = 64 * 1024;
|
|
1417
|
-
var CEAL_HOST_OBSERVATION_KINDS = vocabulary(["prompt", "session", "turn", "model", "token", "
|
|
1708
|
+
var CEAL_HOST_OBSERVATION_KINDS = vocabulary(["prompt", "session", "turn", "model", "token", "timing"]);
|
|
1418
1709
|
function isCealHostPromptObservationValue(value) {
|
|
1419
1710
|
return isOperationRecord(value) && Object.keys(value).length === 1 && isCealDigest(value.body_sha256);
|
|
1420
1711
|
}
|
|
@@ -1537,16 +1828,41 @@ var CEAL_REQUEST_GRAPH_NODE_PREPARE_READINESS_JSON_SCHEMA = {
|
|
|
1537
1828
|
};
|
|
1538
1829
|
|
|
1539
1830
|
// packages/ceal-client-protocol/src/host-invocation-authentication.ts
|
|
1540
|
-
var
|
|
1541
|
-
var
|
|
1542
|
-
var
|
|
1543
|
-
var
|
|
1544
|
-
var
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1831
|
+
var hostInvocationAuthenticationContract = CEAL_CLIENT_WIRE_CONTRACT.host_invocation_authentication;
|
|
1832
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_REQUEST_SCHEMA = deriveCealClientWireSchemaVersion({ schema_name: hostInvocationAuthenticationContract.request_schema_name, fields: hostInvocationAuthenticationContract.request_fields });
|
|
1833
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_REQUEST_FIELDS = Object.freeze([...hostInvocationAuthenticationContract.request_fields]);
|
|
1834
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_SCHEMA = deriveCealClientWireSchemaVersion({ schema_name: hostInvocationAuthenticationContract.result_schema_name, fields: hostInvocationAuthenticationContract.result_fields });
|
|
1835
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_FIELDS = Object.freeze([...hostInvocationAuthenticationContract.result_fields]);
|
|
1836
|
+
var CEAL_HOST_AUTHENTICATION_BUDGET_PATTERN_SOURCE = "^(?:0|[1-9][0-9]{0,8})$";
|
|
1837
|
+
var CEAL_HOST_AUTHENTICATION_BUDGET_PATTERN = new RegExp(CEAL_HOST_AUTHENTICATION_BUDGET_PATTERN_SOURCE, "u");
|
|
1838
|
+
var CEAL_HOST_INVOCATION_CONTEXT_REQUEST_MAX_BYTES = 16 * 1024;
|
|
1839
|
+
var CEAL_HOST_INVOCATION_HUMAN_MESSAGE_WINDOW_MAX_BYTES = 2 * 1024;
|
|
1840
|
+
var CEAL_HOST_CONTEXT_TOKEN_PREFIX = "ceal_context_";
|
|
1841
|
+
function deriveCealHostInvocationNonceContract(contract = CEAL_CLIENT_WIRE_CONTRACT) {
|
|
1842
|
+
const clientNonce = contract.host_invocation_authentication.client_nonce;
|
|
1843
|
+
return {
|
|
1844
|
+
encoded_characters: clientNonce.encoded_characters,
|
|
1845
|
+
client_nonce_pattern_source: `^[${clientNonce.character_class_source}]{${clientNonce.encoded_characters}}$`,
|
|
1846
|
+
context_token_pattern_source: `^${CEAL_HOST_CONTEXT_TOKEN_PREFIX}[${clientNonce.character_class_source}]{${clientNonce.encoded_characters}}$`
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
var hostNonceContract = deriveCealHostInvocationNonceContract();
|
|
1850
|
+
var CEAL_HOST_NONCE_ENCODED_CHARACTERS = hostNonceContract.encoded_characters;
|
|
1851
|
+
var CEAL_HOST_CLIENT_NONCE_PATTERN = new RegExp(hostNonceContract.client_nonce_pattern_source, "u");
|
|
1852
|
+
var CEAL_HOST_CONTEXT_TOKEN_PATTERN = new RegExp(hostNonceContract.context_token_pattern_source, "u");
|
|
1853
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_MAX_BYTES = deriveCealHostInvocationAuthenticationResultMaxBytes();
|
|
1854
|
+
function deriveCealHostInvocationAuthenticationResultMaxBytes(contract = CEAL_CLIENT_WIRE_CONTRACT) {
|
|
1855
|
+
const hostAuthentication = contract.host_invocation_authentication;
|
|
1856
|
+
const values = {
|
|
1857
|
+
schema_version: deriveCealClientWireSchemaVersion({ schema_name: hostAuthentication.result_schema_name, fields: hostAuthentication.result_fields }),
|
|
1858
|
+
context_token: `${CEAL_HOST_CONTEXT_TOKEN_PREFIX}${"A".repeat(hostAuthentication.client_nonce.encoded_characters)}`,
|
|
1859
|
+
expires_at: (/* @__PURE__ */ new Date("+010000-01-01T00:00:00.000Z")).toISOString()
|
|
1860
|
+
};
|
|
1861
|
+
const fields = hostAuthentication.result_fields;
|
|
1862
|
+
if (fields.some((field) => !Object.hasOwn(values, field))) throw new TypeError("Host invocation result fields have no bounded sample value.");
|
|
1863
|
+
return new TextEncoder().encode(`${JSON.stringify(Object.fromEntries(fields.map((field) => [field, values[field]])))}
|
|
1549
1864
|
`).byteLength;
|
|
1865
|
+
}
|
|
1550
1866
|
var CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS = vocabulary([
|
|
1551
1867
|
"request_authority_unavailable",
|
|
1552
1868
|
"request_missing",
|
|
@@ -1570,6 +1886,7 @@ var CEAL_HOST_AUTHENTICATION_CAUSES = vocabulary([
|
|
|
1570
1886
|
"host_credential_refused",
|
|
1571
1887
|
"gateway_unavailable",
|
|
1572
1888
|
"gateway_rate_limited",
|
|
1889
|
+
"contract_mismatch",
|
|
1573
1890
|
"gateway_refused",
|
|
1574
1891
|
"gateway_response_invalid",
|
|
1575
1892
|
"producer_frame_invalid",
|
|
@@ -1584,19 +1901,23 @@ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
|
|
|
1584
1901
|
invalid_arguments: Object.freeze({ classification: "host_arguments_invalid", next_action: "answer_with_gap" }),
|
|
1585
1902
|
options_invalid: Object.freeze({ classification: "host_configuration_invalid", next_action: "answer_with_gap" }),
|
|
1586
1903
|
session_unavailable: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
|
|
1587
|
-
context_unavailable: Object.freeze({ classification: "host_context_unavailable", next_action: "
|
|
1588
|
-
context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "
|
|
1589
|
-
no_binding
|
|
1590
|
-
|
|
1591
|
-
|
|
1904
|
+
context_unavailable: Object.freeze({ classification: "host_context_unavailable", next_action: "retry" }),
|
|
1905
|
+
context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "retry" }),
|
|
1906
|
+
// `no_binding` now means the producer has no real current Host session. The
|
|
1907
|
+
// Host Relay never uses it for an uncreated Gateway Request; there is no
|
|
1908
|
+
// useful retry until the consumer is running inside a Host session.
|
|
1909
|
+
no_binding: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
|
|
1910
|
+
context_stale: Object.freeze({ classification: "host_invocation_context_stale", next_action: "retry" }),
|
|
1911
|
+
context_expired: Object.freeze({ classification: "host_invocation_context_expired", next_action: "retry" }),
|
|
1592
1912
|
host_source_refused: Object.freeze({ classification: "host_source_refused", next_action: "answer_with_gap" }),
|
|
1593
1913
|
host_credential_refused: Object.freeze({ classification: "host_registration_credential_refused", next_action: "ask_admin" }),
|
|
1594
1914
|
gateway_unavailable: Object.freeze({ classification: "host_mint_gateway_unavailable", next_action: "ask_admin" }),
|
|
1595
1915
|
gateway_rate_limited: Object.freeze({ classification: "host_mint_gateway_rate_limited", next_action: "answer_with_gap" }),
|
|
1916
|
+
contract_mismatch: Object.freeze({ classification: "contract_mismatch", next_action: "upgrade_ceal_client" }),
|
|
1596
1917
|
gateway_refused: Object.freeze({ classification: "host_mint_gateway_refused", next_action: "answer_with_gap" }),
|
|
1597
1918
|
gateway_response_invalid: Object.freeze({ classification: "host_mint_gateway_response_invalid", next_action: "answer_with_gap" }),
|
|
1598
|
-
producer_frame_invalid: Object.freeze({ classification: "host_producer_frame_invalid", next_action: "ask_admin"
|
|
1599
|
-
relay_permission_denied: Object.freeze({ classification: "host_relay_permission_denied", next_action: "rerun_outside_agent_sandbox"
|
|
1919
|
+
producer_frame_invalid: Object.freeze({ classification: "host_producer_frame_invalid", next_action: "ask_admin" }),
|
|
1920
|
+
relay_permission_denied: Object.freeze({ classification: "host_relay_permission_denied", next_action: "rerun_outside_agent_sandbox" }),
|
|
1600
1921
|
relay_unavailable: Object.freeze({ classification: "host_relay_unavailable", next_action: "answer_with_gap" }),
|
|
1601
1922
|
transient_retryable: Object.freeze({ classification: "host_mint_transient_retryable", next_action: "retry" }),
|
|
1602
1923
|
mint_failed: Object.freeze({ classification: "host_mint_failed", next_action: "answer_with_gap" }),
|
|
@@ -1611,9 +1932,6 @@ var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
|
|
|
1611
1932
|
"hook_invoked",
|
|
1612
1933
|
"registration_written",
|
|
1613
1934
|
"hook_failed",
|
|
1614
|
-
"watcher_turn_observed",
|
|
1615
|
-
"current_prompt_registered",
|
|
1616
|
-
"current_prompt_registration_failed",
|
|
1617
1935
|
"activation_recorded",
|
|
1618
1936
|
"activation_failed",
|
|
1619
1937
|
"watcher_scan_failed",
|
|
@@ -1636,6 +1954,9 @@ var CEAL_UNREAD_REASONS = vocabulary(["blocked", "unavailable", "too_large", "un
|
|
|
1636
1954
|
var REQUIRED_FIELDS2 = Object.freeze(["schema_version", "operation_id", "path", "sha256"]);
|
|
1637
1955
|
var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "items_path", "row_count", "result_complete", "coverage", "local_result_root", "model_disclosure", "continue_argv", "how_to_read"]);
|
|
1638
1956
|
|
|
1957
|
+
// packages/ceal-client-protocol/src/discovery-manifest.ts
|
|
1958
|
+
var DISCOVERY_MANIFEST_KEYS = CEAL_CLIENT_WIRE_CONTRACT.command_response.discovery_manifest_fields;
|
|
1959
|
+
|
|
1639
1960
|
// packages/ceal-client-protocol/src/gateway-cache-origin-validation.ts
|
|
1640
1961
|
var CEAL_MAX_CACHE_ORIGIN_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1641
1962
|
|
|
@@ -1694,6 +2015,7 @@ var ADMIN_UNAVAILABLE_REASONS = vocabulary([
|
|
|
1694
2015
|
"legacy",
|
|
1695
2016
|
"invalid_observation",
|
|
1696
2017
|
"redacted",
|
|
2018
|
+
"organization_setting_disabled",
|
|
1697
2019
|
"owner_unavailable",
|
|
1698
2020
|
"unit_unknown",
|
|
1699
2021
|
"retention_gap",
|
|
@@ -1723,15 +2045,13 @@ var ADMIN_APPROVAL_DISPOSITIONS = CEAL_OPERATION_APPROVAL_DISPOSITIONS;
|
|
|
1723
2045
|
var ADMIN_APPROVAL_POSTURES = vocabulary([...ADMIN_APPROVAL_DISPOSITIONS, "not_applicable"]);
|
|
1724
2046
|
var ADMIN_LINEAGE_NODE_KINDS = vocabulary(["source_read", "synthesis_preview", "approval", "effect", "readback"]);
|
|
1725
2047
|
|
|
1726
|
-
// packages/ceal-client-protocol/src/actor-kind.ts
|
|
1727
|
-
var CEAL_ACTOR_KINDS = vocabulary(["human", "bot", "app", "unknown"]);
|
|
1728
|
-
|
|
1729
2048
|
// packages/ceal-client-protocol/src/device-enrollment.ts
|
|
1730
2049
|
var CEAL_DEVICE_ENROLLMENT_POLL_FAILURE_CODES = Object.freeze(["unsupported_feature", "recovery_required", "expired", "denied"]);
|
|
1731
2050
|
var POLL_RESULT_KEYS = Object.freeze(["retry_after_ms", "schema_version", "status"]);
|
|
1732
2051
|
|
|
1733
2052
|
// packages/ceal-client-protocol/src/personal-client-session.ts
|
|
1734
2053
|
var REFRESH_FAILURE_CODES = new Set(CEAL_REFRESH_FAILURE_CODES);
|
|
2054
|
+
var REVOKE_FAILURE_CODES = new Set(CEAL_CLIENT_REVOKE_FAILURE_CODES);
|
|
1735
2055
|
|
|
1736
2056
|
// packages/ceal-client-protocol/src/index.ts
|
|
1737
2057
|
var MAX_REQUEST_BYTES = 32 * 1024;
|
|
@@ -1754,6 +2074,125 @@ function waitForAbort(signal, milliseconds) {
|
|
|
1754
2074
|
});
|
|
1755
2075
|
}
|
|
1756
2076
|
|
|
2077
|
+
// host/secure-file.ts
|
|
2078
|
+
import { execFile } from "node:child_process";
|
|
2079
|
+
import { constants } from "node:fs";
|
|
2080
|
+
import { lstat, open } from "node:fs/promises";
|
|
2081
|
+
import { dirname, win32 as win323 } from "node:path";
|
|
2082
|
+
import { fileURLToPath } from "node:url";
|
|
2083
|
+
var SecureFileCustodyError = class extends Error {
|
|
2084
|
+
name = "SecureFileCustodyError";
|
|
2085
|
+
reason;
|
|
2086
|
+
constructor(reason, message) {
|
|
2087
|
+
super(message);
|
|
2088
|
+
this.reason = reason;
|
|
2089
|
+
}
|
|
2090
|
+
get code() {
|
|
2091
|
+
if (this.reason === "reparse_point") return "ELOOP";
|
|
2092
|
+
if (this.reason === "not_found") return "ENOENT";
|
|
2093
|
+
if (this.reason === "too_large") return "secure_file_too_large";
|
|
2094
|
+
if (this.reason === "already_exists") return "EEXIST";
|
|
2095
|
+
return this.reason === "not_private" ? "secure_file_not_private" : "secure_file_unavailable";
|
|
2096
|
+
}
|
|
2097
|
+
};
|
|
2098
|
+
var WINDOWS_SECUREFS_REFUSAL_SCHEMA = "ceal.host_securefs_refusal.v1";
|
|
2099
|
+
var WINDOWS_SECUREFS_TIMEOUT_MS = 1e4;
|
|
2100
|
+
var WINDOWS_SECUREFS_DIAGNOSTIC_BYTES = 4096;
|
|
2101
|
+
function secureOpenFlags(flags, platform) {
|
|
2102
|
+
if (platform === "win32") return flags;
|
|
2103
|
+
const nofollow = constants.O_NOFOLLOW;
|
|
2104
|
+
if (typeof nofollow === "number" && nofollow !== 0) return flags | nofollow;
|
|
2105
|
+
throw new Error("Secure file reads require O_NOFOLLOW on this platform.");
|
|
2106
|
+
}
|
|
2107
|
+
async function openNoFollow(path, flags = constants.O_RDONLY, mode, platform = process.platform, _custody = {}) {
|
|
2108
|
+
if (platform !== "win32") return await open(path, secureOpenFlags(flags, platform), mode);
|
|
2109
|
+
return await openWindowsIdentityScreened(path, flags, mode);
|
|
2110
|
+
}
|
|
2111
|
+
async function openWindowsIdentityScreened(path, flags, mode) {
|
|
2112
|
+
const handle = await open(path, flags, mode);
|
|
2113
|
+
try {
|
|
2114
|
+
const first = await handle.stat();
|
|
2115
|
+
if (!first.isFile()) throw new SecureFileCustodyError("not_private", "The Windows Host file is not a regular file.");
|
|
2116
|
+
await assertWindowsLinkFree(path, first);
|
|
2117
|
+
await assertWindowsIdentityStable(handle, first, path);
|
|
2118
|
+
return handle;
|
|
2119
|
+
} catch (error) {
|
|
2120
|
+
await handle.close();
|
|
2121
|
+
throw error;
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
async function assertWindowsLinkFree(path, first) {
|
|
2125
|
+
const entry = await lstat(path);
|
|
2126
|
+
if (entry.isSymbolicLink() || entry.dev !== first.dev || entry.ino !== first.ino) {
|
|
2127
|
+
throw new SecureFileCustodyError("reparse_point", "The Windows Host file path resolves through a reparse point.");
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
async function assertWindowsIdentityStable(handle, first, path) {
|
|
2131
|
+
const second = await handle.stat();
|
|
2132
|
+
if (second.dev !== first.dev || second.ino !== first.ino || second.size !== first.size || second.mtimeMs !== first.mtimeMs) {
|
|
2133
|
+
throw new SecureFileCustodyError("unavailable", "The Windows Host file changed while its identity was checked.");
|
|
2134
|
+
}
|
|
2135
|
+
const entry = await lstat(path);
|
|
2136
|
+
if (entry.isSymbolicLink()) throw new SecureFileCustodyError("reparse_point", "The Windows Host file path resolves through a reparse point.");
|
|
2137
|
+
if (entry.dev !== second.dev || entry.ino !== second.ino) throw new SecureFileCustodyError("unavailable", "The Windows Host file path no longer names the opened file.");
|
|
2138
|
+
}
|
|
2139
|
+
async function readPrivateFileWithSecurefs(path, maxBytes, custody = {}) {
|
|
2140
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) throw new TypeError("The securefs read bound must be a positive safe integer.");
|
|
2141
|
+
const result = await runWindowsSecurefs(["internal", "securefs", "read-private-file", path, String(maxBytes)], maxBytes, custody);
|
|
2142
|
+
if (result.exit_code === 0 && result.stderr.length === 0 && result.stdout.length <= maxBytes) return result.stdout;
|
|
2143
|
+
if (result.stdout.length !== 0) throw new SecureFileCustodyError("unavailable", "The Windows Host securefs command returned bytes with a refusal.");
|
|
2144
|
+
if (result.exit_code === 0) throw new SecureFileCustodyError("unavailable", "The Windows Host securefs command exceeded its output contract.");
|
|
2145
|
+
throw securefsRefusal(result.stderr);
|
|
2146
|
+
}
|
|
2147
|
+
async function runWindowsSecurefs(args, maxBytes, custody) {
|
|
2148
|
+
if (custody.run_securefs_command !== void 0) return await custody.run_securefs_command(args);
|
|
2149
|
+
const executable = custody.securefs_executable ?? resolveInstalledHostExecutable();
|
|
2150
|
+
const processArgs = [...custody.securefs_args_prefix ?? [], ...args];
|
|
2151
|
+
return await new Promise((resolve, reject) => {
|
|
2152
|
+
execFile(executable, processArgs, { encoding: "buffer", timeout: WINDOWS_SECUREFS_TIMEOUT_MS, maxBuffer: maxBytes + WINDOWS_SECUREFS_DIAGNOSTIC_BYTES }, (error, stdout, stderr) => {
|
|
2153
|
+
const exitCode = error === null ? 0 : typeof error.code === "number" ? error.code : null;
|
|
2154
|
+
if (exitCode === null) {
|
|
2155
|
+
reject(new SecureFileCustodyError("unavailable", "The installed Windows Host securefs command could not run."));
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
resolve({ exit_code: exitCode, stdout: Buffer.from(stdout), stderr: Buffer.from(stderr) });
|
|
2159
|
+
});
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
function securefsRefusal(stderr) {
|
|
2163
|
+
let value;
|
|
2164
|
+
try {
|
|
2165
|
+
value = JSON.parse(stderr.toString("utf8"));
|
|
2166
|
+
} catch {
|
|
2167
|
+
return new SecureFileCustodyError("unavailable", "The Windows Host securefs command returned an unreadable refusal.");
|
|
2168
|
+
}
|
|
2169
|
+
if (!isRecord(value) || value.schema_version !== WINDOWS_SECUREFS_REFUSAL_SCHEMA || typeof value.code !== "string") return new SecureFileCustodyError("unavailable", "The Windows Host securefs command returned an unreadable refusal.");
|
|
2170
|
+
if (value.code === "reparse_point") return new SecureFileCustodyError("reparse_point", "The Windows Host securefs file is a reparse point.");
|
|
2171
|
+
if (value.code === "not_found") return new SecureFileCustodyError("not_found", "The Windows Host securefs file was not found.");
|
|
2172
|
+
if (value.code === "too_large") return new SecureFileCustodyError("too_large", "The Windows Host securefs file exceeds its read bound.");
|
|
2173
|
+
if (value.code === "already_exists") return new SecureFileCustodyError("already_exists", "The Windows Host securefs target already exists.");
|
|
2174
|
+
if (value.code === "not_private") return new SecureFileCustodyError("not_private", "The Windows Host securefs file is not private to the current user.");
|
|
2175
|
+
return new SecureFileCustodyError("unavailable", "The Windows Host securefs command refused the file read.");
|
|
2176
|
+
}
|
|
2177
|
+
function resolveInstalledHostExecutable() {
|
|
2178
|
+
const privateRuntimeRoot = dirname(fileURLToPath(import.meta.url));
|
|
2179
|
+
return win323.join(privateRuntimeRoot, "..", "..", "bin", "ceal-host.exe");
|
|
2180
|
+
}
|
|
2181
|
+
function isRecord(value) {
|
|
2182
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2183
|
+
}
|
|
2184
|
+
function isOwnedBy(uid, expectedUid) {
|
|
2185
|
+
return Number.isSafeInteger(uid) && Number.isSafeInteger(expectedUid) && uid === expectedUid;
|
|
2186
|
+
}
|
|
2187
|
+
function isSelfOwned(uid, platform = process.platform, _handle) {
|
|
2188
|
+
if (platform === "win32" || typeof process.geteuid !== "function") return false;
|
|
2189
|
+
return isOwnedBy(uid, process.geteuid());
|
|
2190
|
+
}
|
|
2191
|
+
function isPrivateFileMode(mode, platform = process.platform, _handle) {
|
|
2192
|
+
if (platform === "win32") return false;
|
|
2193
|
+
return (mode & 63) === 0;
|
|
2194
|
+
}
|
|
2195
|
+
|
|
1757
2196
|
// host/producer-service.ts
|
|
1758
2197
|
var SAFE_REF2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
1759
2198
|
var HOST_PRODUCER_SERVE_COMMAND_PREFIX = ["serve", "--config"];
|
|
@@ -1766,18 +2205,19 @@ var HostProducerServiceError = class extends Error {
|
|
|
1766
2205
|
}
|
|
1767
2206
|
};
|
|
1768
2207
|
var HOST_PRODUCER_CONFIG_KEYS = ["schema_version", "host_source_ref", "producer_installation_ref", "producer_epoch", "outbox_dir", "relay_endpoint"];
|
|
2208
|
+
var HOST_PRODUCER_CONFIG_ENDPOINT_KEY = "relay_endpoint";
|
|
1769
2209
|
var HOST_PRODUCER_TIMING_DEFAULTS = Object.freeze({ reconcile_interval_ms: 250, retry_backoff_ms: 1e3, ipc_timeout_ms: 5e3 });
|
|
1770
2210
|
function hostProducerExtraPath(extras, key) {
|
|
1771
2211
|
const value = extras[key];
|
|
1772
|
-
if (value === void 0) throw hostProducerError("invalid_config");
|
|
2212
|
+
if (value === void 0) throw hostProducerError("invalid_config", refusalFact(key, "missing", "an absolute path"));
|
|
1773
2213
|
return value;
|
|
1774
2214
|
}
|
|
1775
2215
|
function parseHostProducerServeArgs(args, platform = process.platform) {
|
|
1776
2216
|
return parseProducerServeArgs(args, platform, hostProducerError);
|
|
1777
2217
|
}
|
|
1778
|
-
async function readHostProducerConfig(descriptor, configPath, platform = process.platform) {
|
|
1779
|
-
const config = decodeHostProducerConfigText(descriptor, await readProducerConfigText(configPath, platform, hostProducerError), platform);
|
|
1780
|
-
const path = platform === "win32" ?
|
|
2218
|
+
async function readHostProducerConfig(descriptor, configPath, platform = process.platform, custody = {}) {
|
|
2219
|
+
const config = decodeHostProducerConfigText(descriptor, await readProducerConfigText(configPath, platform, hostProducerError, custody), platform);
|
|
2220
|
+
const path = platform === "win32" ? win324 : posix3;
|
|
1781
2221
|
const root = path.resolve(config.outbox_dir, "../../../../..");
|
|
1782
2222
|
try {
|
|
1783
2223
|
assertHostProducerSourcePaths(root, config, descriptor.producer_id, platform);
|
|
@@ -1806,15 +2246,31 @@ function parseProducerServeArgs(args, platform, error) {
|
|
|
1806
2246
|
assertProducerAbsolutePath(args[2], platform, "config", error);
|
|
1807
2247
|
return args[2];
|
|
1808
2248
|
}
|
|
1809
|
-
async function readProducerConfigText(configPath, platform, error) {
|
|
1810
|
-
assertProducerAbsolutePath(configPath, platform, "config", error);
|
|
2249
|
+
async function readProducerConfigText(configPath, platform, error, custody) {
|
|
2250
|
+
assertProducerAbsolutePath(configPath, process.platform, "config", error);
|
|
2251
|
+
if (platform === "win32") {
|
|
2252
|
+
let bytes;
|
|
2253
|
+
try {
|
|
2254
|
+
bytes = await readPrivateFileWithSecurefs(configPath, CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES, custody);
|
|
2255
|
+
} catch (cause) {
|
|
2256
|
+
if (cause instanceof SecureFileCustodyError && cause.reason === "not_private") throw error("config_not_private");
|
|
2257
|
+
if (cause instanceof SecureFileCustodyError && cause.reason === "too_large") throw error("invalid_config", refusalFact("config_bytes", "over_limit", `at most ${CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES}`));
|
|
2258
|
+
if (cause instanceof SecureFileCustodyError && cause.reason === "reparse_point") throw error("invalid_config");
|
|
2259
|
+
throw error("config_unavailable");
|
|
2260
|
+
}
|
|
2261
|
+
if (bytes.byteLength === 0) throw error("invalid_config", refusalFact("config_bytes", 0, `between 1 and ${CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES}`));
|
|
2262
|
+
try {
|
|
2263
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
2264
|
+
} catch {
|
|
2265
|
+
throw error("invalid_config");
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
1811
2268
|
const pathStat = await configPathStat(configPath, error);
|
|
1812
2269
|
validateConfigFile(pathStat, platform, error);
|
|
1813
|
-
const
|
|
1814
|
-
const handle = await openConfigFile(configPath, constants.O_RDONLY | noFollow, error);
|
|
2270
|
+
const handle = await openConfigFile(configPath, platform, error, custody);
|
|
1815
2271
|
try {
|
|
1816
2272
|
const opened = await handle.stat();
|
|
1817
|
-
validateConfigFile(opened, platform, error);
|
|
2273
|
+
validateConfigFile(opened, platform, error, handle);
|
|
1818
2274
|
validateOpenedFile(opened, pathStat, platform, error);
|
|
1819
2275
|
return await readBoundedConfig(handle, opened.size, error);
|
|
1820
2276
|
} finally {
|
|
@@ -1822,8 +2278,9 @@ async function readProducerConfigText(configPath, platform, error) {
|
|
|
1822
2278
|
}
|
|
1823
2279
|
}
|
|
1824
2280
|
function decodeProducerConfigCore(text, options) {
|
|
2281
|
+
const ownership = producerConfigKeyOwnership(options.keys, options.platform);
|
|
1825
2282
|
const value = parseCanonicalJson(text, options.error);
|
|
1826
|
-
if (!
|
|
2283
|
+
if (!isRecord2(value) || !hasConfigKeys(value, ownership.required, ownership.tolerated) || value.schema_version !== options.schema) throw options.error("invalid_config", refusalFacts(producerShapeFacts(value, { schema: options.schema, keys: ownership.required, tolerated: ownership.tolerated })));
|
|
1827
2284
|
const hostSourceRef = typeof value.host_source_ref === "string" ? value.host_source_ref : "";
|
|
1828
2285
|
const installationRef = typeof value.producer_installation_ref === "string" ? value.producer_installation_ref : "";
|
|
1829
2286
|
const producerEpoch = validateIdentity(hostSourceRef, installationRef, value.producer_epoch, options.error);
|
|
@@ -1841,7 +2298,7 @@ function decodeProducerConfigCore(text, options) {
|
|
|
1841
2298
|
producer_installation_ref: installationRef,
|
|
1842
2299
|
producer_epoch: producerEpoch,
|
|
1843
2300
|
outbox_dir: value.outbox_dir,
|
|
1844
|
-
relay_endpoint: value
|
|
2301
|
+
relay_endpoint: producerConfigEndpoint(value),
|
|
1845
2302
|
reconcile_interval_ms: intervals[0],
|
|
1846
2303
|
retry_backoff_ms: intervals[1],
|
|
1847
2304
|
ipc_timeout_ms: intervals[2]
|
|
@@ -1858,8 +2315,15 @@ async function serveProducer(runtimeOptions, timing, signal, drain, wait = (mill
|
|
|
1858
2315
|
if (!signal.aborted) await wait(delay, signal);
|
|
1859
2316
|
}
|
|
1860
2317
|
}
|
|
2318
|
+
function producerConfigKeyOwnership(keys, platform) {
|
|
2319
|
+
if (platform !== "win32") return { required: keys, tolerated: [] };
|
|
2320
|
+
return { required: keys.filter((key) => key !== HOST_PRODUCER_CONFIG_ENDPOINT_KEY), tolerated: [HOST_PRODUCER_CONFIG_ENDPOINT_KEY] };
|
|
2321
|
+
}
|
|
2322
|
+
function producerConfigEndpoint(value) {
|
|
2323
|
+
return typeof value.relay_endpoint === "string" ? value.relay_endpoint : "";
|
|
2324
|
+
}
|
|
1861
2325
|
function assertProducerRelayEndpoint(value, platform, error) {
|
|
1862
|
-
if (platform === "win32"
|
|
2326
|
+
if (platform === "win32") return;
|
|
1863
2327
|
assertProducerAbsolutePath(value, platform, "runtime", error);
|
|
1864
2328
|
}
|
|
1865
2329
|
function assertProducerAbsolutePath(value, platform, kind, error) {
|
|
@@ -1867,26 +2331,27 @@ function assertProducerAbsolutePath(value, platform, kind, error) {
|
|
|
1867
2331
|
}
|
|
1868
2332
|
async function configPathStat(configPath, error) {
|
|
1869
2333
|
try {
|
|
1870
|
-
return await
|
|
2334
|
+
return await lstat2(configPath);
|
|
1871
2335
|
} catch {
|
|
1872
2336
|
throw error("config_unavailable");
|
|
1873
2337
|
}
|
|
1874
2338
|
}
|
|
1875
|
-
function validateConfigFile(value, platform, error) {
|
|
2339
|
+
function validateConfigFile(value, platform, error, handle) {
|
|
1876
2340
|
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" }]));
|
|
1877
2341
|
if (value.size <= 0 || value.size > CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES) throw error("invalid_config", refusalFact("config_bytes", value.size, `between 1 and ${CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES}`));
|
|
1878
|
-
if (
|
|
2342
|
+
if (!isSelfOwned(value.uid, platform, handle) || !isPrivateFileMode(value.mode, platform, handle)) throw error("config_not_private", refusalFacts([{ name: "platform", value: platform, expected: "a platform with verifiable owner and private-mode facts" }, { name: "config_uid", value: value.uid, expected: "the current user" }, { name: "config_mode", value: value.mode, expected: "private to its owner" }]));
|
|
1879
2343
|
}
|
|
1880
|
-
async function openConfigFile(configPath,
|
|
2344
|
+
async function openConfigFile(configPath, platform, error, custody) {
|
|
1881
2345
|
try {
|
|
1882
|
-
return await
|
|
1883
|
-
} catch {
|
|
2346
|
+
return await openNoFollow(configPath, constants2.O_RDONLY, void 0, platform, custody);
|
|
2347
|
+
} catch (error_) {
|
|
2348
|
+
if (error_ instanceof SecureFileCustodyError) throw error(error_.reason === "not_private" ? "config_not_private" : "invalid_config");
|
|
1884
2349
|
throw error("config_unavailable");
|
|
1885
2350
|
}
|
|
1886
2351
|
}
|
|
1887
2352
|
function validateOpenedFile(opened, pathStat, platform, error) {
|
|
1888
2353
|
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" }]));
|
|
1889
|
-
if (
|
|
2354
|
+
if (opened.dev !== pathStat.dev || opened.ino !== pathStat.ino) throw error("config_unavailable", refusalFacts([{ name: "platform", value: platform, expected: "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) }]));
|
|
1890
2355
|
}
|
|
1891
2356
|
async function readBoundedConfig(handle, expectedBytes, error) {
|
|
1892
2357
|
const buffer = Buffer.allocUnsafe(expectedBytes + 1);
|
|
@@ -1918,18 +2383,43 @@ function firstDifferenceIndex(left, right) {
|
|
|
1918
2383
|
for (let index = 0; index < shared; index += 1) if (left[index] !== right[index]) return index;
|
|
1919
2384
|
return shared;
|
|
1920
2385
|
}
|
|
2386
|
+
function producerShapeFacts(value, options) {
|
|
2387
|
+
if (!isRecord2(value)) return [{ name: "config", value: "wrong_type", expected: "a record" }];
|
|
2388
|
+
const tolerated = options.tolerated ?? [];
|
|
2389
|
+
const allowed = [...options.keys, ...tolerated, ...Object.keys(HOST_PRODUCER_TIMING_DEFAULTS)];
|
|
2390
|
+
const facts = [
|
|
2391
|
+
...Object.keys(value).filter((key) => !allowed.includes(key)).map((key) => ({ name: key, value: "unknown", expected: `one of ${[...options.keys, ...tolerated].join(", ")}` })),
|
|
2392
|
+
...options.keys.filter((key) => !Object.hasOwn(value, key)).map((key) => ({ name: key, value: "missing", expected: "present" }))
|
|
2393
|
+
];
|
|
2394
|
+
if (Object.hasOwn(value, "schema_version") && value.schema_version !== options.schema) facts.push({ name: "schema_version", value: "wrong_type", expected: options.schema });
|
|
2395
|
+
return facts;
|
|
2396
|
+
}
|
|
1921
2397
|
function validateIdentity(sourceRef, installationRef, epoch, error) {
|
|
1922
|
-
if (!SAFE_REF2.test(sourceRef) || !SAFE_REF2.test(installationRef) || !positive(epoch, 2147483647)) throw error("invalid_config");
|
|
2398
|
+
if (!SAFE_REF2.test(sourceRef) || !SAFE_REF2.test(installationRef) || !positive(epoch, 2147483647)) throw error("invalid_config", refusalFacts(producerIdentityFacts(sourceRef, installationRef, epoch)));
|
|
1923
2399
|
return epoch;
|
|
1924
2400
|
}
|
|
2401
|
+
function producerIdentityFacts(sourceRef, installationRef, epoch) {
|
|
2402
|
+
const facts = [];
|
|
2403
|
+
if (!SAFE_REF2.test(sourceRef)) facts.push({ name: "host_source_ref", value: "wrong_type", expected: "a safe reference" });
|
|
2404
|
+
if (!SAFE_REF2.test(installationRef)) facts.push({ name: "producer_installation_ref", value: "wrong_type", expected: "a safe reference" });
|
|
2405
|
+
if (!positive(epoch, 2147483647)) facts.push({ name: "producer_epoch", value: "wrong_type", expected: "a positive integer" });
|
|
2406
|
+
return facts;
|
|
2407
|
+
}
|
|
1925
2408
|
function validateIntervals(reconcile, retry, timeout, error) {
|
|
1926
|
-
if (!boundedMilliseconds(reconcile) || !boundedMilliseconds(retry) || !boundedMilliseconds(timeout)) throw error("invalid_config");
|
|
2409
|
+
if (!boundedMilliseconds(reconcile) || !boundedMilliseconds(retry) || !boundedMilliseconds(timeout)) throw error("invalid_config", refusalFacts(producerIntervalFacts(reconcile, retry, timeout)));
|
|
1927
2410
|
return [reconcile, retry, timeout];
|
|
1928
2411
|
}
|
|
1929
|
-
function
|
|
1930
|
-
|
|
2412
|
+
function producerIntervalFacts(reconcile, retry, timeout) {
|
|
2413
|
+
const facts = [];
|
|
2414
|
+
if (!boundedMilliseconds(reconcile)) facts.push({ name: "reconcile_interval_ms", value: "wrong_type", expected: "25..60000 milliseconds" });
|
|
2415
|
+
if (!boundedMilliseconds(retry)) facts.push({ name: "retry_backoff_ms", value: "wrong_type", expected: "25..60000 milliseconds" });
|
|
2416
|
+
if (!boundedMilliseconds(timeout)) facts.push({ name: "ipc_timeout_ms", value: "wrong_type", expected: "25..60000 milliseconds" });
|
|
2417
|
+
return facts;
|
|
1931
2418
|
}
|
|
1932
|
-
function
|
|
2419
|
+
function hasConfigKeys(value, required, tolerated = []) {
|
|
2420
|
+
return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => required.includes(key) || tolerated.includes(key) || Object.hasOwn(HOST_PRODUCER_TIMING_DEFAULTS, key));
|
|
2421
|
+
}
|
|
2422
|
+
function isRecord2(value) {
|
|
1933
2423
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1934
2424
|
}
|
|
1935
2425
|
function positive(value, maximum) {
|
|
@@ -1939,485 +2429,127 @@ function boundedMilliseconds(value) {
|
|
|
1939
2429
|
return positive(value, 6e4) && Number(value) >= 25;
|
|
1940
2430
|
}
|
|
1941
2431
|
|
|
1942
|
-
// host/
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
var
|
|
1946
|
-
var
|
|
1947
|
-
var
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
return typeof value === "string" && FRAME_KINDS.has(value);
|
|
1952
|
-
}
|
|
1953
|
-
var HOST_PRODUCER_FRAME_ERROR_CODES = [
|
|
1954
|
-
"invalid_frame_encoding",
|
|
1955
|
-
"invalid_frame_length",
|
|
1956
|
-
"frame_truncated",
|
|
1957
|
-
"unsupported_frame_schema",
|
|
1958
|
-
"invalid_frame_shape",
|
|
1959
|
-
"invalid_producer_identity",
|
|
1960
|
-
"frame_too_large"
|
|
1961
|
-
];
|
|
1962
|
-
var HostProducerFrameError = class extends Error {
|
|
1963
|
-
code;
|
|
1964
|
-
constructor(code2, message = code2) {
|
|
1965
|
-
super(message);
|
|
1966
|
-
this.code = code2;
|
|
2432
|
+
// host/service-log.ts
|
|
2433
|
+
import { copyFile, mkdir, stat, truncate, appendFile } from "node:fs/promises";
|
|
2434
|
+
import { dirname as dirname2, isAbsolute, posix as posix4, win32 as win325 } from "node:path";
|
|
2435
|
+
var HOST_SERVICE_LOG_ENVIRONMENT = "CEAL_HOST_SERVICE_LOG";
|
|
2436
|
+
var HOST_SERVICE_LOG_ARGUMENT = "--ceal-host-service-log";
|
|
2437
|
+
var HostServiceLogArgumentError = class extends Error {
|
|
2438
|
+
constructor() {
|
|
2439
|
+
super("The Host service log argument must occur once with an absolute normalized path.");
|
|
2440
|
+
this.name = "HostServiceLogArgumentError";
|
|
1967
2441
|
}
|
|
1968
2442
|
};
|
|
1969
|
-
function
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
}
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
const
|
|
1981
|
-
|
|
1982
|
-
if (length === 0) throw new HostProducerFrameError("invalid_frame_length");
|
|
1983
|
-
if (input.byteLength !== length + HOST_PRODUCER_FRAME_LENGTH_BYTES) throw new HostProducerFrameError(input.byteLength < length + HOST_PRODUCER_FRAME_LENGTH_BYTES ? "frame_truncated" : "invalid_frame_length");
|
|
1984
|
-
const bytes = input.subarray(HOST_PRODUCER_FRAME_LENGTH_BYTES);
|
|
1985
|
-
const text = new TextDecoder("utf-8", { fatal: true });
|
|
1986
|
-
let value;
|
|
2443
|
+
function consumeHostServiceLogArgument(args, platform = process.platform) {
|
|
2444
|
+
const indexes = args.flatMap((value, index2) => value === HOST_SERVICE_LOG_ARGUMENT ? [index2] : []);
|
|
2445
|
+
if (indexes.length === 0) return { args: [...args] };
|
|
2446
|
+
const index = indexes[0];
|
|
2447
|
+
if (index === void 0) throw new HostServiceLogArgumentError();
|
|
2448
|
+
const path = args[index + 1];
|
|
2449
|
+
const pathApi = platform === "win32" ? win325 : posix4;
|
|
2450
|
+
if (indexes.length !== 1 || path === void 0 || !pathApi.isAbsolute(path) || pathApi.normalize(path) !== path) throw new HostServiceLogArgumentError();
|
|
2451
|
+
return { args: [...args.slice(0, index), ...args.slice(index + 2)], path };
|
|
2452
|
+
}
|
|
2453
|
+
function writeHostServiceDiagnostic(producer_id, event, failure_kind, cause, sink) {
|
|
2454
|
+
const line = `${JSON.stringify({ schema_version: CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA, producer_id, event, ...event === "watcher_scan_failed" && failure_kind !== void 0 ? { failure_kind } : {}, ...event === "watcher_scan_failed" && cause !== void 0 ? { cause } : {} })}
|
|
2455
|
+
`;
|
|
1987
2456
|
try {
|
|
1988
|
-
|
|
1989
|
-
} catch
|
|
1990
|
-
if (error instanceof TypeError) throw new HostProducerFrameError("invalid_frame_encoding");
|
|
1991
|
-
throw new HostProducerFrameError("frame_truncated");
|
|
2457
|
+
(sink ?? defaultHostServiceDiagnosticSink)(line);
|
|
2458
|
+
} catch {
|
|
1992
2459
|
}
|
|
1993
|
-
validateFrame(value);
|
|
1994
|
-
return value;
|
|
1995
2460
|
}
|
|
1996
|
-
function
|
|
1997
|
-
|
|
2461
|
+
function defaultHostServiceDiagnosticSink(line) {
|
|
2462
|
+
writeHostServiceLogLine(line);
|
|
2463
|
+
process.stderr.write(line);
|
|
1998
2464
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
if (!PRODUCERS.has(String(value.producer_id)) || !safeRef(value.producer_installation_ref)) throw new HostProducerFrameError("invalid_producer_identity");
|
|
2004
|
-
if (!positive2(value.producer_epoch) || !positive2(value.sequence) || !hostProducerFrameKind(value.frame_kind) || !timestamp(value.occurred_at) || !record(value.payload)) throw new HostProducerFrameError("invalid_frame_shape");
|
|
2005
|
-
validatePayload(value.frame_kind, value.payload);
|
|
2006
|
-
}
|
|
2007
|
-
function validatePayload(kind, value) {
|
|
2008
|
-
const valid = kind === "session_started" ? validSession(value) : kind === "turn_started" ? validTurn(value) : kind === "user_input" ? validInput(value) : kind === "turn_terminal" ? validTerminal(value) : validStatus(value);
|
|
2009
|
-
if (!valid) throw new HostProducerFrameError("invalid_frame_shape");
|
|
2465
|
+
var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
|
|
2466
|
+
var HOST_SERVICE_LOG_CHECK_INTERVAL_MS = 6e4;
|
|
2467
|
+
function hostServiceLogPreviousPath(path) {
|
|
2468
|
+
return `${path}.1`;
|
|
2010
2469
|
}
|
|
2011
|
-
function
|
|
2012
|
-
|
|
2470
|
+
async function rotateHostServiceLog(path, maxBytes = HOST_SERVICE_LOG_MAX_BYTES) {
|
|
2471
|
+
let size;
|
|
2472
|
+
try {
|
|
2473
|
+
size = (await stat(path)).size;
|
|
2474
|
+
} catch (error) {
|
|
2475
|
+
if (isNotFound(error)) return false;
|
|
2476
|
+
throw error;
|
|
2477
|
+
}
|
|
2478
|
+
if (size <= maxBytes) return false;
|
|
2479
|
+
await copyFile(path, hostServiceLogPreviousPath(path));
|
|
2480
|
+
await truncate(path, 0);
|
|
2481
|
+
return true;
|
|
2013
2482
|
}
|
|
2014
|
-
function
|
|
2015
|
-
|
|
2483
|
+
function boundHostServiceLog(environment = process.env) {
|
|
2484
|
+
const path = environment[HOST_SERVICE_LOG_ENVIRONMENT];
|
|
2485
|
+
if (path === void 0 || !isAbsolute(path)) return () => void 0;
|
|
2486
|
+
let reported = false;
|
|
2487
|
+
const ready = ensureHostServiceLogDirectory(path);
|
|
2488
|
+
const check = () => {
|
|
2489
|
+
ready.then(() => rotateHostServiceLog(path)).catch((error) => {
|
|
2490
|
+
if (reported) return;
|
|
2491
|
+
reported = true;
|
|
2492
|
+
process.stderr.write(`[host-service-log] rotation_failed code=${errorCode(error)}
|
|
2493
|
+
`);
|
|
2494
|
+
});
|
|
2495
|
+
};
|
|
2496
|
+
check();
|
|
2497
|
+
const timer = setInterval(check, HOST_SERVICE_LOG_CHECK_INTERVAL_MS);
|
|
2498
|
+
timer.unref();
|
|
2499
|
+
return () => clearInterval(timer);
|
|
2016
2500
|
}
|
|
2017
|
-
function
|
|
2018
|
-
|
|
2501
|
+
function writeHostServiceLogLine(line, environment = process.env) {
|
|
2502
|
+
if (process.platform !== "win32") return;
|
|
2503
|
+
const path = environment[HOST_SERVICE_LOG_ENVIRONMENT];
|
|
2504
|
+
if (path === void 0 || !win325.isAbsolute(path) || win325.normalize(path) !== path) return;
|
|
2505
|
+
void appendHostServiceLogLine(path, line).catch(() => void 0);
|
|
2019
2506
|
}
|
|
2020
|
-
function
|
|
2021
|
-
|
|
2507
|
+
async function appendHostServiceLogLine(path, line) {
|
|
2508
|
+
if (!isAbsolute(path)) return;
|
|
2509
|
+
await ensureHostServiceLogDirectory(path);
|
|
2510
|
+
await appendFile(path, line);
|
|
2022
2511
|
}
|
|
2023
|
-
|
|
2024
|
-
|
|
2512
|
+
var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
|
|
2513
|
+
var hostServiceLogDirectories = /* @__PURE__ */ new Map();
|
|
2514
|
+
function ensureHostServiceLogDirectory(path) {
|
|
2515
|
+
const directory = dirname2(path);
|
|
2516
|
+
const existing = hostServiceLogDirectories.get(directory);
|
|
2517
|
+
if (existing !== void 0) return existing;
|
|
2518
|
+
const pending = mkdir(directory, { recursive: true, mode: 448 }).then(() => void 0).catch((error) => {
|
|
2519
|
+
hostServiceLogDirectories.delete(directory);
|
|
2520
|
+
throw error;
|
|
2521
|
+
});
|
|
2522
|
+
hostServiceLogDirectories.set(directory, pending);
|
|
2523
|
+
return pending;
|
|
2025
2524
|
}
|
|
2026
|
-
function
|
|
2027
|
-
return
|
|
2525
|
+
function isNotFound(error) {
|
|
2526
|
+
return errorCode(error) === "ENOENT";
|
|
2028
2527
|
}
|
|
2029
|
-
function
|
|
2030
|
-
return
|
|
2528
|
+
function errorCode(error) {
|
|
2529
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "unknown";
|
|
2031
2530
|
}
|
|
2032
|
-
|
|
2033
|
-
|
|
2531
|
+
|
|
2532
|
+
// host/relay/src/host-producer-core.ts
|
|
2533
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
2534
|
+
var HOST_TRANSCRIPT_CURSOR_REASONS = vocabulary(["transcript_replaced", "transcript_truncated", "transcript_rewritten", "transcript_malformed"]);
|
|
2535
|
+
function isHostTranscriptCursorReason(value) {
|
|
2536
|
+
return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
|
|
2034
2537
|
}
|
|
2035
|
-
|
|
2036
|
-
|
|
2538
|
+
var TRANSCRIPT_MISSING_REASON = "transcript_missing";
|
|
2539
|
+
var TRANSCRIPT_SCAN_READ_ERROR_ERRNOS = ["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"];
|
|
2540
|
+
var HOST_INVOCATION_TRANSCRIPT_TAIL_MAX_BYTES = 1024 * 1024;
|
|
2541
|
+
function transcriptScanFailureKind(error, adapterFailureKind) {
|
|
2542
|
+
if (transcriptErrorCode(error) === "ELOOP") return "symlink";
|
|
2543
|
+
const adapter = adapterFailureKind(error);
|
|
2544
|
+
if (adapter !== void 0) return adapter;
|
|
2545
|
+
const code2 = transcriptErrorCode(error);
|
|
2546
|
+
if (code2 === "EFBIG") return "size_budget";
|
|
2547
|
+
if (code2 !== null && TRANSCRIPT_SCAN_READ_ERROR_ERRNOS.includes(code2)) return "read_error";
|
|
2548
|
+
return "other";
|
|
2037
2549
|
}
|
|
2038
|
-
function
|
|
2039
|
-
return
|
|
2550
|
+
function transcriptErrorCode(error) {
|
|
2551
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
|
|
2040
2552
|
}
|
|
2041
|
-
function optionalRefs(value) {
|
|
2042
|
-
return value === void 0 || Array.isArray(value) && value.length <= 16 && value.every(safeRef);
|
|
2043
|
-
}
|
|
2044
|
-
function validObservations(value) {
|
|
2045
|
-
if (value === void 0) return true;
|
|
2046
|
-
if (!Array.isArray(value) || value.length > 32) return false;
|
|
2047
|
-
return value.every((item) => {
|
|
2048
|
-
try {
|
|
2049
|
-
validateCealHostObservationDraft(item);
|
|
2050
|
-
return true;
|
|
2051
|
-
} catch {
|
|
2052
|
-
return false;
|
|
2053
|
-
}
|
|
2054
|
-
});
|
|
2055
|
-
}
|
|
2056
|
-
function jsonValue(value, depth = 0) {
|
|
2057
|
-
if (depth > 16) return false;
|
|
2058
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
2059
|
-
if (typeof value === "number") return Number.isFinite(value);
|
|
2060
|
-
if (Array.isArray(value)) return value.length <= 256 && value.every((item) => jsonValue(item, depth + 1));
|
|
2061
|
-
return record(value) && Object.keys(value).length <= 256 && Object.entries(value).every(([key, item]) => key.length > 0 && key.length <= 128 && jsonValue(item, depth + 1));
|
|
2062
|
-
}
|
|
2063
|
-
function record(value) {
|
|
2064
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2065
|
-
}
|
|
2066
|
-
function safeRef(value) {
|
|
2067
|
-
return typeof value === "string" && SAFE_REF3.test(value);
|
|
2068
|
-
}
|
|
2069
|
-
function positive2(value) {
|
|
2070
|
-
return Number.isSafeInteger(value) && Number(value) > 0;
|
|
2071
|
-
}
|
|
2072
|
-
function timestamp(value) {
|
|
2073
|
-
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
2074
|
-
}
|
|
2075
|
-
function hostProducerFrameKind(value) {
|
|
2076
|
-
return isHostProducerFrameKind(value);
|
|
2077
|
-
}
|
|
2078
|
-
function exactKeys(value, keys) {
|
|
2079
|
-
return JSON.stringify(Object.keys(value).sort()) === JSON.stringify([...keys].sort());
|
|
2080
|
-
}
|
|
2081
|
-
function exactOptional(value, required, optional) {
|
|
2082
|
-
const keys = Object.keys(value);
|
|
2083
|
-
return required.every((key) => keys.includes(key)) && keys.every((key) => required.includes(key) || optional.includes(key));
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
|
-
// host/relay/src/host-producer-core.ts
|
|
2087
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
2088
|
-
import { O_NOFOLLOW, O_RDONLY as O_RDONLY2 } from "node:constants";
|
|
2089
|
-
import { mkdir as mkdir2, open as open3, readFile, rename as rename2, writeFile } from "node:fs/promises";
|
|
2090
|
-
import { join as join2 } from "node:path";
|
|
2091
|
-
|
|
2092
|
-
// host/relay/src/relay-ipc.ts
|
|
2093
|
-
import { createConnection, createServer } from "node:net";
|
|
2094
|
-
|
|
2095
|
-
// host/relay/src/durable-atomic-replacement.ts
|
|
2096
|
-
import { O_RDONLY } from "node:constants";
|
|
2097
|
-
import { lstat as lstat2, mkdir, open as open2, rename, unlink } from "node:fs/promises";
|
|
2098
|
-
import { dirname } from "node:path";
|
|
2099
|
-
var DEFAULT_PORT = {
|
|
2100
|
-
platform: process.platform,
|
|
2101
|
-
mkdir: (path, options) => mkdir(path, options),
|
|
2102
|
-
lstat: (path) => lstat2(path),
|
|
2103
|
-
open_file: (path) => open2(path, "wx", 384),
|
|
2104
|
-
rename,
|
|
2105
|
-
unlink,
|
|
2106
|
-
open_directory: (path) => open2(path, O_RDONLY),
|
|
2107
|
-
unique_ref: () => `${process.pid}.${crypto.randomUUID()}`
|
|
2108
|
-
};
|
|
2109
|
-
async function durableAtomicReplace(target, data, port = DEFAULT_PORT) {
|
|
2110
|
-
const parent = dirname(target);
|
|
2111
|
-
await port.mkdir(parent, { recursive: true, mode: 448 });
|
|
2112
|
-
const parentStats = await port.lstat(parent);
|
|
2113
|
-
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 }])}.`);
|
|
2114
|
-
const temporary = `${target}.${port.unique_ref()}.new`;
|
|
2115
|
-
let staged;
|
|
2116
|
-
const failures = [];
|
|
2117
|
-
try {
|
|
2118
|
-
staged = await port.open_file(temporary);
|
|
2119
|
-
await staged.writeFile(data, typeof data === "string" ? { encoding: "utf8" } : void 0);
|
|
2120
|
-
await staged.sync();
|
|
2121
|
-
const closing = staged;
|
|
2122
|
-
staged = void 0;
|
|
2123
|
-
await closing.close();
|
|
2124
|
-
await port.rename(temporary, target);
|
|
2125
|
-
await syncParentDirectory(parent, port);
|
|
2126
|
-
} catch (error) {
|
|
2127
|
-
failures.push(error);
|
|
2128
|
-
}
|
|
2129
|
-
await closeStaged(staged, failures);
|
|
2130
|
-
await removeTemporary(temporary, port, failures);
|
|
2131
|
-
throwFailures(failures);
|
|
2132
|
-
}
|
|
2133
|
-
async function syncParentDirectory(path, port) {
|
|
2134
|
-
if (port.platform === "win32") return;
|
|
2135
|
-
const handle = await port.open_directory(path);
|
|
2136
|
-
let failure;
|
|
2137
|
-
try {
|
|
2138
|
-
await handle.sync();
|
|
2139
|
-
} catch (error) {
|
|
2140
|
-
if (!directorySyncUnsupported(error)) failure = error;
|
|
2141
|
-
}
|
|
2142
|
-
try {
|
|
2143
|
-
await handle.close();
|
|
2144
|
-
} catch (error) {
|
|
2145
|
-
if (failure === void 0) failure = error;
|
|
2146
|
-
else failure = new AggregateError([failure, error], "Host Relay directory sync and close failed.");
|
|
2147
|
-
}
|
|
2148
|
-
if (failure !== void 0) throw failure;
|
|
2149
|
-
}
|
|
2150
|
-
function ownerPrivate(stats, platform) {
|
|
2151
|
-
if (platform === "win32") return true;
|
|
2152
|
-
const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
|
|
2153
|
-
return stats.uid === uid && (stats.mode & 63) === 0;
|
|
2154
|
-
}
|
|
2155
|
-
async function closeStaged(staged, failures) {
|
|
2156
|
-
if (staged !== void 0) try {
|
|
2157
|
-
await staged.close();
|
|
2158
|
-
} catch (error) {
|
|
2159
|
-
failures.push(error);
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
async function removeTemporary(path, port, failures) {
|
|
2163
|
-
try {
|
|
2164
|
-
await port.unlink(path);
|
|
2165
|
-
} catch (error) {
|
|
2166
|
-
if (errorCode(error) !== "ENOENT") failures.push(error);
|
|
2167
|
-
}
|
|
2168
|
-
}
|
|
2169
|
-
function throwFailures(failures) {
|
|
2170
|
-
if (failures.length === 1) throw failures[0];
|
|
2171
|
-
if (failures.length > 1) throw new AggregateError(failures, "Host Relay durable replacement and cleanup failed.");
|
|
2172
|
-
}
|
|
2173
|
-
function directorySyncUnsupported(error) {
|
|
2174
|
-
return ["EINVAL", "ENOSYS", "ENOTSUP"].includes(errorCode(error) ?? "");
|
|
2175
|
-
}
|
|
2176
|
-
function errorCode(error) {
|
|
2177
|
-
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
|
|
2178
|
-
}
|
|
2179
|
-
|
|
2180
|
-
// host/relay/src/relay-request-binding-store.ts
|
|
2181
|
-
var MAX_STATE_BYTES = 8 * 1024 * 1024;
|
|
2182
|
-
|
|
2183
|
-
// host/relay/src/current-prompt-custody.ts
|
|
2184
|
-
var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
|
|
2185
|
-
|
|
2186
|
-
// host/relay/src/relay-ipc.ts
|
|
2187
|
-
var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
|
|
2188
|
-
"schema_version",
|
|
2189
|
-
"producer_id",
|
|
2190
|
-
"producer_installation_ref",
|
|
2191
|
-
"local_session_ref",
|
|
2192
|
-
"local_turn_ref",
|
|
2193
|
-
"local_turn_aliases",
|
|
2194
|
-
"prompt_ref",
|
|
2195
|
-
"request_ref",
|
|
2196
|
-
"bound_at",
|
|
2197
|
-
"host_source_ref",
|
|
2198
|
-
"host_session_ref",
|
|
2199
|
-
"host_turn_ref",
|
|
2200
|
-
"host_input_ref"
|
|
2201
|
-
]);
|
|
2202
|
-
var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
|
|
2203
|
-
var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
|
|
2204
|
-
var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
|
|
2205
|
-
var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
2206
|
-
var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
|
|
2207
|
-
var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
|
|
2208
|
-
"context_stale",
|
|
2209
|
-
"context_expired",
|
|
2210
|
-
"host_source_refused",
|
|
2211
|
-
"host_credential_refused",
|
|
2212
|
-
"gateway_unavailable",
|
|
2213
|
-
"gateway_refused",
|
|
2214
|
-
"gateway_response_invalid",
|
|
2215
|
-
"gateway_rate_limited",
|
|
2216
|
-
"producer_frame_invalid",
|
|
2217
|
-
"deadline",
|
|
2218
|
-
"cancelled",
|
|
2219
|
-
"mint_failed",
|
|
2220
|
-
"relay_failed",
|
|
2221
|
-
"request_authority_unavailable",
|
|
2222
|
-
"request_missing",
|
|
2223
|
-
"authority_mismatch",
|
|
2224
|
-
"host_source_mismatch",
|
|
2225
|
-
"host_session_mismatch",
|
|
2226
|
-
"host_turn_mismatch",
|
|
2227
|
-
"host_input_mismatch",
|
|
2228
|
-
"host_prompt_mismatch"
|
|
2229
|
-
]);
|
|
2230
|
-
var HOST_RELAY_DIRECT_WIRE_MINT_REASONS = vocabulary([
|
|
2231
|
-
"context_stale",
|
|
2232
|
-
"context_expired",
|
|
2233
|
-
"host_source_refused",
|
|
2234
|
-
"host_credential_refused",
|
|
2235
|
-
"gateway_unavailable",
|
|
2236
|
-
"gateway_rate_limited",
|
|
2237
|
-
"gateway_refused",
|
|
2238
|
-
"gateway_response_invalid",
|
|
2239
|
-
"producer_frame_invalid"
|
|
2240
|
-
]);
|
|
2241
|
-
var HOST_RELAY_INVOCATION_MINT_OUTCOMES = vocabulary(["completed", "caller_cancelled", "deadline", "failed", "no_binding"]);
|
|
2242
|
-
var HOST_RELAY_EXCHANGE_FAILURES = vocabulary([
|
|
2243
|
-
"permission_denied",
|
|
2244
|
-
"socket_missing",
|
|
2245
|
-
"connection_refused",
|
|
2246
|
-
"closed_early",
|
|
2247
|
-
"response_invalid",
|
|
2248
|
-
"timed_out",
|
|
2249
|
-
"cancelled",
|
|
2250
|
-
"unavailable"
|
|
2251
|
-
]);
|
|
2252
|
-
var HostRelayIpcError = class extends Error {
|
|
2253
|
-
code;
|
|
2254
|
-
/** Set only when the client-side socket exchange itself failed. */
|
|
2255
|
-
failure;
|
|
2256
|
-
constructor(code2, message = code2, failure) {
|
|
2257
|
-
super(message);
|
|
2258
|
-
this.code = code2;
|
|
2259
|
-
this.failure = failure;
|
|
2260
|
-
}
|
|
2261
|
-
};
|
|
2262
|
-
function hostRelayExchangeFailure(error) {
|
|
2263
|
-
return error instanceof HostRelayIpcError ? error.failure : void 0;
|
|
2264
|
-
}
|
|
2265
|
-
function socketErrorFailure(error) {
|
|
2266
|
-
switch (errorCode2(error)) {
|
|
2267
|
-
case "EACCES":
|
|
2268
|
-
case "EPERM":
|
|
2269
|
-
return "permission_denied";
|
|
2270
|
-
case "ENOENT":
|
|
2271
|
-
return "socket_missing";
|
|
2272
|
-
case "ECONNREFUSED":
|
|
2273
|
-
return "connection_refused";
|
|
2274
|
-
default:
|
|
2275
|
-
return "unavailable";
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2278
|
-
var HostRelayIpcAdmissionPort = class {
|
|
2279
|
-
endpoint;
|
|
2280
|
-
timeout_ms;
|
|
2281
|
-
constructor(endpoint, timeoutMs = 5e3) {
|
|
2282
|
-
this.endpoint = endpoint;
|
|
2283
|
-
this.timeout_ms = timeoutMs;
|
|
2284
|
-
}
|
|
2285
|
-
async admit(frame) {
|
|
2286
|
-
const response2 = await submitHostProducerFrame(this.endpoint, frame, this.timeout_ms);
|
|
2287
|
-
if (!relayAdmissionCode(response2.code)) throw new HostRelayIpcError(response2.code);
|
|
2288
|
-
return { code: response2.code, admitted_through_sequence: response2.admitted_through_sequence, delivered_through_sequence: response2.delivered_through_sequence };
|
|
2289
|
-
}
|
|
2290
|
-
};
|
|
2291
|
-
var HostRelayCurrentPromptPort = class {
|
|
2292
|
-
endpoint;
|
|
2293
|
-
timeout_ms;
|
|
2294
|
-
constructor(endpoint, timeoutMs = 5e3) {
|
|
2295
|
-
this.endpoint = endpoint;
|
|
2296
|
-
this.timeout_ms = timeoutMs;
|
|
2297
|
-
}
|
|
2298
|
-
async register(input) {
|
|
2299
|
-
const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA, ...input };
|
|
2300
|
-
const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
|
|
2301
|
-
if (response2.status !== "registered") throw new HostRelayIpcError("relay_unavailable", response2.status);
|
|
2302
|
-
}
|
|
2303
|
-
};
|
|
2304
|
-
function submitHostProducerFrame(endpoint, frame, timeoutMs = 5e3) {
|
|
2305
|
-
return exchange(endpoint, encodeHostProducerFrame(frame), timeoutMs, decodeResponseBuffer);
|
|
2306
|
-
}
|
|
2307
|
-
function exchange(endpoint, encoded, timeoutMs, decode, signal) {
|
|
2308
|
-
return new Promise((resolve, reject) => {
|
|
2309
|
-
const socket = createConnection(endpoint);
|
|
2310
|
-
let buffer = Buffer.alloc(0);
|
|
2311
|
-
let settled = false;
|
|
2312
|
-
const aborted = () => finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC was cancelled.", "cancelled"));
|
|
2313
|
-
const finish = (error, response2) => {
|
|
2314
|
-
if (settled) return;
|
|
2315
|
-
settled = true;
|
|
2316
|
-
signal?.removeEventListener("abort", aborted);
|
|
2317
|
-
socket.destroy();
|
|
2318
|
-
if (error !== null) reject(error);
|
|
2319
|
-
else if (response2 !== void 0) resolve(response2);
|
|
2320
|
-
};
|
|
2321
|
-
if (timeoutMs !== void 0) socket.setTimeout(timeoutMs, () => finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC timed out.", "timed_out")));
|
|
2322
|
-
if (signal?.aborted === true) {
|
|
2323
|
-
aborted();
|
|
2324
|
-
return;
|
|
2325
|
-
}
|
|
2326
|
-
signal?.addEventListener("abort", aborted, { once: true });
|
|
2327
|
-
socket.once("error", (error) => {
|
|
2328
|
-
const failure = socketErrorFailure(error);
|
|
2329
|
-
finish(new HostRelayIpcError("relay_unavailable", `Host Relay IPC is unavailable: ${failure}.`, failure));
|
|
2330
|
-
});
|
|
2331
|
-
socket.once("connect", () => socket.write(encoded));
|
|
2332
|
-
socket.on("data", (chunk) => {
|
|
2333
|
-
try {
|
|
2334
|
-
buffer = Buffer.concat([buffer, Buffer.from(chunk)]);
|
|
2335
|
-
const decoded = decode(buffer);
|
|
2336
|
-
if (decoded !== null) finish(null, decoded);
|
|
2337
|
-
} catch (error) {
|
|
2338
|
-
finish(new HostRelayIpcError("relay_unavailable", error instanceof Error ? error.message : "Host Relay IPC response is invalid.", "response_invalid"));
|
|
2339
|
-
}
|
|
2340
|
-
});
|
|
2341
|
-
socket.once("end", () => {
|
|
2342
|
-
if (!settled) finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC closed without a response.", "closed_early"));
|
|
2343
|
-
});
|
|
2344
|
-
});
|
|
2345
|
-
}
|
|
2346
|
-
function decodeHostRelayIpcResponse(input) {
|
|
2347
|
-
if (input.length < HOST_RELAY_IPC_LENGTH_BYTES) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC response is truncated: ${refusalFact("response_bytes", input.byteLength, `at least ${HOST_RELAY_IPC_LENGTH_BYTES}`)}.`);
|
|
2348
|
-
const length = input.readUInt32BE(0);
|
|
2349
|
-
if (length === 0 || length > HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES || input.length !== length + HOST_RELAY_IPC_LENGTH_BYTES) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC response length is invalid: ${refusalFacts([{ name: "declared_bytes", value: length, expected: `between 1 and ${HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES}` }, { name: "received_bytes", value: input.byteLength, expected: `exactly ${length + HOST_RELAY_IPC_LENGTH_BYTES}` }])}.`);
|
|
2350
|
-
let value;
|
|
2351
|
-
try {
|
|
2352
|
-
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.subarray(HOST_RELAY_IPC_LENGTH_BYTES)));
|
|
2353
|
-
} catch {
|
|
2354
|
-
throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC response is invalid.");
|
|
2355
|
-
}
|
|
2356
|
-
if (!validResponse(value)) throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC response shape is invalid.");
|
|
2357
|
-
return value;
|
|
2358
|
-
}
|
|
2359
|
-
function relayAdmissionCode(value) {
|
|
2360
|
-
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";
|
|
2361
|
-
}
|
|
2362
|
-
function validResponse(value) {
|
|
2363
|
-
return record2(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;
|
|
2364
|
-
}
|
|
2365
|
-
function hostRelayIpcCode(value) {
|
|
2366
|
-
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);
|
|
2367
|
-
}
|
|
2368
|
-
function nonnegative(value) {
|
|
2369
|
-
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
2370
|
-
}
|
|
2371
|
-
function record2(value) {
|
|
2372
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2373
|
-
}
|
|
2374
|
-
function decodeResponseBuffer(buffer) {
|
|
2375
|
-
if (buffer.length < HOST_RELAY_IPC_LENGTH_BYTES) return null;
|
|
2376
|
-
const length = buffer.readUInt32BE(0);
|
|
2377
|
-
if (length === 0 || length > HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC response length is invalid: ${refusalFact("declared_bytes", length, `between 1 and ${HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES}`)}.`);
|
|
2378
|
-
if (buffer.length < length + HOST_RELAY_IPC_LENGTH_BYTES) return null;
|
|
2379
|
-
if (buffer.length !== length + HOST_RELAY_IPC_LENGTH_BYTES) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC returned trailing bytes: ${refusalFacts([{ name: "declared_bytes", value: length, expected: `the received message length minus ${HOST_RELAY_IPC_LENGTH_BYTES}` }, { name: "received_bytes", value: buffer.byteLength, expected: `exactly ${length + HOST_RELAY_IPC_LENGTH_BYTES}` }])}.`);
|
|
2380
|
-
return decodeHostRelayIpcResponse(buffer);
|
|
2381
|
-
}
|
|
2382
|
-
function decodeCurrentPromptResponseBuffer(buffer) {
|
|
2383
|
-
const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES);
|
|
2384
|
-
if (value === null) return null;
|
|
2385
|
-
if (!record2(value) || !exactKeys2(value, ["schema_version", "status"]) || value.schema_version !== HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA || value.status !== "registered" && value.status !== "current_prompt_unavailable") throw new HostRelayIpcError("relay_unavailable");
|
|
2386
|
-
return { schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status: value.status };
|
|
2387
|
-
}
|
|
2388
|
-
function decodeLengthPrefixedJson(input, maximum) {
|
|
2389
|
-
if (input.length < HOST_RELAY_IPC_LENGTH_BYTES) return null;
|
|
2390
|
-
const length = input.readUInt32BE(0);
|
|
2391
|
-
if (length === 0 || length > maximum) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC message length is invalid: ${refusalFact("declared_bytes", length, `between 1 and ${maximum}`)}.`);
|
|
2392
|
-
if (input.length < length + HOST_RELAY_IPC_LENGTH_BYTES) return null;
|
|
2393
|
-
if (input.length !== length + HOST_RELAY_IPC_LENGTH_BYTES) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC message has trailing bytes: ${refusalFacts([{ name: "declared_bytes", value: length, expected: `the received message length minus ${HOST_RELAY_IPC_LENGTH_BYTES}` }, { name: "received_bytes", value: input.byteLength, expected: `exactly ${length + HOST_RELAY_IPC_LENGTH_BYTES}` }])}.`);
|
|
2394
|
-
try {
|
|
2395
|
-
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.subarray(HOST_RELAY_IPC_LENGTH_BYTES)));
|
|
2396
|
-
} catch {
|
|
2397
|
-
throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC message is invalid.");
|
|
2398
|
-
}
|
|
2399
|
-
}
|
|
2400
|
-
function encodeLengthPrefixed(value) {
|
|
2401
|
-
const body = Buffer.from(JSON.stringify(value), "utf8");
|
|
2402
|
-
const output = Buffer.allocUnsafe(body.length + HOST_RELAY_IPC_LENGTH_BYTES);
|
|
2403
|
-
output.writeUInt32BE(body.length, 0);
|
|
2404
|
-
body.copy(output, HOST_RELAY_IPC_LENGTH_BYTES);
|
|
2405
|
-
return output;
|
|
2406
|
-
}
|
|
2407
|
-
function exactKeys2(value, required, optional = []) {
|
|
2408
|
-
const keys = Object.keys(value);
|
|
2409
|
-
return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => required.includes(key) || optional.includes(key));
|
|
2410
|
-
}
|
|
2411
|
-
function errorCode2(error) {
|
|
2412
|
-
return record2(error) && typeof error.code === "string" ? error.code : null;
|
|
2413
|
-
}
|
|
2414
|
-
|
|
2415
|
-
// host/relay/src/host-producer-core.ts
|
|
2416
|
-
var HOST_TRANSCRIPT_CURSOR_REASONS = vocabulary(["transcript_replaced", "transcript_truncated", "transcript_rewritten", "transcript_malformed"]);
|
|
2417
|
-
function isHostTranscriptCursorReason(value) {
|
|
2418
|
-
return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
|
|
2419
|
-
}
|
|
2420
|
-
var TRANSCRIPT_MISSING_REASON = "transcript_missing";
|
|
2421
2553
|
var HOST_TRANSCRIPT_READ_CHUNK_BYTES = 256 * 1024;
|
|
2422
2554
|
async function readTranscriptCompleteChunk(handle, offset, size) {
|
|
2423
2555
|
const available = Math.max(0, size - offset);
|
|
@@ -2462,10 +2594,10 @@ async function transcriptCursorBoundsReason(handle, cursor, size) {
|
|
|
2462
2594
|
return null;
|
|
2463
2595
|
}
|
|
2464
2596
|
function transcriptSha256(value) {
|
|
2465
|
-
return
|
|
2597
|
+
return createHash4("sha256").update(value).digest("hex");
|
|
2466
2598
|
}
|
|
2467
2599
|
function transcriptObjectSha256(value) {
|
|
2468
|
-
return
|
|
2600
|
+
return createHash4("sha256").update(JSON.stringify(value), "utf8").digest("hex");
|
|
2469
2601
|
}
|
|
2470
2602
|
function isTranscriptRecord(value) {
|
|
2471
2603
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -2534,11 +2666,10 @@ var HostTranscriptWatcher = class {
|
|
|
2534
2666
|
async openTranscript(registration, scan, handlers) {
|
|
2535
2667
|
let handle;
|
|
2536
2668
|
try {
|
|
2537
|
-
handle = await
|
|
2669
|
+
handle = await openNoFollow(this.transcriptPathOf(registration));
|
|
2538
2670
|
return await scan(handle);
|
|
2539
2671
|
} catch (error) {
|
|
2540
2672
|
if (handle === void 0 && isMissingTranscriptError(error)) return handlers.missing();
|
|
2541
|
-
if (hostRelayExchangeFailure(error) !== void 0) throw error;
|
|
2542
2673
|
return handlers.failed(handle, error);
|
|
2543
2674
|
} finally {
|
|
2544
2675
|
await handle?.close();
|
|
@@ -2577,278 +2708,51 @@ var HostTranscriptWatcher = class {
|
|
|
2577
2708
|
readAnchor(handle, offset, anchorBytes) {
|
|
2578
2709
|
return readTranscriptAnchor(handle, offset, anchorBytes);
|
|
2579
2710
|
}
|
|
2580
|
-
cursorIdentityReason(cursor, transcriptPath, device, inode) {
|
|
2581
|
-
return transcriptCursorIdentityReason(cursor, transcriptPath, device, inode);
|
|
2582
|
-
}
|
|
2583
|
-
cursorBoundsReason(handle, cursor, size) {
|
|
2584
|
-
return transcriptCursorBoundsReason(handle, cursor, size);
|
|
2585
|
-
}
|
|
2586
|
-
};
|
|
2587
|
-
var
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
}
|
|
2612
|
-
drainOutbox() {
|
|
2613
|
-
const run = this.tail.then(() => this.drainLocked());
|
|
2614
|
-
this.tail = run.then(() => void 0, () => void 0);
|
|
2615
|
-
return run;
|
|
2616
|
-
}
|
|
2617
|
-
async drainLocked() {
|
|
2618
|
-
const read = await this.host.listEnvelopes();
|
|
2619
|
-
let admitted = 0;
|
|
2620
|
-
let expired = 0;
|
|
2621
|
-
let retained = 0;
|
|
2622
|
-
for (const envelope of read.entries) {
|
|
2623
|
-
const reserved = await this.hasReservation(this.host.submissionRefOf(envelope));
|
|
2624
|
-
if (!reserved && Date.parse(this.host.expiresAtOf(envelope)) <= this.now().getTime()) {
|
|
2625
|
-
await this.host.removeEnvelope(envelope);
|
|
2626
|
-
expired += 1;
|
|
2627
|
-
continue;
|
|
2628
|
-
}
|
|
2629
|
-
const frames = await this.reserve(envelope);
|
|
2630
|
-
let complete = true;
|
|
2631
|
-
for (const frame of frames) {
|
|
2632
|
-
const outcome = await this.relay.admit(frame);
|
|
2633
|
-
if (outcome.code !== "admitted" && outcome.code !== "already_admitted") {
|
|
2634
|
-
complete = false;
|
|
2635
|
-
break;
|
|
2636
|
-
}
|
|
2637
|
-
}
|
|
2638
|
-
if (!complete) {
|
|
2639
|
-
retained += 1;
|
|
2640
|
-
break;
|
|
2641
|
-
}
|
|
2642
|
-
await this.host.removeEnvelope(envelope);
|
|
2643
|
-
await this.release(this.host.submissionRefOf(envelope));
|
|
2644
|
-
admitted += 1;
|
|
2645
|
-
}
|
|
2646
|
-
return { admitted, retained: retained + Math.max(0, read.entries.length - admitted - expired - retained), expired, malformed: read.malformed };
|
|
2647
|
-
}
|
|
2648
|
-
async hasReservation(submissionRef2) {
|
|
2649
|
-
return (await this.readState()).pending[submissionRef2] !== void 0;
|
|
2650
|
-
}
|
|
2651
|
-
async reserve(envelope) {
|
|
2652
|
-
const state = await this.readState();
|
|
2653
|
-
const submissionRef2 = this.host.submissionRefOf(envelope);
|
|
2654
|
-
const existing = state.pending[submissionRef2];
|
|
2655
|
-
if (existing !== void 0) return existing;
|
|
2656
|
-
const drafts = await this.host.mapEnvelope(envelope);
|
|
2657
|
-
const frames = drafts.map((draft, offset) => ({
|
|
2658
|
-
schema_version: "ceal.host_producer_frame.v1",
|
|
2659
|
-
producer_id: this.host.producer_id,
|
|
2660
|
-
producer_installation_ref: this.installationRef,
|
|
2661
|
-
producer_epoch: this.epoch,
|
|
2662
|
-
sequence: state.next_sequence + offset,
|
|
2663
|
-
frame_kind: draft.frame_kind,
|
|
2664
|
-
occurred_at: this.host.createdAtOf(envelope),
|
|
2665
|
-
payload: draft.payload
|
|
2666
|
-
}));
|
|
2667
|
-
await this.writeState({ ...state, next_sequence: state.next_sequence + frames.length, pending: { ...state.pending, [submissionRef2]: frames } });
|
|
2668
|
-
return frames;
|
|
2669
|
-
}
|
|
2670
|
-
async release(submissionRef2) {
|
|
2671
|
-
const state = await this.readState();
|
|
2672
|
-
if (state.pending[submissionRef2] === void 0) return;
|
|
2673
|
-
const pending = { ...state.pending };
|
|
2674
|
-
delete pending[submissionRef2];
|
|
2675
|
-
await this.writeState({ ...state, pending });
|
|
2676
|
-
}
|
|
2677
|
-
async readState() {
|
|
2678
|
-
try {
|
|
2679
|
-
const value = JSON.parse(await readFile(this.statePath, "utf8"));
|
|
2680
|
-
return this.decodeState(value);
|
|
2681
|
-
} catch (error) {
|
|
2682
|
-
if (errorCode3(error) !== "ENOENT") throw error;
|
|
2683
|
-
return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: 1, pending: {} };
|
|
2684
|
-
}
|
|
2685
|
-
}
|
|
2686
|
-
async writeState(state) {
|
|
2687
|
-
await mkdir2(this.host.outbox_directory, { recursive: true, mode: 448 });
|
|
2688
|
-
const temporary = `${this.statePath}.${process.pid}.new`;
|
|
2689
|
-
await writeFile(temporary, JSON.stringify(state), { mode: 384 });
|
|
2690
|
-
await rename2(temporary, this.statePath);
|
|
2691
|
-
}
|
|
2692
|
-
decodeState(value) {
|
|
2693
|
-
const label = this.host.producer_label;
|
|
2694
|
-
const fail = (code2, message) => this.host.fail(code2, message);
|
|
2695
|
-
if (!isDrainStateHeader(value, this.stateSchema, this.installationRef)) {
|
|
2696
|
-
throw fail("storage", `${label} producer state header is malformed or belongs to another installation (${refusalFacts([
|
|
2697
|
-
{ name: "schema_version", value: isTranscriptRecord(value) ? value.schema_version : void 0, expected: this.stateSchema },
|
|
2698
|
-
{ name: "producer_installation_ref", value: isTranscriptRecord(value) ? value.producer_installation_ref : void 0, expected: this.installationRef }
|
|
2699
|
-
])}).`);
|
|
2700
|
-
}
|
|
2701
|
-
if (Number(value.producer_epoch) > this.epoch) throw fail("storage", `${label} producer state epoch is ahead of this installation (${refusalFact("producer_epoch", value.producer_epoch, `at most ${this.epoch}`)}).`);
|
|
2702
|
-
const pending = {};
|
|
2703
|
-
const storedEpoch = Number(value.producer_epoch);
|
|
2704
|
-
for (const [submissionRef2, frames] of Object.entries(value.pending)) {
|
|
2705
|
-
if (!isHostSafeRef(submissionRef2) || !Array.isArray(frames) || frames.length === 0) throw fail("storage", `${label} producer pending reservation is malformed.`);
|
|
2706
|
-
pending[submissionRef2] = frames.map((frame) => this.decodeStoredFrame(frame, storedEpoch));
|
|
2707
|
-
}
|
|
2708
|
-
return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: Number(value.next_sequence), pending };
|
|
2709
|
-
}
|
|
2710
|
-
decodeStoredFrame(value, storedEpoch) {
|
|
2711
|
-
const label = this.host.producer_label;
|
|
2712
|
-
const json = Buffer.from(JSON.stringify(value), "utf8");
|
|
2713
|
-
const encoded = Buffer.allocUnsafe(json.length + 4);
|
|
2714
|
-
encoded.writeUInt32BE(json.length, 0);
|
|
2715
|
-
json.copy(encoded, 4);
|
|
2716
|
-
const frame = decodeHostProducerFrame(encoded);
|
|
2717
|
-
if (frame.producer_id !== this.host.producer_id || frame.producer_installation_ref !== this.installationRef || frame.producer_epoch > storedEpoch) throw this.host.fail("storage", `${label} producer pending frame identity is malformed (${refusalFacts([
|
|
2718
|
-
{ name: "producer_id", value: frame.producer_id, expected: this.host.producer_id },
|
|
2719
|
-
{ name: "producer_installation_ref", value: frame.producer_installation_ref, expected: this.installationRef },
|
|
2720
|
-
{ name: "producer_epoch", value: frame.producer_epoch, expected: `at most ${storedEpoch}` }
|
|
2721
|
-
])}).`);
|
|
2722
|
-
return frame;
|
|
2723
|
-
}
|
|
2724
|
-
};
|
|
2725
|
-
function isDrainStateHeader(value, schema, installationRef) {
|
|
2726
|
-
return isTranscriptRecord(value) && value.schema_version === schema && value.producer_installation_ref === installationRef && Number.isSafeInteger(value.producer_epoch) && Number(value.producer_epoch) > 0 && Number.isSafeInteger(value.next_sequence) && Number(value.next_sequence) > 0 && isTranscriptRecord(value.pending);
|
|
2727
|
-
}
|
|
2728
|
-
function errorCode3(error) {
|
|
2729
|
-
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
|
|
2730
|
-
}
|
|
2731
|
-
|
|
2732
|
-
// host/relay/src/relay-quarantine-store.ts
|
|
2733
|
-
var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
|
|
2734
|
-
var RELAY_QUARANTINE_FRAME_REASONS = ["frame_identity_conflict", "sequence_gap", "gateway_refused"];
|
|
2735
|
-
var RELAY_QUARANTINE_REASONS = [...RELAY_QUARANTINE_FRAME_REASONS, ...HOST_PRODUCER_FRAME_ERROR_CODES];
|
|
2736
|
-
|
|
2737
|
-
// host/relay/src/relay-admission-store.ts
|
|
2738
|
-
var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
|
|
2739
|
-
var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
|
|
2740
|
-
var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
|
|
2741
|
-
|
|
2742
|
-
// host/service-log.ts
|
|
2743
|
-
import { copyFile, open as open4, stat, truncate } from "node:fs/promises";
|
|
2744
|
-
import { isAbsolute } from "node:path";
|
|
2745
|
-
var HOST_SERVICE_LOG_ENVIRONMENT = "CEAL_HOST_SERVICE_LOG";
|
|
2746
|
-
function writeHostServiceDiagnostic(producer_id, event, failure_kind) {
|
|
2747
|
-
try {
|
|
2748
|
-
process.stderr.write(`${JSON.stringify({ schema_version: CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA, producer_id, event, ...event === "watcher_scan_failed" && failure_kind !== void 0 ? { failure_kind } : {} })}
|
|
2749
|
-
`);
|
|
2750
|
-
} catch {
|
|
2751
|
-
}
|
|
2752
|
-
}
|
|
2753
|
-
var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
|
|
2754
|
-
var HOST_SERVICE_LOG_CHECK_INTERVAL_MS = 6e4;
|
|
2755
|
-
function hostServiceLogPreviousPath(path) {
|
|
2756
|
-
return `${path}.1`;
|
|
2757
|
-
}
|
|
2758
|
-
async function rotateHostServiceLog(path, maxBytes = HOST_SERVICE_LOG_MAX_BYTES) {
|
|
2759
|
-
let size;
|
|
2760
|
-
try {
|
|
2761
|
-
size = (await stat(path)).size;
|
|
2762
|
-
} catch (error) {
|
|
2763
|
-
if (isNotFound(error)) return false;
|
|
2764
|
-
throw error;
|
|
2765
|
-
}
|
|
2766
|
-
if (size <= maxBytes) return false;
|
|
2767
|
-
await copyFile(path, hostServiceLogPreviousPath(path));
|
|
2768
|
-
await truncate(path, 0);
|
|
2769
|
-
return true;
|
|
2770
|
-
}
|
|
2771
|
-
function boundHostServiceLog(environment = process.env) {
|
|
2772
|
-
const path = environment[HOST_SERVICE_LOG_ENVIRONMENT];
|
|
2773
|
-
if (path === void 0 || !isAbsolute(path)) return () => void 0;
|
|
2774
|
-
let reported = false;
|
|
2775
|
-
const check = () => {
|
|
2776
|
-
rotateHostServiceLog(path).catch((error) => {
|
|
2777
|
-
if (reported) return;
|
|
2778
|
-
reported = true;
|
|
2779
|
-
process.stderr.write(`[host-service-log] rotation_failed code=${errorCode4(error)}
|
|
2780
|
-
`);
|
|
2781
|
-
});
|
|
2782
|
-
};
|
|
2783
|
-
check();
|
|
2784
|
-
const timer = setInterval(check, HOST_SERVICE_LOG_CHECK_INTERVAL_MS);
|
|
2785
|
-
timer.unref();
|
|
2786
|
-
return () => clearInterval(timer);
|
|
2787
|
-
}
|
|
2788
|
-
var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
|
|
2789
|
-
function isNotFound(error) {
|
|
2790
|
-
return errorCode4(error) === "ENOENT";
|
|
2791
|
-
}
|
|
2792
|
-
function errorCode4(error) {
|
|
2793
|
-
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "unknown";
|
|
2794
|
-
}
|
|
2795
|
-
|
|
2796
|
-
// host/claude/src/claude-decoder.ts
|
|
2797
|
-
import { createHash as createHash4 } from "node:crypto";
|
|
2798
|
-
|
|
2799
|
-
// host/claude/src/claude-types.ts
|
|
2800
|
-
var CLAUDE_HOST_ERROR_CODES = ["invalid_input", "payload_rejected", "storage", "configuration"];
|
|
2801
|
-
function isClaudeHostErrorCode(value) {
|
|
2802
|
-
return typeof value === "string" && CLAUDE_HOST_ERROR_CODES.some((code2) => code2 === value);
|
|
2803
|
-
}
|
|
2804
|
-
var CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION = "ceal.claude_invocation_context.v1";
|
|
2805
|
-
var CLAUDE_OUTBOX_SCHEMA_VERSION = "ceal.claude_observation_outbox.v1";
|
|
2806
|
-
var CLAUDE_SESSION_SCHEMA_VERSION = "ceal.claude_session.v1";
|
|
2807
|
-
var CLAUDE_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION = "ceal.claude_transcript_registration.v1";
|
|
2808
|
-
var CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION = "ceal.claude_transcript_cursor.v1";
|
|
2809
|
-
var CLAUDE_DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1e3;
|
|
2810
|
-
var CLAUDE_TRANSCRIPT_MAX_TURN_MESSAGES = 1024;
|
|
2811
|
-
var CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES = 64;
|
|
2812
|
-
var CLAUDE_TRANSCRIPT_ANCHOR_BYTES = 256;
|
|
2813
|
-
var ClaudeHostError = class extends Error {
|
|
2814
|
-
name = "ClaudeHostError";
|
|
2815
|
-
code;
|
|
2816
|
-
diagnostic_kind;
|
|
2817
|
-
constructor(code2, message, diagnostic_kind) {
|
|
2818
|
-
super(message);
|
|
2819
|
-
if (!isClaudeHostErrorCode(code2)) throw new TypeError("Claude Host error code is invalid.");
|
|
2820
|
-
this.code = code2;
|
|
2821
|
-
this.diagnostic_kind = diagnostic_kind;
|
|
2822
|
-
}
|
|
2711
|
+
cursorIdentityReason(cursor, transcriptPath, device, inode) {
|
|
2712
|
+
return transcriptCursorIdentityReason(cursor, transcriptPath, device, inode);
|
|
2713
|
+
}
|
|
2714
|
+
cursorBoundsReason(handle, cursor, size) {
|
|
2715
|
+
return transcriptCursorBoundsReason(handle, cursor, size);
|
|
2716
|
+
}
|
|
2717
|
+
};
|
|
2718
|
+
var HostProducerDrainCore = class {
|
|
2719
|
+
host;
|
|
2720
|
+
now;
|
|
2721
|
+
tail = Promise.resolve();
|
|
2722
|
+
constructor(host, runtime) {
|
|
2723
|
+
this.host = host;
|
|
2724
|
+
this.now = runtime.now ?? (() => /* @__PURE__ */ new Date());
|
|
2725
|
+
}
|
|
2726
|
+
drainOutbox() {
|
|
2727
|
+
const run = this.tail.then(() => this.drainLocked());
|
|
2728
|
+
this.tail = run.then(() => void 0, () => void 0);
|
|
2729
|
+
return run;
|
|
2730
|
+
}
|
|
2731
|
+
async drainLocked() {
|
|
2732
|
+
const read = await this.host.listEnvelopes();
|
|
2733
|
+
let removed = 0;
|
|
2734
|
+
let expired = 0;
|
|
2735
|
+
for (const envelope of read.entries) {
|
|
2736
|
+
if (Date.parse(this.host.expiresAtOf(envelope)) <= this.now().getTime()) expired += 1;
|
|
2737
|
+
else removed += 1;
|
|
2738
|
+
await this.host.removeEnvelope(envelope);
|
|
2739
|
+
}
|
|
2740
|
+
return { removed, expired, malformed: read.malformed };
|
|
2741
|
+
}
|
|
2823
2742
|
};
|
|
2824
|
-
|
|
2825
|
-
// host/claude/src/claude-decoder.ts
|
|
2826
|
-
var MAX_HOOK_BYTES = 256 * 1024;
|
|
2827
|
-
function claudeLocalTurnRef(sessionId, turnId) {
|
|
2828
|
-
return `claude-turn:${cealRequestSha256({ session_id: sessionId, prompt_id: turnId })}`;
|
|
2829
|
-
}
|
|
2830
|
-
function claudeLocalInputRef(sessionId, turnId) {
|
|
2831
|
-
return `claude-input:${cealRequestSha256({ session_id: sessionId, prompt_id: turnId })}`;
|
|
2832
|
-
}
|
|
2833
|
-
function claudeWorkspaceRef(cwd) {
|
|
2834
|
-
return `workspace:${digest(cwd)}`;
|
|
2835
|
-
}
|
|
2836
|
-
function digest(value) {
|
|
2837
|
-
return createHash4("sha256").update(value, "utf8").digest("hex");
|
|
2838
|
-
}
|
|
2839
2743
|
|
|
2840
2744
|
// host/claude/src/claude-outbox.ts
|
|
2841
|
-
import { mkdir as mkdir3, readdir, readFile
|
|
2842
|
-
import { isAbsolute as isAbsolute2, join as
|
|
2745
|
+
import { mkdir as mkdir3, readdir, readFile, unlink as unlink2 } from "node:fs/promises";
|
|
2746
|
+
import { isAbsolute as isAbsolute2, join as join2 } from "node:path";
|
|
2843
2747
|
|
|
2844
2748
|
// host/invocation-context.ts
|
|
2845
|
-
var
|
|
2749
|
+
var HOST_INVOCATION_TURN_SELECTION_SCHEMA = "ceal.host_invocation_turn_selection.v1";
|
|
2846
2750
|
var HOST_INVOCATION_CONTEXT_REQUIRED_KEYS = ["schema_version", "identity", "prompt_ref", "expires_at"];
|
|
2847
2751
|
function invocationContextKey(identity) {
|
|
2848
2752
|
return cealRequestSha256({ host_source_ref: identity.host_source_ref, host_session_ref: identity.host_session_ref });
|
|
2849
2753
|
}
|
|
2850
2754
|
function hasHostInvocationIdentityKeys(value) {
|
|
2851
|
-
return
|
|
2755
|
+
return exactKeys(value, CEAL_HOST_INVOCATION_IDENTITY_KEYS);
|
|
2852
2756
|
}
|
|
2853
2757
|
function hasHostInvocationContextKeys(value, optional = []) {
|
|
2854
2758
|
return requiredKeys(value, HOST_INVOCATION_CONTEXT_REQUIRED_KEYS, optional);
|
|
@@ -2874,12 +2778,9 @@ function invocationContextFromTurn(identity, promptRef, expiresAt, descriptor) {
|
|
|
2874
2778
|
return Object.freeze(context);
|
|
2875
2779
|
}
|
|
2876
2780
|
function isHostInvocationTurnSelection(value) {
|
|
2877
|
-
return
|
|
2781
|
+
return isRecord3(value) && Object.keys(value).length === 5 && value.schema_version === HOST_INVOCATION_TURN_SELECTION_SCHEMA && ["host_source_ref", "host_session_ref", "prompt_ref", "turn_generation_ref"].every((key) => safeRef(value[key]));
|
|
2878
2782
|
}
|
|
2879
|
-
function
|
|
2880
|
-
return selection === null || context !== null && selection.host_source_ref === context.identity.host_source_ref && selection.host_session_ref === context.identity.host_session_ref && selection.prompt_ref === context.prompt_ref && selection.turn_generation_ref === context.turn_generation_ref;
|
|
2881
|
-
}
|
|
2882
|
-
function exactKeys3(value, keys) {
|
|
2783
|
+
function exactKeys(value, keys) {
|
|
2883
2784
|
const actual = Object.keys(value);
|
|
2884
2785
|
return actual.length === keys.length && keys.every((key) => Object.hasOwn(value, key)) && actual.every((key) => keys.includes(key));
|
|
2885
2786
|
}
|
|
@@ -2888,13 +2789,210 @@ function requiredKeys(value, required, optional) {
|
|
|
2888
2789
|
const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
|
|
2889
2790
|
return actual.length >= required.length && actual.length <= allowed.size && required.every((key) => Object.hasOwn(value, key)) && actual.every((key) => allowed.has(key));
|
|
2890
2791
|
}
|
|
2891
|
-
function
|
|
2792
|
+
function isRecord3(value) {
|
|
2892
2793
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2893
2794
|
}
|
|
2894
|
-
function
|
|
2795
|
+
function safeRef(value) {
|
|
2895
2796
|
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
|
|
2896
2797
|
}
|
|
2897
2798
|
|
|
2799
|
+
// host/relay/src/producer-frame.ts
|
|
2800
|
+
var HOST_PRODUCER_FRAME_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
2801
|
+
var HOST_PRODUCER_FRAME_KINDS = ["session_started", "turn_started", "turn_terminal", "producer_status"];
|
|
2802
|
+
var FRAME_KINDS = new Set(HOST_PRODUCER_FRAME_KINDS);
|
|
2803
|
+
var HOST_PRODUCER_FRAME_ERROR_CODES = [
|
|
2804
|
+
"invalid_frame_encoding",
|
|
2805
|
+
"invalid_frame_length",
|
|
2806
|
+
"frame_truncated",
|
|
2807
|
+
"unsupported_frame_schema",
|
|
2808
|
+
"invalid_frame_shape",
|
|
2809
|
+
"invalid_producer_identity",
|
|
2810
|
+
"frame_too_large"
|
|
2811
|
+
];
|
|
2812
|
+
|
|
2813
|
+
// host/relay/src/durable-atomic-replacement.ts
|
|
2814
|
+
import { lstat as lstat3, mkdir as mkdir2, open as open2, rename, unlink } from "node:fs/promises";
|
|
2815
|
+
import { dirname as dirname3 } from "node:path";
|
|
2816
|
+
|
|
2817
|
+
// host/windows-rename-retry.ts
|
|
2818
|
+
import { setTimeout as setTimeout2 } from "node:timers/promises";
|
|
2819
|
+
var RETRY_WINDOW_MS = 1e4;
|
|
2820
|
+
var RETRY_START_DELAY_MS = 100;
|
|
2821
|
+
var RETRY_MAX_DELAY_MS = 2e3;
|
|
2822
|
+
var RETRYABLE_CODES = /* @__PURE__ */ new Set(["EBUSY", "EPERM", "EACCES"]);
|
|
2823
|
+
var WindowsRenameRetryError = class extends Error {
|
|
2824
|
+
code = "rename_unavailable";
|
|
2825
|
+
constructor(target, attempts, options = {}) {
|
|
2826
|
+
super(`Windows did not permit renaming into place within the bounded retry window: ${target} (after ${attempts} attempts). Wait for the process holding the path to release it, then rerun the same install or update command.`, options);
|
|
2827
|
+
this.name = "WindowsRenameRetryError";
|
|
2828
|
+
}
|
|
2829
|
+
};
|
|
2830
|
+
function resolveRetrySchedule(tuning) {
|
|
2831
|
+
return {
|
|
2832
|
+
windowMs: tuning.windowMs ?? RETRY_WINDOW_MS,
|
|
2833
|
+
startDelayMs: tuning.startDelayMs ?? RETRY_START_DELAY_MS,
|
|
2834
|
+
maxDelayMs: tuning.maxDelayMs ?? RETRY_MAX_DELAY_MS,
|
|
2835
|
+
sleep: tuning.sleep ?? ((ms) => setTimeout2(ms)),
|
|
2836
|
+
now: tuning.now ?? (() => Date.now())
|
|
2837
|
+
};
|
|
2838
|
+
}
|
|
2839
|
+
async function renameWithWindowsRetry(operation, tuning) {
|
|
2840
|
+
const schedule = resolveRetrySchedule(tuning);
|
|
2841
|
+
const deadline = schedule.now() + schedule.windowMs;
|
|
2842
|
+
let delayMs = schedule.startDelayMs;
|
|
2843
|
+
let attempts = 0;
|
|
2844
|
+
for (; ; ) {
|
|
2845
|
+
attempts += 1;
|
|
2846
|
+
try {
|
|
2847
|
+
await operation();
|
|
2848
|
+
return;
|
|
2849
|
+
} catch (error) {
|
|
2850
|
+
if (tuning.platform !== "win32" || !isRetryableRenameError(error)) throw error;
|
|
2851
|
+
await sleepWithinWindow(schedule, deadline, delayMs, () => new WindowsRenameRetryError(tuning.target, attempts, { cause: error }));
|
|
2852
|
+
delayMs = Math.min(delayMs * 2, schedule.maxDelayMs);
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
async function sleepWithinWindow(schedule, deadline, delayMs, exhausted) {
|
|
2857
|
+
if (schedule.now() >= deadline) throw exhausted();
|
|
2858
|
+
await schedule.sleep(Math.min(delayMs, deadline - schedule.now()));
|
|
2859
|
+
if (schedule.now() >= deadline) throw exhausted();
|
|
2860
|
+
}
|
|
2861
|
+
function isRetryableRenameError(error) {
|
|
2862
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" && RETRYABLE_CODES.has(error.code);
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
// host/relay/src/durable-atomic-replacement.ts
|
|
2866
|
+
var DEFAULT_PORT = {
|
|
2867
|
+
platform: process.platform,
|
|
2868
|
+
mkdir: (path, options) => mkdir2(path, options),
|
|
2869
|
+
lstat: (path) => lstat3(path),
|
|
2870
|
+
open_file: (path) => open2(path, "wx", 384),
|
|
2871
|
+
rename,
|
|
2872
|
+
unlink,
|
|
2873
|
+
open_directory: (path) => open2(path),
|
|
2874
|
+
unique_ref: () => `${process.pid}.${crypto.randomUUID()}`
|
|
2875
|
+
};
|
|
2876
|
+
async function durableAtomicReplace(target, data, port = DEFAULT_PORT) {
|
|
2877
|
+
const parent = dirname3(target);
|
|
2878
|
+
await port.mkdir(parent, { recursive: true, mode: 448 });
|
|
2879
|
+
const parentStats = await port.lstat(parent);
|
|
2880
|
+
const expectedUid = typeof process.geteuid === "function" ? process.geteuid() : parentStats.uid;
|
|
2881
|
+
const ownerPrivate = port.platform === "win32" || isOwnedBy(parentStats.uid, expectedUid) && isPrivateFileMode(parentStats.mode, port.platform);
|
|
2882
|
+
if (!parentStats.isDirectory() || parentStats.isSymbolicLink() || !ownerPrivate) 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 }])}.`);
|
|
2883
|
+
const temporary = `${target}.${port.unique_ref()}.new`;
|
|
2884
|
+
let staged;
|
|
2885
|
+
const failures = [];
|
|
2886
|
+
try {
|
|
2887
|
+
staged = await port.open_file(temporary);
|
|
2888
|
+
await staged.writeFile(data, typeof data === "string" ? { encoding: "utf8" } : void 0);
|
|
2889
|
+
await staged.sync();
|
|
2890
|
+
const closing = staged;
|
|
2891
|
+
staged = void 0;
|
|
2892
|
+
await closing.close();
|
|
2893
|
+
await renameWithWindowsRetry(() => port.rename(temporary, target), { ...port.rename_retry, platform: port.platform, target });
|
|
2894
|
+
await syncParentDirectory(parent, port);
|
|
2895
|
+
} catch (error) {
|
|
2896
|
+
failures.push(error);
|
|
2897
|
+
}
|
|
2898
|
+
await closeStaged(staged, failures);
|
|
2899
|
+
await removeTemporary(temporary, port, failures);
|
|
2900
|
+
throwFailures(failures);
|
|
2901
|
+
}
|
|
2902
|
+
async function syncParentDirectory(path, port) {
|
|
2903
|
+
if (port.platform === "win32") return;
|
|
2904
|
+
const handle = await port.open_directory(path);
|
|
2905
|
+
let failure;
|
|
2906
|
+
try {
|
|
2907
|
+
await handle.sync();
|
|
2908
|
+
} catch (error) {
|
|
2909
|
+
if (!directorySyncUnsupported(error)) failure = error;
|
|
2910
|
+
}
|
|
2911
|
+
try {
|
|
2912
|
+
await handle.close();
|
|
2913
|
+
} catch (error) {
|
|
2914
|
+
if (failure === void 0) failure = error;
|
|
2915
|
+
else failure = new AggregateError([failure, error], "Host Relay directory sync and close failed.");
|
|
2916
|
+
}
|
|
2917
|
+
if (failure !== void 0) throw failure;
|
|
2918
|
+
}
|
|
2919
|
+
async function closeStaged(staged, failures) {
|
|
2920
|
+
if (staged !== void 0) try {
|
|
2921
|
+
await staged.close();
|
|
2922
|
+
} catch (error) {
|
|
2923
|
+
failures.push(error);
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
async function removeTemporary(path, port, failures) {
|
|
2927
|
+
try {
|
|
2928
|
+
await port.unlink(path);
|
|
2929
|
+
} catch (error) {
|
|
2930
|
+
if (errorCode2(error) !== "ENOENT") failures.push(error);
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
function throwFailures(failures) {
|
|
2934
|
+
if (failures.length === 1) throw failures[0];
|
|
2935
|
+
if (failures.length > 1) throw new AggregateError(failures, "Host Relay durable replacement and cleanup failed.");
|
|
2936
|
+
}
|
|
2937
|
+
function directorySyncUnsupported(error) {
|
|
2938
|
+
return ["EINVAL", "ENOSYS", "ENOTSUP"].includes(errorCode2(error) ?? "");
|
|
2939
|
+
}
|
|
2940
|
+
function errorCode2(error) {
|
|
2941
|
+
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
// host/relay/src/relay-quarantine-store.ts
|
|
2945
|
+
var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
|
|
2946
|
+
var RELAY_QUARANTINE_FRAME_REASONS = ["frame_identity_conflict", "sequence_gap", "gateway_refused"];
|
|
2947
|
+
var RELAY_QUARANTINE_REASONS = [...RELAY_QUARANTINE_FRAME_REASONS, ...HOST_PRODUCER_FRAME_ERROR_CODES];
|
|
2948
|
+
|
|
2949
|
+
// host/relay/src/relay-admission-store.ts
|
|
2950
|
+
var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
|
|
2951
|
+
var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
|
|
2952
|
+
var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
|
|
2953
|
+
|
|
2954
|
+
// host/relay/src/relay-pipe-handshake.ts
|
|
2955
|
+
var RELAY_PIPE_FRAME_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
2956
|
+
|
|
2957
|
+
// host/relay/src/relay-ipc.ts
|
|
2958
|
+
var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
2959
|
+
var HOST_RELAY_EXCHANGE_FAILURES = vocabulary([
|
|
2960
|
+
"permission_denied",
|
|
2961
|
+
"socket_missing",
|
|
2962
|
+
"connection_refused",
|
|
2963
|
+
"closed_early",
|
|
2964
|
+
"response_invalid",
|
|
2965
|
+
"timed_out",
|
|
2966
|
+
"cancelled",
|
|
2967
|
+
"unavailable"
|
|
2968
|
+
]);
|
|
2969
|
+
|
|
2970
|
+
// host/claude/src/claude-types.ts
|
|
2971
|
+
var CLAUDE_HOST_ERROR_CODES = ["invalid_input", "payload_rejected", "storage", "configuration"];
|
|
2972
|
+
function isClaudeHostErrorCode(value) {
|
|
2973
|
+
return typeof value === "string" && CLAUDE_HOST_ERROR_CODES.some((code2) => code2 === value);
|
|
2974
|
+
}
|
|
2975
|
+
var CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION = "ceal.claude_invocation_context.v1";
|
|
2976
|
+
var CLAUDE_OUTBOX_SCHEMA_VERSION = "ceal.claude_observation_outbox.v1";
|
|
2977
|
+
var CLAUDE_SESSION_SCHEMA_VERSION = "ceal.claude_session.v1";
|
|
2978
|
+
var CLAUDE_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION = "ceal.claude_transcript_registration.v1";
|
|
2979
|
+
var CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION = "ceal.claude_transcript_cursor.v1";
|
|
2980
|
+
var CLAUDE_DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1e3;
|
|
2981
|
+
var CLAUDE_TRANSCRIPT_MAX_TURN_MESSAGES = 1024;
|
|
2982
|
+
var CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES = 64;
|
|
2983
|
+
var CLAUDE_TRANSCRIPT_ANCHOR_BYTES = 256;
|
|
2984
|
+
var ClaudeHostError = class extends Error {
|
|
2985
|
+
name = "ClaudeHostError";
|
|
2986
|
+
code;
|
|
2987
|
+
diagnostic_kind;
|
|
2988
|
+
constructor(code2, message, diagnostic_kind) {
|
|
2989
|
+
super(message);
|
|
2990
|
+
if (!isClaudeHostErrorCode(code2)) throw new TypeError("Claude Host error code is invalid.");
|
|
2991
|
+
this.code = code2;
|
|
2992
|
+
this.diagnostic_kind = diagnostic_kind;
|
|
2993
|
+
}
|
|
2994
|
+
};
|
|
2995
|
+
|
|
2898
2996
|
// host/claude/src/claude-invocation-context.ts
|
|
2899
2997
|
function claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory, turnGenerationRef) {
|
|
2900
2998
|
return invocationContextFromTurn(identity, promptRef, expiresAt, { schema_version: CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION, validate: validateClaudeHostInvocationContext, working_directory: workingDirectory, turn_generation_ref: turnGenerationRef });
|
|
@@ -2906,42 +3004,38 @@ function validateClaudeHostInvocationContext(value) {
|
|
|
2906
3004
|
validateContextTimes(value);
|
|
2907
3005
|
}
|
|
2908
3006
|
function contextShape(value) {
|
|
2909
|
-
return
|
|
3007
|
+
return record(value) && value.schema_version === CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION && hasHostInvocationContextKeys(value, ["working_directory", "turn_generation_ref"]);
|
|
2910
3008
|
}
|
|
2911
3009
|
function validateIdentity2(value) {
|
|
2912
3010
|
if (!validIdentity(value)) throw new ClaudeHostError("storage", "Claude Host invocation identity is malformed.");
|
|
2913
3011
|
}
|
|
2914
3012
|
function validateContextReferences(value) {
|
|
2915
|
-
if (!
|
|
3013
|
+
if (!safeRef2(value.prompt_ref) || !optionalSafeRef(value.turn_generation_ref)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
|
|
2916
3014
|
}
|
|
2917
3015
|
function validateContextTimes(value) {
|
|
2918
|
-
if (!
|
|
2919
|
-
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)}`)}).`);
|
|
3016
|
+
if (!timestamp(value.expires_at) || !optionalDirectory(value.working_directory)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
|
|
2920
3017
|
}
|
|
2921
3018
|
function validIdentity(value) {
|
|
2922
|
-
return
|
|
3019
|
+
return record(value) && hasHostInvocationIdentityKeys(value) && [value.host_session_ref, value.host_turn_ref, value.host_source_ref].every(safeRef2);
|
|
2923
3020
|
}
|
|
2924
3021
|
function optionalSafeRef(value) {
|
|
2925
|
-
return value === void 0 ||
|
|
2926
|
-
}
|
|
2927
|
-
function optionalTimestamp(value) {
|
|
2928
|
-
return value === void 0 || timestamp2(value);
|
|
3022
|
+
return value === void 0 || safeRef2(value);
|
|
2929
3023
|
}
|
|
2930
3024
|
function optionalDirectory(value) {
|
|
2931
3025
|
return value === void 0 || typeof value === "string" && value.startsWith("/") && !value.includes("\0") && value.length <= 4096;
|
|
2932
3026
|
}
|
|
2933
|
-
function
|
|
3027
|
+
function safeRef2(value) {
|
|
2934
3028
|
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
|
|
2935
3029
|
}
|
|
2936
|
-
function
|
|
3030
|
+
function timestamp(value) {
|
|
2937
3031
|
return typeof value === "string" && value.length <= 64 && Number.isFinite(Date.parse(value));
|
|
2938
3032
|
}
|
|
2939
|
-
function
|
|
3033
|
+
function record(value) {
|
|
2940
3034
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2941
3035
|
}
|
|
2942
3036
|
|
|
2943
3037
|
// host/claude/src/claude-outbox.ts
|
|
2944
|
-
var
|
|
3038
|
+
var SAFE_REF3 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
2945
3039
|
var CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS = 4096;
|
|
2946
3040
|
var CLAUDE_TRANSCRIPT_SUBAGENT_NAME_MAX_CHARACTERS = 256;
|
|
2947
3041
|
var ClaudeOutboxStore = class {
|
|
@@ -2955,12 +3049,12 @@ var ClaudeOutboxStore = class {
|
|
|
2955
3049
|
constructor(directory) {
|
|
2956
3050
|
if (!directory.startsWith("/")) throw new ClaudeHostError("configuration", "Claude outbox directory must be absolute.");
|
|
2957
3051
|
this.directory = directory;
|
|
2958
|
-
this.envelopes =
|
|
2959
|
-
this.sessions =
|
|
2960
|
-
this.registrations =
|
|
2961
|
-
this.cursors =
|
|
2962
|
-
this.activeContexts =
|
|
2963
|
-
this.turnSelections =
|
|
3052
|
+
this.envelopes = join2(directory, "envelopes");
|
|
3053
|
+
this.sessions = join2(directory, "sessions");
|
|
3054
|
+
this.registrations = join2(directory, "transcript-registrations");
|
|
3055
|
+
this.cursors = join2(directory, "transcript-cursors");
|
|
3056
|
+
this.activeContexts = join2(directory, "active-contexts");
|
|
3057
|
+
this.turnSelections = join2(directory, "turn-selections");
|
|
2964
3058
|
}
|
|
2965
3059
|
async prepare() {
|
|
2966
3060
|
for (const path of [this.envelopes, this.sessions, this.registrations, this.cursors, this.activeContexts, this.turnSelections]) {
|
|
@@ -2999,10 +3093,10 @@ var ClaudeOutboxStore = class {
|
|
|
2999
3093
|
* a missing one. Every other caller keeps the fail-closed read above.
|
|
3000
3094
|
*/
|
|
3001
3095
|
async readRawInvocationContext(hostSourceRef, hostSessionRef) {
|
|
3002
|
-
if (!
|
|
3096
|
+
if (!SAFE_REF3.test(hostSourceRef) || !SAFE_REF3.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host context identity or clock is invalid.");
|
|
3003
3097
|
await this.prepare();
|
|
3004
3098
|
const context = await readLatestSessionProjection(this.activeContextPath({ host_source_ref: hostSourceRef, host_session_ref: hostSessionRef }), hostSourceRef, hostSessionRef, readProjectionFile);
|
|
3005
|
-
return
|
|
3099
|
+
return context;
|
|
3006
3100
|
}
|
|
3007
3101
|
async listInvocationContexts(now = /* @__PURE__ */ new Date()) {
|
|
3008
3102
|
await this.prepare();
|
|
@@ -3010,9 +3104,9 @@ var ClaudeOutboxStore = class {
|
|
|
3010
3104
|
for (const name of await readdir(this.activeContexts)) {
|
|
3011
3105
|
if (!name.endsWith(".json")) continue;
|
|
3012
3106
|
try {
|
|
3013
|
-
const value = JSON.parse(await
|
|
3107
|
+
const value = JSON.parse(await readFile(join2(this.activeContexts, name), "utf8"));
|
|
3014
3108
|
validateClaudeHostInvocationContext(value);
|
|
3015
|
-
if (Date.parse(value.expires_at) > now.getTime()
|
|
3109
|
+
if (Date.parse(value.expires_at) > now.getTime()) contexts.push(value);
|
|
3016
3110
|
} catch {
|
|
3017
3111
|
}
|
|
3018
3112
|
}
|
|
@@ -3030,7 +3124,7 @@ var ClaudeOutboxStore = class {
|
|
|
3030
3124
|
for (const name of await readdir(this.envelopes)) {
|
|
3031
3125
|
if (!name.endsWith(".json")) continue;
|
|
3032
3126
|
try {
|
|
3033
|
-
const value = JSON.parse(await
|
|
3127
|
+
const value = JSON.parse(await readFile(join2(this.envelopes, name), "utf8"));
|
|
3034
3128
|
validateEnvelope(value);
|
|
3035
3129
|
entries.push(value);
|
|
3036
3130
|
} catch {
|
|
@@ -3051,13 +3145,13 @@ var ClaudeOutboxStore = class {
|
|
|
3051
3145
|
async saveSession(snapshot) {
|
|
3052
3146
|
validateSession(snapshot);
|
|
3053
3147
|
await this.prepare();
|
|
3054
|
-
await durableAtomicReplace(
|
|
3148
|
+
await durableAtomicReplace(join2(this.sessions, `${cealRequestSha256({ source: snapshot.host_source_ref, session: snapshot.host_session_ref })}.json`), JSON.stringify(snapshot));
|
|
3055
3149
|
}
|
|
3056
3150
|
async readSession(sourceRef, sessionRef) {
|
|
3057
|
-
if (!
|
|
3151
|
+
if (!SAFE_REF3.test(sourceRef) || !SAFE_REF3.test(sessionRef)) throw new ClaudeHostError("invalid_input", "Claude session identity is invalid.");
|
|
3058
3152
|
await this.prepare();
|
|
3059
3153
|
try {
|
|
3060
|
-
const value = JSON.parse(await
|
|
3154
|
+
const value = JSON.parse(await readFile(join2(this.sessions, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`), "utf8"));
|
|
3061
3155
|
validateSession(value);
|
|
3062
3156
|
return value;
|
|
3063
3157
|
} catch (error) {
|
|
@@ -3092,7 +3186,7 @@ var ClaudeOutboxStore = class {
|
|
|
3092
3186
|
for (const name of await readdir(this.registrations)) {
|
|
3093
3187
|
if (!name.endsWith(".json")) continue;
|
|
3094
3188
|
try {
|
|
3095
|
-
const value = JSON.parse(await
|
|
3189
|
+
const value = JSON.parse(await readFile(join2(this.registrations, name), "utf8"));
|
|
3096
3190
|
validateRegistration(value);
|
|
3097
3191
|
registrations.push(value);
|
|
3098
3192
|
} catch {
|
|
@@ -3101,10 +3195,10 @@ var ClaudeOutboxStore = class {
|
|
|
3101
3195
|
return registrations.sort((left, right) => left.registered_at.localeCompare(right.registered_at));
|
|
3102
3196
|
}
|
|
3103
3197
|
async readInvocationTurnSelection(hostSourceRef, hostSessionRef) {
|
|
3104
|
-
if (!
|
|
3198
|
+
if (!SAFE_REF3.test(hostSourceRef) || !SAFE_REF3.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host turn selection identity is invalid.");
|
|
3105
3199
|
await this.prepare();
|
|
3106
3200
|
try {
|
|
3107
|
-
const value = JSON.parse(await
|
|
3201
|
+
const value = JSON.parse(await readFile(this.turnSelectionPath(hostSourceRef, hostSessionRef), "utf8"));
|
|
3108
3202
|
if (!isHostInvocationTurnSelection(value) || value.host_source_ref !== hostSourceRef || value.host_session_ref !== hostSessionRef) throw new ClaudeHostError("storage", "Claude Host turn selection is malformed.");
|
|
3109
3203
|
return value;
|
|
3110
3204
|
} catch (error) {
|
|
@@ -3116,7 +3210,7 @@ var ClaudeOutboxStore = class {
|
|
|
3116
3210
|
validateRegistration(registration);
|
|
3117
3211
|
await this.prepare();
|
|
3118
3212
|
try {
|
|
3119
|
-
const value = JSON.parse(await
|
|
3213
|
+
const value = JSON.parse(await readFile(this.cursorPath(registration.host_source_ref, registration.host_session_ref), "utf8"));
|
|
3120
3214
|
validateCursor(value);
|
|
3121
3215
|
return value;
|
|
3122
3216
|
} catch (error) {
|
|
@@ -3130,20 +3224,20 @@ var ClaudeOutboxStore = class {
|
|
|
3130
3224
|
await durableAtomicReplace(this.cursorPath(cursor.host_source_ref, cursor.host_session_ref), JSON.stringify(cursor));
|
|
3131
3225
|
}
|
|
3132
3226
|
envelopePath(ref) {
|
|
3133
|
-
return
|
|
3227
|
+
return join2(this.envelopes, `${cealRequestSha256(ref)}.json`);
|
|
3134
3228
|
}
|
|
3135
3229
|
registrationPath(sourceRef, sessionRef) {
|
|
3136
|
-
return
|
|
3230
|
+
return join2(this.registrations, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
|
|
3137
3231
|
}
|
|
3138
3232
|
turnSelectionPath(sourceRef, sessionRef) {
|
|
3139
|
-
return
|
|
3233
|
+
return join2(this.turnSelections, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
|
|
3140
3234
|
}
|
|
3141
3235
|
cursorPath(sourceRef, sessionRef) {
|
|
3142
|
-
return
|
|
3236
|
+
return join2(this.cursors, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
|
|
3143
3237
|
}
|
|
3144
3238
|
async readRegistrationFile(path) {
|
|
3145
3239
|
try {
|
|
3146
|
-
const value = JSON.parse(await
|
|
3240
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
3147
3241
|
validateRegistration(value);
|
|
3148
3242
|
return value;
|
|
3149
3243
|
} catch (error) {
|
|
@@ -3159,15 +3253,15 @@ var ClaudeOutboxStore = class {
|
|
|
3159
3253
|
}
|
|
3160
3254
|
}
|
|
3161
3255
|
activeContextPath(identity) {
|
|
3162
|
-
return
|
|
3256
|
+
return join2(this.activeContexts, `${invocationContextKey(identity)}.json`);
|
|
3163
3257
|
}
|
|
3164
3258
|
};
|
|
3165
3259
|
function validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now) {
|
|
3166
|
-
if (!
|
|
3260
|
+
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.");
|
|
3167
3261
|
}
|
|
3168
3262
|
async function readProjectionFile(file) {
|
|
3169
3263
|
try {
|
|
3170
|
-
const context = JSON.parse(await
|
|
3264
|
+
const context = JSON.parse(await readFile(file, "utf8"));
|
|
3171
3265
|
validateClaudeHostInvocationContext(context);
|
|
3172
3266
|
return context;
|
|
3173
3267
|
} catch (error) {
|
|
@@ -3176,65 +3270,63 @@ async function readProjectionFile(file) {
|
|
|
3176
3270
|
throw new ClaudeHostError("storage", "Unable to read the Claude Host invocation context.");
|
|
3177
3271
|
}
|
|
3178
3272
|
}
|
|
3179
|
-
function
|
|
3180
|
-
return typeof value === "string" &&
|
|
3273
|
+
function safeRef3(value) {
|
|
3274
|
+
return typeof value === "string" && SAFE_REF3.test(value);
|
|
3181
3275
|
}
|
|
3182
|
-
function
|
|
3276
|
+
function timestamp2(value) {
|
|
3183
3277
|
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
3184
3278
|
}
|
|
3185
|
-
function
|
|
3279
|
+
function record2(value) {
|
|
3186
3280
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3187
3281
|
}
|
|
3188
3282
|
function code(error) {
|
|
3189
|
-
return
|
|
3283
|
+
return record2(error) && typeof error.code === "string" ? error.code : null;
|
|
3190
3284
|
}
|
|
3191
3285
|
function validateEnvelope(value) {
|
|
3192
|
-
if (!
|
|
3193
|
-
if (!sameKeys(value, ["schema_version", "identity", "host_input_ref", "
|
|
3286
|
+
if (!record2(value)) throw new ClaudeHostError("storage", "Claude outbox entry is malformed.");
|
|
3287
|
+
if (!sameKeys(value, ["schema_version", "identity", "host_input_ref", "prompt_ref", "observations", "origin_input", "submission_ref", "created_at", "expires_at"])) {
|
|
3194
3288
|
throw new ClaudeHostError("storage", `Claude outbox entry key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the outbox entry key set")}).`);
|
|
3195
3289
|
}
|
|
3196
3290
|
if (value.schema_version !== CLAUDE_OUTBOX_SCHEMA_VERSION) throw new ClaudeHostError("storage", `Claude outbox entry is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CLAUDE_OUTBOX_SCHEMA_VERSION}`)}).`);
|
|
3197
3291
|
validateEnvelopeIdentity(value);
|
|
3198
3292
|
validateEnvelopeObservations(value.observations);
|
|
3199
3293
|
if (typeof value.origin_input !== "boolean") throw new ClaudeHostError("storage", "Claude outbox origin disposition is malformed.");
|
|
3200
|
-
if (!
|
|
3294
|
+
if (!timestamp2(value.created_at) || !timestamp2(value.expires_at)) throw new ClaudeHostError("storage", "Claude outbox timestamps are malformed.");
|
|
3201
3295
|
}
|
|
3202
3296
|
function validateEnvelopeIdentity(value) {
|
|
3203
3297
|
validateIdentity3(value.identity);
|
|
3204
|
-
if (!
|
|
3205
|
-
if (!
|
|
3206
|
-
if (!safeRef4(value.prompt_ref) || !safeRef4(value.submission_ref)) throw new ClaudeHostError("storage", "Claude outbox references are malformed.");
|
|
3207
|
-
if (value.request_ref !== void 0 && !safeRef4(value.request_ref)) throw new ClaudeHostError("storage", "Claude outbox Request reference is malformed.");
|
|
3298
|
+
if (!safeRef3(value.host_input_ref)) throw new ClaudeHostError("storage", "Claude outbox input reference is malformed.");
|
|
3299
|
+
if (!safeRef3(value.prompt_ref) || !safeRef3(value.submission_ref)) throw new ClaudeHostError("storage", "Claude outbox references are malformed.");
|
|
3208
3300
|
}
|
|
3209
3301
|
function validateEnvelopeObservations(value) {
|
|
3210
3302
|
if (!Array.isArray(value)) throw new ClaudeHostError("storage", "Claude outbox observations are malformed.");
|
|
3211
3303
|
try {
|
|
3212
|
-
for (const
|
|
3304
|
+
for (const observation of value) validateCealHostObservationDraft(observation);
|
|
3213
3305
|
} catch {
|
|
3214
3306
|
throw new ClaudeHostError("storage", "Claude outbox observations are malformed.");
|
|
3215
3307
|
}
|
|
3216
3308
|
}
|
|
3217
3309
|
function validateSession(value) {
|
|
3218
|
-
if (!
|
|
3310
|
+
if (!record2(value)) throw new ClaudeHostError("storage", "Claude session snapshot is malformed.");
|
|
3219
3311
|
if (!sameKeys(value, ["schema_version", "host_session_ref", "host_source_ref", "observed_at", "value", "expires_at"])) {
|
|
3220
3312
|
throw new ClaudeHostError("storage", `Claude session snapshot key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the session snapshot key set")}).`);
|
|
3221
3313
|
}
|
|
3222
3314
|
if (value.schema_version !== CLAUDE_SESSION_SCHEMA_VERSION) throw new ClaudeHostError("storage", `Claude session snapshot is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CLAUDE_SESSION_SCHEMA_VERSION}`)}).`);
|
|
3223
|
-
if (!
|
|
3315
|
+
if (!safeRef3(value.host_session_ref) || !safeRef3(value.host_source_ref) || !timestamp2(value.observed_at) || !timestamp2(value.expires_at)) {
|
|
3224
3316
|
throw new ClaudeHostError("storage", "Claude session snapshot fields are malformed.");
|
|
3225
3317
|
}
|
|
3226
3318
|
}
|
|
3227
3319
|
function validateRegistration(value) {
|
|
3228
|
-
if (!
|
|
3320
|
+
if (!record2(value)) throw new ClaudeHostError("storage", "Claude transcript registration is malformed.");
|
|
3229
3321
|
if (!sameKeys(value, ["schema_version", "host_source_ref", "host_session_ref", "claude_session_id", "transcript_path", "registered_at", "expires_at"], ["cwd"])) {
|
|
3230
3322
|
throw new ClaudeHostError("storage", `Claude transcript registration key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the registration key set")}).`);
|
|
3231
3323
|
}
|
|
3232
3324
|
if (value.schema_version !== CLAUDE_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION) throw new ClaudeHostError("storage", "Claude transcript registration is malformed.");
|
|
3233
|
-
if (![value.host_source_ref, value.host_session_ref, value.claude_session_id].every((candidate) => typeof candidate === "string" &&
|
|
3325
|
+
if (![value.host_source_ref, value.host_session_ref, value.claude_session_id].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) {
|
|
3234
3326
|
throw new ClaudeHostError("storage", "Claude transcript registration references are malformed.");
|
|
3235
3327
|
}
|
|
3236
3328
|
validateRegistrationPaths(value.cwd, value.transcript_path);
|
|
3237
|
-
if (!
|
|
3329
|
+
if (!timestamp2(value.registered_at) || !timestamp2(value.expires_at)) throw new ClaudeHostError("storage", "Claude transcript registration timestamps are malformed.");
|
|
3238
3330
|
}
|
|
3239
3331
|
function validateRegistrationPaths(cwd, transcriptPath) {
|
|
3240
3332
|
if (cwd !== void 0 && (typeof cwd !== "string" || !isAbsolute2(cwd) || cwd.length > CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS || cwd.includes("\0"))) {
|
|
@@ -3245,12 +3337,12 @@ function validateRegistrationPaths(cwd, transcriptPath) {
|
|
|
3245
3337
|
}
|
|
3246
3338
|
}
|
|
3247
3339
|
function validateCursor(value) {
|
|
3248
|
-
if (!
|
|
3340
|
+
if (!record2(value)) throw new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
|
|
3249
3341
|
if (!sameKeys(value, ["schema_version", "host_source_ref", "host_session_ref", "transcript_path", "offset", "anchor_start", "anchor_sha256", "subagents", "status", "updated_at"], ["device", "inode", "open_turn", "unattributed", "session_observed", "reason"])) {
|
|
3250
3342
|
throw new ClaudeHostError("storage", "Claude transcript cursor key set is malformed.");
|
|
3251
3343
|
}
|
|
3252
3344
|
if (value.schema_version !== CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION) throw new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
|
|
3253
|
-
if (![value.host_source_ref, value.host_session_ref].every((candidate) => typeof candidate === "string" &&
|
|
3345
|
+
if (![value.host_source_ref, value.host_session_ref].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) throw new ClaudeHostError("storage", "Claude transcript cursor references are malformed.");
|
|
3254
3346
|
if (typeof value.transcript_path !== "string" || value.transcript_path.length === 0) throw new ClaudeHostError("storage", "Claude transcript cursor path is malformed.");
|
|
3255
3347
|
validateCursorPosition(value);
|
|
3256
3348
|
validateCursorTurn(value.open_turn, value.unattributed, value.session_observed);
|
|
@@ -3262,7 +3354,7 @@ function validateCursorPosition(value) {
|
|
|
3262
3354
|
if (value.device !== void 0 && typeof value.device !== "string") throw new ClaudeHostError("storage", "Claude transcript cursor device is malformed.");
|
|
3263
3355
|
if (value.inode !== void 0 && typeof value.inode !== "string") throw new ClaudeHostError("storage", "Claude transcript cursor inode is malformed.");
|
|
3264
3356
|
if (value.device === void 0 && value.offset !== 0) throw new ClaudeHostError("storage", "Claude transcript cursor is unpinned past its start.");
|
|
3265
|
-
if (value.status !== "active" && value.status !== "quarantined" || !
|
|
3357
|
+
if (value.status !== "active" && value.status !== "quarantined" || !timestamp2(value.updated_at)) throw new ClaudeHostError("storage", "Claude transcript cursor status is malformed.");
|
|
3266
3358
|
}
|
|
3267
3359
|
function validateCursorTurn(openTurn, unattributed, sessionObserved) {
|
|
3268
3360
|
if (openTurn !== void 0 && !validOpenTurn(openTurn)) throw new ClaudeHostError("storage", "Claude transcript turn cursor is malformed.");
|
|
@@ -3270,13 +3362,13 @@ function validateCursorTurn(openTurn, unattributed, sessionObserved) {
|
|
|
3270
3362
|
if (sessionObserved !== void 0 && typeof sessionObserved !== "boolean") throw new ClaudeHostError("storage", "Claude transcript cursor session marker is malformed.");
|
|
3271
3363
|
}
|
|
3272
3364
|
function validateSubagentCursors(value) {
|
|
3273
|
-
if (!
|
|
3365
|
+
if (!record2(value)) throw new ClaudeHostError("storage", "Claude transcript subagent cursors are malformed.");
|
|
3274
3366
|
for (const [name, entry] of Object.entries(value)) validateSubagentEntry(name, entry);
|
|
3275
3367
|
}
|
|
3276
3368
|
function validateSubagentEntry(name, entry) {
|
|
3277
3369
|
if (name.length === 0 || name.length > CLAUDE_TRANSCRIPT_SUBAGENT_NAME_MAX_CHARACTERS || name.includes("/")) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
|
|
3278
3370
|
if (name.includes("\0")) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
|
|
3279
|
-
if (!
|
|
3371
|
+
if (!record2(entry) || !sameKeys(entry, ["offset", "anchor_start", "anchor_sha256"], ["device", "inode"]) || !nonNegativeInteger(entry.offset)) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
|
|
3280
3372
|
validateSubagentIdentity(entry);
|
|
3281
3373
|
}
|
|
3282
3374
|
function validateSubagentIdentity(entry) {
|
|
@@ -3292,9 +3384,9 @@ function validCursorPosition(value) {
|
|
|
3292
3384
|
return nonNegativeInteger(value.offset) && nonNegativeInteger(value.anchor_start) && typeof value.anchor_sha256 === "string" && /^[a-f0-9]{64}$/u.test(value.anchor_sha256);
|
|
3293
3385
|
}
|
|
3294
3386
|
function validOpenTurn(value) {
|
|
3295
|
-
if (!
|
|
3296
|
-
if (![value.turn_key, value.user_uuid, value.prompt_ref, value.host_input_ref].every((candidate) => typeof candidate === "string" &&
|
|
3297
|
-
if (!
|
|
3387
|
+
if (!record2(value) || !sameKeys(value, ["turn_key", "user_uuid", "prompt_ref", "host_input_ref", "started_at", "last_observed_at", "message_ids", "counts"], ["last_terminal_digest"])) return false;
|
|
3388
|
+
if (![value.turn_key, value.user_uuid, value.prompt_ref, value.host_input_ref].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) return false;
|
|
3389
|
+
if (!timestamp2(value.started_at) || !timestamp2(value.last_observed_at)) return false;
|
|
3298
3390
|
if (!validMessageIds(value.message_ids)) return false;
|
|
3299
3391
|
if (!validTurnCounts(value.counts)) return false;
|
|
3300
3392
|
return value.last_terminal_digest === void 0 || typeof value.last_terminal_digest === "string" && /^[a-f0-9]{64}$/u.test(value.last_terminal_digest);
|
|
@@ -3305,17 +3397,17 @@ function validMessageIds(value) {
|
|
|
3305
3397
|
return new Set(value).size === value.length;
|
|
3306
3398
|
}
|
|
3307
3399
|
function validUnattributed(value) {
|
|
3308
|
-
if (!
|
|
3400
|
+
if (!record2(value) || !sameKeys(value, ["input", "cached_input", "cache_write_input", "output", "message_ids", "last_observed_at"])) return false;
|
|
3309
3401
|
if (!validMessageIds(value.message_ids) || value.message_ids.length === 0) return false;
|
|
3310
|
-
if (!
|
|
3402
|
+
if (!timestamp2(value.last_observed_at)) return false;
|
|
3311
3403
|
return validTurnCounts(value);
|
|
3312
3404
|
}
|
|
3313
3405
|
function validTurnCounts(value) {
|
|
3314
|
-
if (!
|
|
3406
|
+
if (!record2(value) || !sameKeys(value, ["input", "cached_input", "cache_write_input", "output"])) return false;
|
|
3315
3407
|
return [value.input, value.cached_input, value.cache_write_input, value.output].every(nonNegativeInteger);
|
|
3316
3408
|
}
|
|
3317
3409
|
function validateIdentity3(value) {
|
|
3318
|
-
if (!
|
|
3410
|
+
if (!record2(value) || ![value.host_session_ref, value.host_turn_ref, value.host_source_ref].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) {
|
|
3319
3411
|
throw new ClaudeHostError("storage", "Claude Host identity is malformed.");
|
|
3320
3412
|
}
|
|
3321
3413
|
}
|
|
@@ -3328,125 +3420,101 @@ function sameKeys(value, required, optional = []) {
|
|
|
3328
3420
|
|
|
3329
3421
|
// host/claude/src/claude-producer.ts
|
|
3330
3422
|
var ClaudeProducer = class {
|
|
3331
|
-
producer_id = "claude";
|
|
3332
|
-
producer_label = "Claude";
|
|
3333
|
-
outbox_directory;
|
|
3334
3423
|
store;
|
|
3335
3424
|
core;
|
|
3336
3425
|
constructor(options) {
|
|
3337
3426
|
this.store = new ClaudeOutboxStore(options.outbox_dir);
|
|
3338
|
-
this.outbox_directory = this.store.directory;
|
|
3339
3427
|
this.core = new HostProducerDrainCore(this, options);
|
|
3340
3428
|
}
|
|
3341
3429
|
drainOutbox() {
|
|
3342
3430
|
return this.core.drainOutbox();
|
|
3343
3431
|
}
|
|
3344
|
-
fail(code2, message) {
|
|
3345
|
-
return new ClaudeHostError(code2, message);
|
|
3346
|
-
}
|
|
3347
3432
|
listEnvelopes() {
|
|
3348
3433
|
return this.store.listEnvelopes();
|
|
3349
3434
|
}
|
|
3350
3435
|
removeEnvelope(envelope) {
|
|
3351
3436
|
return this.store.removeEnvelope(envelope);
|
|
3352
3437
|
}
|
|
3353
|
-
submissionRefOf(envelope) {
|
|
3354
|
-
return envelope.submission_ref;
|
|
3355
|
-
}
|
|
3356
3438
|
expiresAtOf(envelope) {
|
|
3357
3439
|
return envelope.expires_at;
|
|
3358
3440
|
}
|
|
3359
|
-
createdAtOf(envelope) {
|
|
3360
|
-
return envelope.created_at;
|
|
3361
|
-
}
|
|
3362
|
-
async mapEnvelope(envelope) {
|
|
3363
|
-
const prompt = observation(envelope, "prompt");
|
|
3364
|
-
if (prompt !== void 0) return this.mapInput(envelope, prompt);
|
|
3365
|
-
return [mapTerminal(envelope)];
|
|
3366
|
-
}
|
|
3367
|
-
async mapInput(envelope, prompt) {
|
|
3368
|
-
const drafts = [];
|
|
3369
|
-
const session = observation(envelope, "session");
|
|
3370
|
-
if (session !== void 0) {
|
|
3371
|
-
const registrations = await this.store.listTranscriptRegistrations();
|
|
3372
|
-
const registration = registrations.find((item) => item.host_source_ref === envelope.identity.host_source_ref && item.host_session_ref === envelope.identity.host_session_ref);
|
|
3373
|
-
const directoryIdentity = registration?.cwd ?? envelope.identity.host_session_ref;
|
|
3374
|
-
drafts.push({
|
|
3375
|
-
frame_kind: "session_started",
|
|
3376
|
-
payload: compact({
|
|
3377
|
-
local_session_ref: envelope.identity.host_session_ref,
|
|
3378
|
-
working_directory_ref: claudeWorkspaceRef(directoryIdentity),
|
|
3379
|
-
model: recordText(session.value, "model"),
|
|
3380
|
-
observations: [session]
|
|
3381
|
-
})
|
|
3382
|
-
});
|
|
3383
|
-
}
|
|
3384
|
-
const turn = observation(envelope, "turn");
|
|
3385
|
-
if (turn !== void 0) drafts.push({
|
|
3386
|
-
frame_kind: "turn_started",
|
|
3387
|
-
payload: compact({
|
|
3388
|
-
local_session_ref: envelope.identity.host_session_ref,
|
|
3389
|
-
local_turn_ref: localTurnRef(envelope),
|
|
3390
|
-
observations: [turn]
|
|
3391
|
-
})
|
|
3392
|
-
});
|
|
3393
|
-
if (!isCealHostPromptObservationValue(prompt.value)) throw new ClaudeHostError("storage", "Claude prompt observation is malformed.");
|
|
3394
|
-
drafts.push({
|
|
3395
|
-
frame_kind: "user_input",
|
|
3396
|
-
payload: compact({
|
|
3397
|
-
local_session_ref: envelope.identity.host_session_ref,
|
|
3398
|
-
local_turn_ref: localTurnRef(envelope),
|
|
3399
|
-
local_input_ref: envelope.host_input_ref,
|
|
3400
|
-
input_kind: envelope.input_kind,
|
|
3401
|
-
origin_input: envelope.origin_input,
|
|
3402
|
-
observations: [prompt],
|
|
3403
|
-
context_expires_at: envelope.expires_at
|
|
3404
|
-
})
|
|
3405
|
-
});
|
|
3406
|
-
return drafts;
|
|
3407
|
-
}
|
|
3408
3441
|
};
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
return envelope.observations.find((item) => item.observation_kind === kind);
|
|
3424
|
-
}
|
|
3425
|
-
function localTurnRef(envelope) {
|
|
3426
|
-
return envelope.identity.host_turn_ref;
|
|
3442
|
+
|
|
3443
|
+
// host/claude/src/claude-transcript-watcher.ts
|
|
3444
|
+
import { readdir as readdir2 } from "node:fs/promises";
|
|
3445
|
+
import { join as join3 } from "node:path";
|
|
3446
|
+
|
|
3447
|
+
// host/claude/src/claude-decoder.ts
|
|
3448
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
3449
|
+
var MAX_HOOK_BYTES = 256 * 1024;
|
|
3450
|
+
function decodeClaudeQueuedPromptRow(row, sessionId) {
|
|
3451
|
+
if (!record3(row)) return null;
|
|
3452
|
+
if (row.type === "user") return decodeArrayTextPrompt(row, sessionId);
|
|
3453
|
+
if (row.type === "attachment") return decodeQueuedCommandPrompt(row, sessionId);
|
|
3454
|
+
if (row.type === "queue-operation") return decodeQueueEnqueuePrompt(row, sessionId);
|
|
3455
|
+
return null;
|
|
3427
3456
|
}
|
|
3428
|
-
function
|
|
3429
|
-
|
|
3457
|
+
function decodeArrayTextPrompt(row, sessionId) {
|
|
3458
|
+
if (!record3(row.message) || !Array.isArray(row.message.content)) return null;
|
|
3459
|
+
if (!sessionMatches(row, sessionId)) return null;
|
|
3460
|
+
if (row.isMeta === true && (!record3(row.origin) || typeof row.origin.kind !== "string")) return null;
|
|
3461
|
+
const texts = arrayTextBlocks(row.message.content);
|
|
3462
|
+
if (texts === null) return null;
|
|
3463
|
+
const identity = queuedRowIdentity(row);
|
|
3464
|
+
if (identity === null) return null;
|
|
3465
|
+
return { turnId: identity.uuid, timestamp: identity.timestamp, content: texts.join("") };
|
|
3466
|
+
}
|
|
3467
|
+
function arrayTextBlocks(content) {
|
|
3468
|
+
const texts = [];
|
|
3469
|
+
for (const block of content) {
|
|
3470
|
+
if (!record3(block) || block.type !== "text" || typeof block.text !== "string") return null;
|
|
3471
|
+
texts.push(block.text);
|
|
3472
|
+
}
|
|
3473
|
+
return texts.length === 0 ? null : texts;
|
|
3474
|
+
}
|
|
3475
|
+
function decodeQueuedCommandPrompt(row, sessionId) {
|
|
3476
|
+
if (!record3(row.attachment) || row.attachment.type !== "queued_command" || row.attachment.commandMode !== "prompt") return null;
|
|
3477
|
+
if (typeof row.attachment.prompt !== "string" || row.attachment.prompt.length === 0) return null;
|
|
3478
|
+
if (!sessionMatches(row, sessionId)) return null;
|
|
3479
|
+
const identity = queuedRowIdentity(row);
|
|
3480
|
+
if (identity === null) return null;
|
|
3481
|
+
return { turnId: identity.uuid, timestamp: identity.timestamp, content: row.attachment.prompt };
|
|
3482
|
+
}
|
|
3483
|
+
function decodeQueueEnqueuePrompt(row, sessionId) {
|
|
3484
|
+
if (row.operation !== "enqueue" || typeof row.content !== "string" || row.content.length === 0) return null;
|
|
3485
|
+
if (!sessionMatches(row, sessionId)) return null;
|
|
3486
|
+
if (typeof row.timestamp !== "string" || !Number.isFinite(Date.parse(row.timestamp))) return null;
|
|
3487
|
+
return { turnId: queueEnqueueTurnId(sessionId, row.timestamp, row.content), timestamp: row.timestamp, content: row.content };
|
|
3488
|
+
}
|
|
3489
|
+
function sessionMatches(row, sessionId) {
|
|
3490
|
+
const hasSessionId = Object.hasOwn(row, "sessionId");
|
|
3491
|
+
const hasSnakeSessionId = Object.hasOwn(row, "session_id");
|
|
3492
|
+
return (hasSessionId || hasSnakeSessionId) && (!hasSessionId || row.sessionId === sessionId) && (!hasSnakeSessionId || row.session_id === sessionId);
|
|
3493
|
+
}
|
|
3494
|
+
function queuedRowIdentity(row) {
|
|
3495
|
+
if (typeof row.uuid !== "string" || row.uuid.length === 0 || row.uuid.length > 256) return null;
|
|
3496
|
+
if (typeof row.timestamp !== "string" || !Number.isFinite(Date.parse(row.timestamp))) return null;
|
|
3497
|
+
return { uuid: row.uuid, timestamp: row.timestamp };
|
|
3498
|
+
}
|
|
3499
|
+
function queueEnqueueTurnId(sessionId, timestamp3, content) {
|
|
3500
|
+
return `queue-enqueue:${createHash5("sha256").update(`${sessionId}
|
|
3501
|
+
${timestamp3}
|
|
3502
|
+
${content}`, "utf8").digest("hex").slice(0, 32)}`;
|
|
3430
3503
|
}
|
|
3431
|
-
function
|
|
3432
|
-
return
|
|
3504
|
+
function claudeLocalTurnRef(sessionId, turnId) {
|
|
3505
|
+
return `claude-turn:${cealRequestSha256({ session_id: sessionId, prompt_id: turnId })}`;
|
|
3433
3506
|
}
|
|
3434
|
-
function
|
|
3435
|
-
|
|
3436
|
-
return typeof record5?.[key] === "string" ? record5[key] : void 0;
|
|
3507
|
+
function claudeLocalInputRef(sessionId, turnId) {
|
|
3508
|
+
return `claude-input:${cealRequestSha256({ session_id: sessionId, prompt_id: turnId })}`;
|
|
3437
3509
|
}
|
|
3438
|
-
function
|
|
3510
|
+
function record3(value) {
|
|
3439
3511
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3440
3512
|
}
|
|
3441
3513
|
|
|
3442
3514
|
// host/claude/src/claude-transcript-watcher.ts
|
|
3443
|
-
import { O_NOFOLLOW as O_NOFOLLOW2, O_RDONLY as O_RDONLY3 } from "node:constants";
|
|
3444
|
-
import { open as open5, readdir as readdir2 } from "node:fs/promises";
|
|
3445
|
-
import { join as join4 } from "node:path";
|
|
3446
3515
|
var TOKEN_OBSERVATION_SOURCE = "claude_transcript.message.usage";
|
|
3447
3516
|
var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
3448
3517
|
store;
|
|
3449
|
-
registerCurrentPrompt;
|
|
3450
3518
|
diagnostic;
|
|
3451
3519
|
constructor(options) {
|
|
3452
3520
|
super(options, {
|
|
@@ -3455,7 +3523,6 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3455
3523
|
clock_failure: () => new ClaudeHostError("storage", "Claude watcher clock is invalid.")
|
|
3456
3524
|
});
|
|
3457
3525
|
this.store = new ClaudeOutboxStore(options.outbox_dir);
|
|
3458
|
-
this.registerCurrentPrompt = options.register_current_prompt;
|
|
3459
3526
|
this.diagnostic = options.diagnostic ?? (() => void 0);
|
|
3460
3527
|
}
|
|
3461
3528
|
transcriptPathOf(registration) {
|
|
@@ -3468,13 +3535,15 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3468
3535
|
const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
|
|
3469
3536
|
const cursor = await this.preflight(registration, handle, size, device, inode);
|
|
3470
3537
|
if (cursor === null) return { records: 0, enqueued: 0, quarantined: true };
|
|
3471
|
-
if (cursor.status === "quarantined")
|
|
3538
|
+
if (cursor.status === "quarantined") {
|
|
3539
|
+
return { records: 0, enqueued: 0, quarantined: true };
|
|
3540
|
+
}
|
|
3472
3541
|
const draft = mutableCursor(cursor);
|
|
3473
3542
|
await this.scanOpenedTranscript(registration, handle, draft, size, device, inode);
|
|
3474
3543
|
return { records: draft.consumed, enqueued: draft.enqueued, quarantined: false };
|
|
3475
3544
|
}
|
|
3476
3545
|
async scanFailed(handle, registration, error) {
|
|
3477
|
-
this.diagnostic("watcher_scan_failed", claudeTranscriptScanFailureKind(error));
|
|
3546
|
+
this.diagnostic("watcher_scan_failed", claudeTranscriptScanFailureKind(error), claudeTranscriptScanCause(handle, error));
|
|
3478
3547
|
if (handle === void 0 && isSymlinkTranscript(error)) return await this.quarantineUnopened(registration, "transcript_malformed");
|
|
3479
3548
|
if (error instanceof ClaudeHostError && error.code === "payload_rejected") return await this.quarantineDraft(await this.preservedCursor(registration));
|
|
3480
3549
|
await this.quarantineFailure(registration, handle);
|
|
@@ -3522,55 +3591,72 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3522
3591
|
async consumeMainChunk(registration, complete, draft) {
|
|
3523
3592
|
for (const line of complete.toString("utf8").split("\n").filter((value) => value.length > 0)) {
|
|
3524
3593
|
draft.consumed += 1;
|
|
3525
|
-
const
|
|
3526
|
-
if (
|
|
3527
|
-
if (isUserTurnLine(
|
|
3528
|
-
else if (isAssistantUsageLine(
|
|
3529
|
-
else if (isAssistantLine(
|
|
3594
|
+
const record4 = parseTranscriptLine(line);
|
|
3595
|
+
if (record4 === null) throw new ClaudeHostError("payload_rejected", "Claude transcript line is not valid JSON.", "malformed_line");
|
|
3596
|
+
if (isUserTurnLine(record4)) await this.openTurn(registration, draft, record4);
|
|
3597
|
+
else if (isAssistantUsageLine(record4)) this.accumulateUsage(draft, toUsage(record4.uuid, record4.timestamp, record4.message.usage));
|
|
3598
|
+
else if (isAssistantLine(record4) && record4.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude transcript usage is malformed.");
|
|
3599
|
+
else {
|
|
3600
|
+
const queued = decodeClaudeQueuedPromptRow(record4, registration.claude_session_id);
|
|
3601
|
+
if (queued !== null) await this.openQueuedTurn(registration, draft, queued);
|
|
3602
|
+
}
|
|
3530
3603
|
}
|
|
3531
3604
|
}
|
|
3532
|
-
async openTurn(registration, draft,
|
|
3533
|
-
this.
|
|
3534
|
-
|
|
3605
|
+
async openTurn(registration, draft, record4) {
|
|
3606
|
+
await this.openPromptTurn(registration, draft, { uuid: record4.uuid, timestamp: record4.timestamp, content: record4.message.content });
|
|
3607
|
+
}
|
|
3608
|
+
async openQueuedTurn(registration, draft, queued) {
|
|
3609
|
+
await this.openPromptTurn(registration, draft, { uuid: queued.turnId, timestamp: queued.timestamp, content: queued.content });
|
|
3610
|
+
}
|
|
3611
|
+
async openPromptTurn(registration, draft, prompt) {
|
|
3612
|
+
await this.closeTurn(registration, draft, prompt.timestamp);
|
|
3535
3613
|
const sessionRef = registration.host_session_ref;
|
|
3536
|
-
const turnRef = claudeLocalTurnRef(registration.claude_session_id,
|
|
3537
|
-
const inputRef = claudeLocalInputRef(registration.claude_session_id,
|
|
3538
|
-
const promptRef = hostProducerPromptRef(this.sha(
|
|
3614
|
+
const turnRef = claudeLocalTurnRef(registration.claude_session_id, prompt.uuid);
|
|
3615
|
+
const inputRef = claudeLocalInputRef(registration.claude_session_id, prompt.uuid);
|
|
3616
|
+
const promptRef = hostProducerPromptRef(this.sha(prompt.content));
|
|
3539
3617
|
const identity = { host_source_ref: registration.host_source_ref, host_session_ref: sessionRef, host_turn_ref: turnRef };
|
|
3540
3618
|
const observations = [
|
|
3541
|
-
{ observation_ref: observationRef("transcript_user_text", inputRef,
|
|
3542
|
-
{ observation_ref: observationRef("transcript_turn_started", turnRef,
|
|
3619
|
+
{ observation_ref: observationRef(registration.host_source_ref, "transcript_user_text", inputRef, prompt.content), observation_kind: "prompt", observed_at: prompt.timestamp, value: { body_sha256: this.sha(prompt.content) } },
|
|
3620
|
+
{ observation_ref: observationRef(registration.host_source_ref, "transcript_turn_started", turnRef, prompt.uuid), observation_kind: "turn", observed_at: prompt.timestamp, value: { status: "started", host_session_id_sha256: this.sha(registration.claude_session_id), host_turn_ref: turnRef } }
|
|
3543
3621
|
];
|
|
3544
3622
|
if (!draft.session_observed) {
|
|
3545
3623
|
const session = await this.store.readSession(registration.host_source_ref, sessionRef);
|
|
3546
|
-
if (session !== null) observations.push({ observation_ref: observationRef("transcript_session", sessionRef, session.value), observation_kind: "session", observed_at:
|
|
3624
|
+
if (session !== null) observations.push({ observation_ref: observationRef(registration.host_source_ref, "transcript_session", sessionRef, session.value), observation_kind: "session", observed_at: prompt.timestamp, value: session.value });
|
|
3547
3625
|
draft.session_observed = true;
|
|
3548
3626
|
}
|
|
3627
|
+
await this.persistOpenedTurnContext(draft, { uuid: prompt.uuid, timestamp: prompt.timestamp, message: { content: prompt.content } }, inputRef, turnRef, promptRef, identity, observations);
|
|
3628
|
+
}
|
|
3629
|
+
async persistOpenedTurnContext(draft, record4, inputRef, turnRef, promptRef, identity, observations) {
|
|
3630
|
+
const expiresAt = this.expire(this.outboxRetentionMs);
|
|
3549
3631
|
await this.store.putEnvelope({
|
|
3550
3632
|
schema_version: "ceal.claude_observation_outbox.v1",
|
|
3551
3633
|
identity,
|
|
3552
3634
|
host_input_ref: inputRef,
|
|
3553
|
-
input_kind: "human",
|
|
3554
3635
|
prompt_ref: promptRef,
|
|
3555
3636
|
observations,
|
|
3556
3637
|
origin_input: true,
|
|
3557
3638
|
submission_ref: submissionRef("input", turnRef, inputRef, promptRef),
|
|
3558
|
-
created_at:
|
|
3559
|
-
expires_at:
|
|
3639
|
+
created_at: record4.timestamp,
|
|
3640
|
+
expires_at: expiresAt
|
|
3560
3641
|
});
|
|
3561
3642
|
draft.enqueued += 1;
|
|
3562
|
-
await this.
|
|
3643
|
+
await this.activateOpenedTurnContext(identity, promptRef, expiresAt);
|
|
3644
|
+
this.setOpenedTurn(draft, record4, inputRef, turnRef, promptRef);
|
|
3645
|
+
}
|
|
3646
|
+
async activateOpenedTurnContext(identity, promptRef, expiresAt) {
|
|
3563
3647
|
const selection = await this.store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
|
|
3564
3648
|
const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
|
|
3565
|
-
await this.activateContext(identity, promptRef, turnGenerationRef);
|
|
3649
|
+
await this.activateContext(identity, promptRef, turnGenerationRef, expiresAt);
|
|
3650
|
+
}
|
|
3651
|
+
setOpenedTurn(draft, record4, inputRef, turnRef, promptRef) {
|
|
3566
3652
|
const folded = foldUnattributed(draft.unattributed);
|
|
3567
3653
|
draft.open_turn = {
|
|
3568
3654
|
turn_key: turnRef,
|
|
3569
|
-
user_uuid:
|
|
3655
|
+
user_uuid: record4.uuid,
|
|
3570
3656
|
prompt_ref: promptRef,
|
|
3571
3657
|
host_input_ref: inputRef,
|
|
3572
|
-
started_at:
|
|
3573
|
-
last_observed_at: folded?.last_observed_at ??
|
|
3658
|
+
started_at: record4.timestamp,
|
|
3659
|
+
last_observed_at: folded?.last_observed_at ?? record4.timestamp,
|
|
3574
3660
|
message_ids: folded?.message_ids ?? [],
|
|
3575
3661
|
counts: folded?.counts ?? zeroCounts()
|
|
3576
3662
|
};
|
|
@@ -3580,10 +3666,11 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3580
3666
|
const turn = draft.open_turn;
|
|
3581
3667
|
draft.open_turn = void 0;
|
|
3582
3668
|
if (turn === void 0) return;
|
|
3583
|
-
const
|
|
3584
|
-
if (
|
|
3585
|
-
|
|
3586
|
-
|
|
3669
|
+
const digest = terminalDigest(turn);
|
|
3670
|
+
if (digest !== turn.last_terminal_digest) {
|
|
3671
|
+
await this.putTerminalEnvelope(registration, turn, digest, turn.message_ids.length === 0 ? at : void 0);
|
|
3672
|
+
draft.enqueued += 1;
|
|
3673
|
+
}
|
|
3587
3674
|
}
|
|
3588
3675
|
accumulateUsage(draft, usage) {
|
|
3589
3676
|
const turn = draft.open_turn;
|
|
@@ -3617,6 +3704,8 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3617
3704
|
if (isMissingTranscriptError(error) || isNotDirectory(error)) return 0;
|
|
3618
3705
|
throw new ClaudeHostError("payload_rejected", "Claude subagent transcript directory is unavailable.");
|
|
3619
3706
|
}
|
|
3707
|
+
const listed = new Set(names);
|
|
3708
|
+
for (const name of Object.keys(draft.subagents)) if (!listed.has(name)) delete draft.subagents[name];
|
|
3620
3709
|
if (names.length > CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES) {
|
|
3621
3710
|
this.log(`Claude ignored subagent transcripts past its file bound: transcript_path=${JSON.stringify(registration.transcript_path)}, files=${names.length}.`);
|
|
3622
3711
|
names = names.slice(0, CLAUDE_TRANSCRIPT_MAX_SUBAGENT_FILES);
|
|
@@ -3628,10 +3717,10 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3628
3717
|
return consumed;
|
|
3629
3718
|
}
|
|
3630
3719
|
async consumeSubagentFile(draft, directory, name) {
|
|
3631
|
-
const path =
|
|
3720
|
+
const path = join3(directory, name);
|
|
3632
3721
|
let handle;
|
|
3633
3722
|
try {
|
|
3634
|
-
handle = await
|
|
3723
|
+
handle = await openNoFollow(path);
|
|
3635
3724
|
const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
|
|
3636
3725
|
const entry = draft.subagents[name] ?? { offset: 0, anchor_start: 0, anchor_sha256: this.sha(Buffer.alloc(0)) };
|
|
3637
3726
|
const pinned = await this.preflightSubagent(entry, size, device, inode);
|
|
@@ -3659,10 +3748,10 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3659
3748
|
let consumed = 0;
|
|
3660
3749
|
for (const line of chunk.toString("utf8").split("\n").filter((value) => value.length > 0)) {
|
|
3661
3750
|
consumed += 1;
|
|
3662
|
-
const
|
|
3663
|
-
if (
|
|
3664
|
-
if (isAssistantUsageLine(
|
|
3665
|
-
else if (isAssistantLine(
|
|
3751
|
+
const record4 = parseTranscriptLine(line);
|
|
3752
|
+
if (record4 === null) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript line is not valid JSON.", "malformed_line");
|
|
3753
|
+
if (isAssistantUsageLine(record4)) this.accumulateUsage(draft, toUsage(record4.uuid, record4.timestamp, record4.message.usage));
|
|
3754
|
+
else if (isAssistantLine(record4) && record4.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript usage is malformed.");
|
|
3666
3755
|
}
|
|
3667
3756
|
return consumed;
|
|
3668
3757
|
}
|
|
@@ -3684,13 +3773,13 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3684
3773
|
async emitOpenTerminal(registration, draft) {
|
|
3685
3774
|
const turn = draft.open_turn;
|
|
3686
3775
|
if (turn === void 0 || turn.message_ids.length === 0) return;
|
|
3687
|
-
const
|
|
3688
|
-
if (
|
|
3689
|
-
await this.putTerminalEnvelope(registration, turn,
|
|
3776
|
+
const digest = terminalDigest(turn);
|
|
3777
|
+
if (digest === turn.last_terminal_digest) return;
|
|
3778
|
+
await this.putTerminalEnvelope(registration, turn, digest, void 0);
|
|
3690
3779
|
draft.enqueued += 1;
|
|
3691
|
-
draft.open_turn = { ...turn, last_terminal_digest:
|
|
3780
|
+
draft.open_turn = { ...turn, last_terminal_digest: digest };
|
|
3692
3781
|
}
|
|
3693
|
-
async putTerminalEnvelope(registration, turn,
|
|
3782
|
+
async putTerminalEnvelope(registration, turn, digest, gapAt) {
|
|
3694
3783
|
const observedAt = gapAt ?? turn.last_observed_at;
|
|
3695
3784
|
const tokenValue = turn.message_ids.length === 0 ? hostObservationNotRecorded("claude_turn_usage_not_observed") : availableTokenValue(turn.counts);
|
|
3696
3785
|
const identity = { host_source_ref: registration.host_source_ref, host_session_ref: registration.host_session_ref, host_turn_ref: turn.turn_key };
|
|
@@ -3698,15 +3787,13 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3698
3787
|
schema_version: "ceal.claude_observation_outbox.v1",
|
|
3699
3788
|
identity,
|
|
3700
3789
|
host_input_ref: turn.host_input_ref,
|
|
3701
|
-
input_kind: "host_automatic",
|
|
3702
3790
|
prompt_ref: turn.prompt_ref,
|
|
3703
3791
|
observations: [
|
|
3704
|
-
{ observation_ref: observationRef("transcript_terminal", turn.turn_key,
|
|
3705
|
-
{ observation_ref: observationRef("transcript_token", turn.turn_key,
|
|
3706
|
-
{ observation_ref: observationRef("transcript_cost", turn.turn_key, digest2), observation_kind: "cost", observed_at: observedAt, value: hostObservationNotRecorded("provider_cost_not_observed") }
|
|
3792
|
+
{ observation_ref: observationRef(registration.host_source_ref, "transcript_terminal", turn.turn_key, digest), observation_kind: "turn", observed_at: observedAt, value: { status: "stopped", host_session_id_sha256: this.sha(registration.claude_session_id), host_turn_ref: turn.turn_key } },
|
|
3793
|
+
{ observation_ref: observationRef(registration.host_source_ref, "transcript_token", turn.turn_key, digest), observation_kind: "token", observed_at: observedAt, value: tokenValue }
|
|
3707
3794
|
],
|
|
3708
3795
|
origin_input: false,
|
|
3709
|
-
submission_ref: submissionRef("terminal", turn.turn_key,
|
|
3796
|
+
submission_ref: submissionRef("terminal", turn.turn_key, digest, turn.prompt_ref),
|
|
3710
3797
|
created_at: observedAt,
|
|
3711
3798
|
expires_at: this.expire(this.outboxRetentionMs)
|
|
3712
3799
|
});
|
|
@@ -3731,27 +3818,9 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3731
3818
|
updated_at: this.stamp()
|
|
3732
3819
|
});
|
|
3733
3820
|
}
|
|
3734
|
-
async
|
|
3735
|
-
if (this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return;
|
|
3736
|
-
try {
|
|
3737
|
-
await this.registerCurrentPrompt({
|
|
3738
|
-
producer_id: "claude",
|
|
3739
|
-
producer_installation_ref: this.producerInstallationRef,
|
|
3740
|
-
local_session_ref: sessionRef,
|
|
3741
|
-
local_turn_ref: turnRef,
|
|
3742
|
-
prompt_ref: promptRef,
|
|
3743
|
-
current_prompt: prompt,
|
|
3744
|
-
context_expires_at: this.expire(this.outboxRetentionMs)
|
|
3745
|
-
});
|
|
3746
|
-
this.diagnostic("current_prompt_registered");
|
|
3747
|
-
} catch (error) {
|
|
3748
|
-
this.diagnostic("current_prompt_registration_failed");
|
|
3749
|
-
throw error;
|
|
3750
|
-
}
|
|
3751
|
-
}
|
|
3752
|
-
async activateContext(identity, promptRef, turnGenerationRef) {
|
|
3821
|
+
async activateContext(identity, promptRef, turnGenerationRef, expiresAt) {
|
|
3753
3822
|
try {
|
|
3754
|
-
await this.store.activateInvocationContext(identity, promptRef,
|
|
3823
|
+
await this.store.activateInvocationContext(identity, promptRef, expiresAt, void 0, turnGenerationRef);
|
|
3755
3824
|
this.diagnostic("activation_recorded");
|
|
3756
3825
|
} catch (error) {
|
|
3757
3826
|
this.diagnostic("activation_failed");
|
|
@@ -3862,81 +3931,77 @@ function parseTranscriptLine(line) {
|
|
|
3862
3931
|
return null;
|
|
3863
3932
|
}
|
|
3864
3933
|
}
|
|
3865
|
-
function lineIdentity(
|
|
3866
|
-
if (typeof
|
|
3867
|
-
if (typeof
|
|
3868
|
-
return { uuid:
|
|
3934
|
+
function lineIdentity(record4) {
|
|
3935
|
+
if (typeof record4.uuid !== "string" || record4.uuid.length === 0 || record4.uuid.length > 256) return null;
|
|
3936
|
+
if (typeof record4.timestamp !== "string" || !Number.isFinite(Date.parse(record4.timestamp))) return null;
|
|
3937
|
+
return { uuid: record4.uuid, timestamp: record4.timestamp };
|
|
3869
3938
|
}
|
|
3870
|
-
function isUserTurnLine(
|
|
3871
|
-
if (!isTranscriptRecord(
|
|
3872
|
-
if (!isTranscriptRecord(
|
|
3873
|
-
if (
|
|
3874
|
-
const identity = lineIdentity(
|
|
3939
|
+
function isUserTurnLine(record4) {
|
|
3940
|
+
if (!isTranscriptRecord(record4) || record4.type !== "user") return false;
|
|
3941
|
+
if (!isTranscriptRecord(record4.message) || typeof record4.message.content !== "string") return false;
|
|
3942
|
+
if (record4.isMeta === true && (!isTranscriptRecord(record4.origin) || typeof record4.origin.kind !== "string")) return false;
|
|
3943
|
+
const identity = lineIdentity(record4);
|
|
3875
3944
|
if (identity === null) throw new ClaudeHostError("payload_rejected", "Claude transcript user line is malformed.");
|
|
3876
3945
|
return true;
|
|
3877
3946
|
}
|
|
3878
|
-
function
|
|
3879
|
-
|
|
3880
|
-
if (error instanceof ClaudeHostError) {
|
|
3881
|
-
if (error.diagnostic_kind !== void 0) return error.diagnostic_kind;
|
|
3882
|
-
if (error.code === "payload_rejected") return "payload_rejected";
|
|
3883
|
-
if (error.code === "storage") return "read_error";
|
|
3884
|
-
}
|
|
3885
|
-
const code2 = transcriptErrorCode(error);
|
|
3886
|
-
if (code2 === "EFBIG") return "size_budget";
|
|
3887
|
-
if (["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"].includes(code2 ?? "")) return "read_error";
|
|
3888
|
-
return "other";
|
|
3889
|
-
}
|
|
3890
|
-
function isAssistantLine(record5) {
|
|
3891
|
-
return isTranscriptRecord(record5) && record5.type === "assistant" && isTranscriptRecord(record5.message);
|
|
3947
|
+
function isAssistantLine(record4) {
|
|
3948
|
+
return isTranscriptRecord(record4) && record4.type === "assistant" && isTranscriptRecord(record4.message);
|
|
3892
3949
|
}
|
|
3893
|
-
function isAssistantUsageLine(
|
|
3894
|
-
if (!isTranscriptRecord(
|
|
3895
|
-
if (!isAssistantLine(
|
|
3896
|
-
const identity = lineIdentity(
|
|
3897
|
-
if (identity === null || !isTranscriptRecord(
|
|
3950
|
+
function isAssistantUsageLine(record4) {
|
|
3951
|
+
if (!isTranscriptRecord(record4)) return false;
|
|
3952
|
+
if (!isAssistantLine(record4) || record4.message.usage === void 0) return false;
|
|
3953
|
+
const identity = lineIdentity(record4);
|
|
3954
|
+
if (identity === null || !isTranscriptRecord(record4.message.usage)) throw new ClaudeHostError("payload_rejected", "Claude transcript usage line is malformed.");
|
|
3898
3955
|
return true;
|
|
3899
3956
|
}
|
|
3900
3957
|
function subagentsDirectory(transcriptPath) {
|
|
3901
3958
|
const stem = transcriptPath.endsWith(".jsonl") ? transcriptPath.slice(0, -".jsonl".length) : transcriptPath;
|
|
3902
|
-
return
|
|
3959
|
+
return join3(stem, "subagents");
|
|
3903
3960
|
}
|
|
3904
|
-
function observationRef(event, identity, value) {
|
|
3905
|
-
return cealHostObservationRef("claude", transcriptObjectSha256({ event, identity, value }));
|
|
3961
|
+
function observationRef(hostSourceRef, event, identity, value) {
|
|
3962
|
+
return cealHostObservationRef("claude", transcriptObjectSha256({ host_source_ref: hostSourceRef, event, identity, value }));
|
|
3906
3963
|
}
|
|
3907
|
-
function submissionRef(kind, turn,
|
|
3908
|
-
return `claude-submission:${transcriptObjectSha256({ kind, turn, digest
|
|
3964
|
+
function submissionRef(kind, turn, digest, prompt) {
|
|
3965
|
+
return `claude-submission:${transcriptObjectSha256({ kind, turn, digest, prompt })}`;
|
|
3909
3966
|
}
|
|
3910
3967
|
async function ownedClaudeTranscriptFile(handle) {
|
|
3911
3968
|
const stats = await handle.stat();
|
|
3912
|
-
const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
|
|
3913
3969
|
if (!stats.isFile()) throw new ClaudeHostError("payload_rejected", "Claude transcript source is not a regular file.");
|
|
3914
|
-
if (stats.uid
|
|
3970
|
+
if (process.platform !== "win32" && !isSelfOwned(stats.uid, process.platform, handle)) throw new ClaudeHostError("payload_rejected", "Claude transcript source is owned by another user.");
|
|
3915
3971
|
return { size: stats.size, device: String(stats.dev), inode: String(stats.ino) };
|
|
3916
3972
|
}
|
|
3917
3973
|
function isNotDirectory(error) {
|
|
3918
3974
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOTDIR";
|
|
3919
3975
|
}
|
|
3976
|
+
function claudeTranscriptScanCause(handle, error) {
|
|
3977
|
+
if (error instanceof ClaudeHostError) return error.diagnostic_kind ?? error.code;
|
|
3978
|
+
const code2 = transcriptErrorCode(error);
|
|
3979
|
+
if (code2 !== null) return `${handle === void 0 ? "open" : "scan"}:${code2}`;
|
|
3980
|
+
return `${handle === void 0 ? "open" : "scan"}:unknown`;
|
|
3981
|
+
}
|
|
3982
|
+
function claudeTranscriptScanFailureKind(error) {
|
|
3983
|
+
return transcriptScanFailureKind(error, (candidate) => {
|
|
3984
|
+
if (!(candidate instanceof ClaudeHostError)) return void 0;
|
|
3985
|
+
if (candidate.diagnostic_kind !== void 0) return candidate.diagnostic_kind;
|
|
3986
|
+
if (candidate.code === "payload_rejected") return "payload_rejected";
|
|
3987
|
+
if (candidate.code === "storage") return "read_error";
|
|
3988
|
+
return void 0;
|
|
3989
|
+
});
|
|
3990
|
+
}
|
|
3920
3991
|
function isSymlinkTranscript(error) {
|
|
3921
3992
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ELOOP";
|
|
3922
3993
|
}
|
|
3923
|
-
function transcriptErrorCode(error) {
|
|
3924
|
-
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
|
|
3925
|
-
}
|
|
3926
3994
|
|
|
3927
3995
|
// host/claude/src/claude-producer-runtime.ts
|
|
3928
3996
|
async function drainClaudeProducerOnce(options) {
|
|
3929
|
-
const promptPort = new HostRelayCurrentPromptPort(options.relay_endpoint, options.timeout_ms);
|
|
3930
3997
|
const scan = await new ClaudeTranscriptWatcher({
|
|
3931
3998
|
outbox_dir: options.outbox_dir,
|
|
3932
|
-
|
|
3933
|
-
register_current_prompt: (input) => promptPort.register(input),
|
|
3934
|
-
diagnostic: (event, failure_kind) => writeHostServiceDiagnostic("claude", event, failure_kind)
|
|
3999
|
+
diagnostic: (event, failure_kind, cause) => writeHostServiceDiagnostic("claude", event, failure_kind, cause)
|
|
3935
4000
|
}).scanOnce().catch((error) => {
|
|
3936
|
-
writeHostServiceDiagnostic("claude", "watcher_scan_failed", claudeTranscriptScanFailureKind(error));
|
|
4001
|
+
writeHostServiceDiagnostic("claude", "watcher_scan_failed", claudeTranscriptScanFailureKind(error), claudeTranscriptScanCause(void 0, error));
|
|
3937
4002
|
throw error;
|
|
3938
4003
|
});
|
|
3939
|
-
const producer = new ClaudeProducer({ outbox_dir: options.outbox_dir,
|
|
4004
|
+
const producer = new ClaudeProducer({ outbox_dir: options.outbox_dir, ...options.now === void 0 ? {} : { now: options.now } });
|
|
3940
4005
|
return { scanned: scan.enqueued, ...await producer.drainOutbox() };
|
|
3941
4006
|
}
|
|
3942
4007
|
|
|
@@ -3949,11 +4014,7 @@ var CLAUDE_PRODUCER_DESCRIPTOR = {
|
|
|
3949
4014
|
extra_paths: [CLAUDE_TRANSCRIPTS_ROOT],
|
|
3950
4015
|
config: (shared, extras) => ({ ...shared, claude_transcripts_root: hostProducerExtraPath(extras, CLAUDE_TRANSCRIPTS_ROOT) }),
|
|
3951
4016
|
runtime_options: (config) => Object.freeze({
|
|
3952
|
-
outbox_dir: config.outbox_dir
|
|
3953
|
-
producer_installation_ref: config.producer_installation_ref,
|
|
3954
|
-
producer_epoch: config.producer_epoch,
|
|
3955
|
-
relay_endpoint: config.relay_endpoint,
|
|
3956
|
-
timeout_ms: config.ipc_timeout_ms
|
|
4017
|
+
outbox_dir: config.outbox_dir
|
|
3957
4018
|
}),
|
|
3958
4019
|
drain: drainClaudeProducerOnce
|
|
3959
4020
|
};
|
|
@@ -3963,13 +4024,15 @@ function runClaudeProducerCli(args, signal, dependencies = {}) {
|
|
|
3963
4024
|
|
|
3964
4025
|
// host/producer-command.ts
|
|
3965
4026
|
async function runProducerCommand(schemaVersion, run) {
|
|
4027
|
+
const serviceArguments = consumeHostServiceLogArgument(process.argv.slice(2));
|
|
4028
|
+
if (serviceArguments.path !== void 0) process.env[HOST_SERVICE_LOG_ENVIRONMENT] = serviceArguments.path;
|
|
3966
4029
|
const controller = new AbortController();
|
|
3967
4030
|
const stop = () => controller.abort();
|
|
3968
4031
|
process.once("SIGINT", stop);
|
|
3969
4032
|
process.once("SIGTERM", stop);
|
|
3970
4033
|
const stopLogBound = boundHostServiceLog();
|
|
3971
4034
|
try {
|
|
3972
|
-
await run(
|
|
4035
|
+
await run(serviceArguments.args, controller.signal);
|
|
3973
4036
|
} catch (error) {
|
|
3974
4037
|
const code2 = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "producer_failed";
|
|
3975
4038
|
process.stderr.write(`${JSON.stringify({ schema_version: schemaVersion, ok: false, code: code2 })}
|