@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(record, options, depth, count) {
|
|
510
|
+
const entries = Object.entries(record);
|
|
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(record.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,6 +745,317 @@ function requireIsoTimestamp(value, message) {
|
|
|
835
745
|
if (!isCealIsoTimestamp(value)) throw new TypeError(message);
|
|
836
746
|
}
|
|
837
747
|
|
|
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"]);
|
|
923
|
+
|
|
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
|
+
|
|
838
1059
|
// packages/ceal-client-protocol/src/operation-json-schema.ts
|
|
839
1060
|
var JSON_DESCRIPTION_CONTROL_CHARACTER = new RegExp("\\p{Cc}", "u");
|
|
840
1061
|
|
|
@@ -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,10 +1653,6 @@ 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;
|
|
@@ -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 record = operationRecord(value, "Ceal Host observation draft is invalid.");
|
|
1702
|
+
requirePrefixedRef(record.observation_ref, CEAL_HOST_OBSERVATION_REF_PREFIX, "Ceal Host observation reference is invalid.");
|
|
1703
|
+
requireClosedValue(record.observation_kind, CEAL_HOST_OBSERVATION_KINDS, "Ceal Host observation kind is invalid.");
|
|
1704
|
+
requireIsoTimestamp(record.observed_at, "Ceal Host observation time is invalid.");
|
|
1705
|
+
assertHostObservationValue(record.value, record.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" }),
|
|
@@ -1605,14 +1926,12 @@ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
|
|
|
1605
1926
|
});
|
|
1606
1927
|
|
|
1607
1928
|
// packages/ceal-client-protocol/src/host-service-diagnostic.ts
|
|
1929
|
+
var CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA = "ceal.host_service_diagnostic.v1";
|
|
1608
1930
|
var CEAL_HOST_SERVICE_DIAGNOSTIC_PRODUCERS = vocabulary(["claude", "codex"]);
|
|
1609
1931
|
var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
|
|
1610
1932
|
"hook_invoked",
|
|
1611
1933
|
"registration_written",
|
|
1612
1934
|
"hook_failed",
|
|
1613
|
-
"watcher_turn_observed",
|
|
1614
|
-
"current_prompt_registered",
|
|
1615
|
-
"current_prompt_registration_failed",
|
|
1616
1935
|
"activation_recorded",
|
|
1617
1936
|
"activation_failed",
|
|
1618
1937
|
"watcher_scan_failed",
|
|
@@ -1635,6 +1954,9 @@ var CEAL_UNREAD_REASONS = vocabulary(["blocked", "unavailable", "too_large", "un
|
|
|
1635
1954
|
var REQUIRED_FIELDS2 = Object.freeze(["schema_version", "operation_id", "path", "sha256"]);
|
|
1636
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"]);
|
|
1637
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
|
+
|
|
1638
1960
|
// packages/ceal-client-protocol/src/gateway-cache-origin-validation.ts
|
|
1639
1961
|
var CEAL_MAX_CACHE_ORIGIN_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1640
1962
|
|
|
@@ -1693,6 +2015,7 @@ var ADMIN_UNAVAILABLE_REASONS = vocabulary([
|
|
|
1693
2015
|
"legacy",
|
|
1694
2016
|
"invalid_observation",
|
|
1695
2017
|
"redacted",
|
|
2018
|
+
"organization_setting_disabled",
|
|
1696
2019
|
"owner_unavailable",
|
|
1697
2020
|
"unit_unknown",
|
|
1698
2021
|
"retention_gap",
|
|
@@ -1722,15 +2045,13 @@ var ADMIN_APPROVAL_DISPOSITIONS = CEAL_OPERATION_APPROVAL_DISPOSITIONS;
|
|
|
1722
2045
|
var ADMIN_APPROVAL_POSTURES = vocabulary([...ADMIN_APPROVAL_DISPOSITIONS, "not_applicable"]);
|
|
1723
2046
|
var ADMIN_LINEAGE_NODE_KINDS = vocabulary(["source_read", "synthesis_preview", "approval", "effect", "readback"]);
|
|
1724
2047
|
|
|
1725
|
-
// packages/ceal-client-protocol/src/actor-kind.ts
|
|
1726
|
-
var CEAL_ACTOR_KINDS = vocabulary(["human", "bot", "app", "unknown"]);
|
|
1727
|
-
|
|
1728
2048
|
// packages/ceal-client-protocol/src/device-enrollment.ts
|
|
1729
2049
|
var CEAL_DEVICE_ENROLLMENT_POLL_FAILURE_CODES = Object.freeze(["unsupported_feature", "recovery_required", "expired", "denied"]);
|
|
1730
2050
|
var POLL_RESULT_KEYS = Object.freeze(["retry_after_ms", "schema_version", "status"]);
|
|
1731
2051
|
|
|
1732
2052
|
// packages/ceal-client-protocol/src/personal-client-session.ts
|
|
1733
2053
|
var REFRESH_FAILURE_CODES = new Set(CEAL_REFRESH_FAILURE_CODES);
|
|
2054
|
+
var REVOKE_FAILURE_CODES = new Set(CEAL_CLIENT_REVOKE_FAILURE_CODES);
|
|
1734
2055
|
|
|
1735
2056
|
// packages/ceal-client-protocol/src/index.ts
|
|
1736
2057
|
var MAX_REQUEST_BYTES = 32 * 1024;
|
|
@@ -1753,6 +2074,125 @@ function waitForAbort(signal, milliseconds) {
|
|
|
1753
2074
|
});
|
|
1754
2075
|
}
|
|
1755
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, reject2) => {
|
|
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
|
+
reject2(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
|
+
|
|
1756
2196
|
// host/producer-service.ts
|
|
1757
2197
|
var SAFE_REF2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
1758
2198
|
var HOST_PRODUCER_SERVE_COMMAND_PREFIX = ["serve", "--config"];
|
|
@@ -1765,18 +2205,19 @@ var HostProducerServiceError = class extends Error {
|
|
|
1765
2205
|
}
|
|
1766
2206
|
};
|
|
1767
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";
|
|
1768
2209
|
var HOST_PRODUCER_TIMING_DEFAULTS = Object.freeze({ reconcile_interval_ms: 250, retry_backoff_ms: 1e3, ipc_timeout_ms: 5e3 });
|
|
1769
2210
|
function hostProducerExtraPath(extras, key) {
|
|
1770
2211
|
const value = extras[key];
|
|
1771
|
-
if (value === void 0) throw hostProducerError("invalid_config");
|
|
2212
|
+
if (value === void 0) throw hostProducerError("invalid_config", refusalFact(key, "missing", "an absolute path"));
|
|
1772
2213
|
return value;
|
|
1773
2214
|
}
|
|
1774
2215
|
function parseHostProducerServeArgs(args, platform = process.platform) {
|
|
1775
2216
|
return parseProducerServeArgs(args, platform, hostProducerError);
|
|
1776
2217
|
}
|
|
1777
|
-
async function readHostProducerConfig(descriptor, configPath, platform = process.platform) {
|
|
1778
|
-
const config = decodeHostProducerConfigText(descriptor, await readProducerConfigText(configPath, platform, hostProducerError), platform);
|
|
1779
|
-
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;
|
|
1780
2221
|
const root = path.resolve(config.outbox_dir, "../../../../..");
|
|
1781
2222
|
try {
|
|
1782
2223
|
assertHostProducerSourcePaths(root, config, descriptor.producer_id, platform);
|
|
@@ -1805,15 +2246,31 @@ function parseProducerServeArgs(args, platform, error) {
|
|
|
1805
2246
|
assertProducerAbsolutePath(args[2], platform, "config", error);
|
|
1806
2247
|
return args[2];
|
|
1807
2248
|
}
|
|
1808
|
-
async function readProducerConfigText(configPath, platform, error) {
|
|
1809
|
-
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
|
+
}
|
|
1810
2268
|
const pathStat = await configPathStat(configPath, error);
|
|
1811
2269
|
validateConfigFile(pathStat, platform, error);
|
|
1812
|
-
const
|
|
1813
|
-
const handle = await openConfigFile(configPath, constants.O_RDONLY | noFollow, error);
|
|
2270
|
+
const handle = await openConfigFile(configPath, platform, error, custody);
|
|
1814
2271
|
try {
|
|
1815
2272
|
const opened = await handle.stat();
|
|
1816
|
-
validateConfigFile(opened, platform, error);
|
|
2273
|
+
validateConfigFile(opened, platform, error, handle);
|
|
1817
2274
|
validateOpenedFile(opened, pathStat, platform, error);
|
|
1818
2275
|
return await readBoundedConfig(handle, opened.size, error);
|
|
1819
2276
|
} finally {
|
|
@@ -1821,8 +2278,9 @@ async function readProducerConfigText(configPath, platform, error) {
|
|
|
1821
2278
|
}
|
|
1822
2279
|
}
|
|
1823
2280
|
function decodeProducerConfigCore(text, options) {
|
|
2281
|
+
const ownership = producerConfigKeyOwnership(options.keys, options.platform);
|
|
1824
2282
|
const value = parseCanonicalJson(text, options.error);
|
|
1825
|
-
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 })));
|
|
1826
2284
|
const hostSourceRef = typeof value.host_source_ref === "string" ? value.host_source_ref : "";
|
|
1827
2285
|
const installationRef = typeof value.producer_installation_ref === "string" ? value.producer_installation_ref : "";
|
|
1828
2286
|
const producerEpoch = validateIdentity(hostSourceRef, installationRef, value.producer_epoch, options.error);
|
|
@@ -1840,7 +2298,7 @@ function decodeProducerConfigCore(text, options) {
|
|
|
1840
2298
|
producer_installation_ref: installationRef,
|
|
1841
2299
|
producer_epoch: producerEpoch,
|
|
1842
2300
|
outbox_dir: value.outbox_dir,
|
|
1843
|
-
relay_endpoint: value
|
|
2301
|
+
relay_endpoint: producerConfigEndpoint(value),
|
|
1844
2302
|
reconcile_interval_ms: intervals[0],
|
|
1845
2303
|
retry_backoff_ms: intervals[1],
|
|
1846
2304
|
ipc_timeout_ms: intervals[2]
|
|
@@ -1857,8 +2315,15 @@ async function serveProducer(runtimeOptions, timing, signal, drain, wait = (mill
|
|
|
1857
2315
|
if (!signal.aborted) await wait(delay, signal);
|
|
1858
2316
|
}
|
|
1859
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
|
+
}
|
|
1860
2325
|
function assertProducerRelayEndpoint(value, platform, error) {
|
|
1861
|
-
if (platform === "win32"
|
|
2326
|
+
if (platform === "win32") return;
|
|
1862
2327
|
assertProducerAbsolutePath(value, platform, "runtime", error);
|
|
1863
2328
|
}
|
|
1864
2329
|
function assertProducerAbsolutePath(value, platform, kind, error) {
|
|
@@ -1866,26 +2331,27 @@ function assertProducerAbsolutePath(value, platform, kind, error) {
|
|
|
1866
2331
|
}
|
|
1867
2332
|
async function configPathStat(configPath, error) {
|
|
1868
2333
|
try {
|
|
1869
|
-
return await
|
|
2334
|
+
return await lstat2(configPath);
|
|
1870
2335
|
} catch {
|
|
1871
2336
|
throw error("config_unavailable");
|
|
1872
2337
|
}
|
|
1873
2338
|
}
|
|
1874
|
-
function validateConfigFile(value, platform, error) {
|
|
2339
|
+
function validateConfigFile(value, platform, error, handle) {
|
|
1875
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" }]));
|
|
1876
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}`));
|
|
1877
|
-
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" }]));
|
|
1878
2343
|
}
|
|
1879
|
-
async function openConfigFile(configPath,
|
|
2344
|
+
async function openConfigFile(configPath, platform, error, custody) {
|
|
1880
2345
|
try {
|
|
1881
|
-
return await
|
|
1882
|
-
} 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");
|
|
1883
2349
|
throw error("config_unavailable");
|
|
1884
2350
|
}
|
|
1885
2351
|
}
|
|
1886
2352
|
function validateOpenedFile(opened, pathStat, platform, error) {
|
|
1887
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" }]));
|
|
1888
|
-
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) }]));
|
|
1889
2355
|
}
|
|
1890
2356
|
async function readBoundedConfig(handle, expectedBytes, error) {
|
|
1891
2357
|
const buffer = Buffer.allocUnsafe(expectedBytes + 1);
|
|
@@ -1917,435 +2383,173 @@ function firstDifferenceIndex(left, right) {
|
|
|
1917
2383
|
for (let index = 0; index < shared; index += 1) if (left[index] !== right[index]) return index;
|
|
1918
2384
|
return shared;
|
|
1919
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
|
+
}
|
|
1920
2397
|
function validateIdentity(sourceRef, installationRef, epoch, error) {
|
|
1921
|
-
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)));
|
|
1922
2399
|
return epoch;
|
|
1923
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
|
+
}
|
|
1924
2408
|
function validateIntervals(reconcile, retry, timeout, error) {
|
|
1925
|
-
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)));
|
|
1926
2410
|
return [reconcile, retry, timeout];
|
|
1927
2411
|
}
|
|
1928
|
-
function
|
|
1929
|
-
|
|
1930
|
-
}
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
function positive(value, maximum) {
|
|
1935
|
-
return Number.isSafeInteger(value) && Number(value) > 0 && Number(value) <= maximum;
|
|
1936
|
-
}
|
|
1937
|
-
function boundedMilliseconds(value) {
|
|
1938
|
-
return positive(value, 6e4) && Number(value) >= 25;
|
|
1939
|
-
}
|
|
1940
|
-
|
|
1941
|
-
// host/relay/src/producer-frame.ts
|
|
1942
|
-
var HOST_PRODUCER_FRAME_SCHEMA = "ceal.host_producer_frame.v1";
|
|
1943
|
-
var HOST_PRODUCER_MAX_JSON_BYTES = 262144;
|
|
1944
|
-
var HOST_PRODUCER_FRAME_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
1945
|
-
var SAFE_REF3 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
1946
|
-
var PRODUCERS = /* @__PURE__ */ new Set(["codex", "claude"]);
|
|
1947
|
-
var HOST_PRODUCER_FRAME_KINDS = ["session_started", "turn_started", "user_input", "turn_terminal", "producer_status"];
|
|
1948
|
-
var FRAME_KINDS = new Set(HOST_PRODUCER_FRAME_KINDS);
|
|
1949
|
-
function isHostProducerFrameKind(value) {
|
|
1950
|
-
return typeof value === "string" && FRAME_KINDS.has(value);
|
|
1951
|
-
}
|
|
1952
|
-
var HOST_PRODUCER_FRAME_ERROR_CODES = [
|
|
1953
|
-
"invalid_frame_encoding",
|
|
1954
|
-
"invalid_frame_length",
|
|
1955
|
-
"frame_truncated",
|
|
1956
|
-
"unsupported_frame_schema",
|
|
1957
|
-
"invalid_frame_shape",
|
|
1958
|
-
"invalid_producer_identity",
|
|
1959
|
-
"frame_too_large"
|
|
1960
|
-
];
|
|
1961
|
-
var HostProducerFrameError = class extends Error {
|
|
1962
|
-
code;
|
|
1963
|
-
constructor(code, message = code) {
|
|
1964
|
-
super(message);
|
|
1965
|
-
this.code = code;
|
|
1966
|
-
}
|
|
1967
|
-
};
|
|
1968
|
-
function encodeHostProducerFrame(frame) {
|
|
1969
|
-
validateFrame(frame);
|
|
1970
|
-
const json = Buffer.from(JSON.stringify(frame), "utf8");
|
|
1971
|
-
if (json.byteLength > HOST_PRODUCER_MAX_JSON_BYTES) throw new HostProducerFrameError("frame_too_large", `frame_too_large: ${refusalFact("frame_bytes", json.byteLength, `at most ${HOST_PRODUCER_MAX_JSON_BYTES}`)}`);
|
|
1972
|
-
const result = Buffer.allocUnsafe(json.byteLength + HOST_PRODUCER_FRAME_LENGTH_BYTES);
|
|
1973
|
-
result.writeUInt32BE(json.byteLength, 0);
|
|
1974
|
-
json.copy(result, HOST_PRODUCER_FRAME_LENGTH_BYTES);
|
|
1975
|
-
return result;
|
|
1976
|
-
}
|
|
1977
|
-
function decodeHostProducerFrame(input) {
|
|
1978
|
-
if (input.byteLength < HOST_PRODUCER_FRAME_LENGTH_BYTES) throw new HostProducerFrameError("frame_truncated", `frame_truncated: ${refusalFact("frame_bytes", input.byteLength, `at least ${HOST_PRODUCER_FRAME_LENGTH_BYTES}`)}`);
|
|
1979
|
-
const length = input.readUInt32BE(0);
|
|
1980
|
-
if (length > HOST_PRODUCER_MAX_JSON_BYTES) throw new HostProducerFrameError("frame_too_large", `frame_too_large: ${refusalFact("declared_bytes", length, `at most ${HOST_PRODUCER_MAX_JSON_BYTES}`)}`);
|
|
1981
|
-
if (length === 0) throw new HostProducerFrameError("invalid_frame_length");
|
|
1982
|
-
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");
|
|
1983
|
-
const bytes = input.subarray(HOST_PRODUCER_FRAME_LENGTH_BYTES);
|
|
1984
|
-
const text = new TextDecoder("utf-8", { fatal: true });
|
|
1985
|
-
let value;
|
|
1986
|
-
try {
|
|
1987
|
-
value = JSON.parse(text.decode(bytes));
|
|
1988
|
-
} catch (error) {
|
|
1989
|
-
if (error instanceof TypeError) throw new HostProducerFrameError("invalid_frame_encoding");
|
|
1990
|
-
throw new HostProducerFrameError("frame_truncated");
|
|
1991
|
-
}
|
|
1992
|
-
validateFrame(value);
|
|
1993
|
-
return value;
|
|
1994
|
-
}
|
|
1995
|
-
function isHostSafeRef(value) {
|
|
1996
|
-
return safeRef(value);
|
|
1997
|
-
}
|
|
1998
|
-
function validateFrame(value) {
|
|
1999
|
-
if (!record(value)) throw new HostProducerFrameError("invalid_frame_shape");
|
|
2000
|
-
if (value.schema_version !== HOST_PRODUCER_FRAME_SCHEMA) throw new HostProducerFrameError("unsupported_frame_schema", `unsupported_frame_schema: ${refusalFact("schema_version", value.schema_version, HOST_PRODUCER_FRAME_SCHEMA)}`);
|
|
2001
|
-
if (!exactKeys(value, ["schema_version", "producer_id", "producer_installation_ref", "producer_epoch", "sequence", "frame_kind", "occurred_at", "payload"])) throw new HostProducerFrameError("invalid_frame_shape");
|
|
2002
|
-
if (!PRODUCERS.has(String(value.producer_id)) || !safeRef(value.producer_installation_ref)) throw new HostProducerFrameError("invalid_producer_identity");
|
|
2003
|
-
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");
|
|
2004
|
-
validatePayload(value.frame_kind, value.payload);
|
|
2005
|
-
}
|
|
2006
|
-
function validatePayload(kind, value) {
|
|
2007
|
-
const valid = kind === "session_started" ? validSession(value) : kind === "turn_started" ? validTurn(value) : kind === "user_input" ? validInput(value) : kind === "turn_terminal" ? validTerminal(value) : validStatus(value);
|
|
2008
|
-
if (!valid) throw new HostProducerFrameError("invalid_frame_shape");
|
|
2009
|
-
}
|
|
2010
|
-
function validSession(value) {
|
|
2011
|
-
return exactOptional(value, ["local_session_ref", "working_directory_ref"], ["model", "host_version", "observations"]) && safeRef(value.local_session_ref) && safeRef(value.working_directory_ref) && optionalText(value.model, 256) && optionalText(value.host_version, 256) && validObservations(value.observations);
|
|
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;
|
|
2012
2418
|
}
|
|
2013
|
-
function
|
|
2014
|
-
return
|
|
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));
|
|
2015
2421
|
}
|
|
2016
|
-
function
|
|
2017
|
-
return exactOptional(value, ["local_session_ref", "local_turn_ref", "local_input_ref", "input_kind", "origin_input", "observations"], ["local_turn_aliases", "context_expires_at"]) && safeRef(value.local_session_ref) && safeRef(value.local_turn_ref) && safeRef(value.local_input_ref) && validInputObservations(value.observations) && isCealHostInputKind(value.input_kind) && typeof value.origin_input === "boolean" && validInputOptionals(value);
|
|
2018
|
-
}
|
|
2019
|
-
function validInputObservations(value) {
|
|
2020
|
-
return validObservations(value) && Array.isArray(value) && value.filter((item) => record(item) && item.observation_kind === "prompt").length === 1;
|
|
2021
|
-
}
|
|
2022
|
-
function validInputOptionals(value) {
|
|
2023
|
-
return optionalRefs(value.local_turn_aliases) && (value.context_expires_at === void 0 || timestamp(value.context_expires_at));
|
|
2024
|
-
}
|
|
2025
|
-
function validTerminal(value) {
|
|
2026
|
-
return exactOptional(value, ["local_session_ref", "local_turn_ref", "terminal_state"], ["abort_reason", "duration_ms", "time_to_first_token_ms", "usage", "observations"]) && safeRef(value.local_session_ref) && safeRef(value.local_turn_ref) && validTerminalDisposition(value.terminal_state, value.abort_reason) && optionalNonNegative(value.duration_ms) && optionalNonNegative(value.time_to_first_token_ms) && (value.usage === void 0 || jsonValue(value.usage)) && validObservations(value.observations);
|
|
2027
|
-
}
|
|
2028
|
-
function validTerminalDisposition(state, reason) {
|
|
2029
|
-
return (state === "completed" || state === "aborted") && (reason === void 0 || state === "aborted" && safeRef(reason));
|
|
2030
|
-
}
|
|
2031
|
-
function validStatus(value) {
|
|
2032
|
-
return exactOptional(value, ["status", "reason_code"], ["detail"]) && ["ready", "degraded", "stopping"].includes(String(value.status)) && safeRef(value.reason_code) && optionalText(value.detail, 4096);
|
|
2033
|
-
}
|
|
2034
|
-
function optionalText(value, maxLength) {
|
|
2035
|
-
return value === void 0 || typeof value === "string" && value.length > 0 && value.length <= maxLength;
|
|
2036
|
-
}
|
|
2037
|
-
function optionalNonNegative(value) {
|
|
2038
|
-
return value === void 0 || typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
2039
|
-
}
|
|
2040
|
-
function optionalRefs(value) {
|
|
2041
|
-
return value === void 0 || Array.isArray(value) && value.length <= 16 && value.every(safeRef);
|
|
2042
|
-
}
|
|
2043
|
-
function validObservations(value) {
|
|
2044
|
-
if (value === void 0) return true;
|
|
2045
|
-
if (!Array.isArray(value) || value.length > 32) return false;
|
|
2046
|
-
return value.every((item) => {
|
|
2047
|
-
try {
|
|
2048
|
-
validateCealHostObservationDraft(item);
|
|
2049
|
-
return true;
|
|
2050
|
-
} catch {
|
|
2051
|
-
return false;
|
|
2052
|
-
}
|
|
2053
|
-
});
|
|
2054
|
-
}
|
|
2055
|
-
function jsonValue(value, depth = 0) {
|
|
2056
|
-
if (depth > 16) return false;
|
|
2057
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
2058
|
-
if (typeof value === "number") return Number.isFinite(value);
|
|
2059
|
-
if (Array.isArray(value)) return value.length <= 256 && value.every((item) => jsonValue(item, depth + 1));
|
|
2060
|
-
return record(value) && Object.keys(value).length <= 256 && Object.entries(value).every(([key, item]) => key.length > 0 && key.length <= 128 && jsonValue(item, depth + 1));
|
|
2061
|
-
}
|
|
2062
|
-
function record(value) {
|
|
2422
|
+
function isRecord2(value) {
|
|
2063
2423
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2064
|
-
}
|
|
2065
|
-
function
|
|
2066
|
-
return
|
|
2067
|
-
}
|
|
2068
|
-
function
|
|
2069
|
-
return
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
const keys = Object.keys(value);
|
|
2082
|
-
return required.every((key) => keys.includes(key)) && keys.every((key) => required.includes(key) || optional.includes(key));
|
|
2083
|
-
}
|
|
2084
|
-
|
|
2085
|
-
// host/relay/src/host-producer-core.ts
|
|
2086
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
2087
|
-
import { O_NOFOLLOW, O_RDONLY as O_RDONLY2 } from "node:constants";
|
|
2088
|
-
import { mkdir as mkdir2, open as open3, readFile, rename as rename2, writeFile } from "node:fs/promises";
|
|
2089
|
-
import { join as join2 } from "node:path";
|
|
2090
|
-
|
|
2091
|
-
// host/relay/src/relay-ipc.ts
|
|
2092
|
-
import { createConnection, createServer } from "node:net";
|
|
2093
|
-
|
|
2094
|
-
// host/relay/src/durable-atomic-replacement.ts
|
|
2095
|
-
import { O_RDONLY } from "node:constants";
|
|
2096
|
-
import { lstat as lstat2, mkdir, open as open2, rename, unlink } from "node:fs/promises";
|
|
2097
|
-
var DEFAULT_PORT = {
|
|
2098
|
-
platform: process.platform,
|
|
2099
|
-
mkdir: (path, options) => mkdir(path, options),
|
|
2100
|
-
lstat: (path) => lstat2(path),
|
|
2101
|
-
open_file: (path) => open2(path, "wx", 384),
|
|
2102
|
-
rename,
|
|
2103
|
-
unlink,
|
|
2104
|
-
open_directory: (path) => open2(path, O_RDONLY),
|
|
2105
|
-
unique_ref: () => `${process.pid}.${crypto.randomUUID()}`
|
|
2106
|
-
};
|
|
2107
|
-
|
|
2108
|
-
// host/relay/src/relay-request-binding-store.ts
|
|
2109
|
-
var MAX_STATE_BYTES = 8 * 1024 * 1024;
|
|
2110
|
-
|
|
2111
|
-
// host/relay/src/current-prompt-custody.ts
|
|
2112
|
-
var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
|
|
2113
|
-
|
|
2114
|
-
// host/relay/src/relay-ipc.ts
|
|
2115
|
-
var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
|
|
2116
|
-
"schema_version",
|
|
2117
|
-
"producer_id",
|
|
2118
|
-
"producer_installation_ref",
|
|
2119
|
-
"local_session_ref",
|
|
2120
|
-
"local_turn_ref",
|
|
2121
|
-
"local_turn_aliases",
|
|
2122
|
-
"prompt_ref",
|
|
2123
|
-
"request_ref",
|
|
2124
|
-
"bound_at",
|
|
2125
|
-
"host_source_ref",
|
|
2126
|
-
"host_session_ref",
|
|
2127
|
-
"host_turn_ref",
|
|
2128
|
-
"host_input_ref"
|
|
2129
|
-
]);
|
|
2130
|
-
var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
|
|
2131
|
-
var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
|
|
2132
|
-
var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
|
|
2133
|
-
var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
2134
|
-
var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
|
|
2135
|
-
var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
|
|
2136
|
-
"context_stale",
|
|
2137
|
-
"context_expired",
|
|
2138
|
-
"host_source_refused",
|
|
2139
|
-
"host_credential_refused",
|
|
2140
|
-
"gateway_unavailable",
|
|
2141
|
-
"gateway_refused",
|
|
2142
|
-
"gateway_response_invalid",
|
|
2143
|
-
"gateway_rate_limited",
|
|
2144
|
-
"producer_frame_invalid",
|
|
2145
|
-
"deadline",
|
|
2146
|
-
"cancelled",
|
|
2147
|
-
"mint_failed",
|
|
2148
|
-
"relay_failed",
|
|
2149
|
-
"request_authority_unavailable",
|
|
2150
|
-
"request_missing",
|
|
2151
|
-
"authority_mismatch",
|
|
2152
|
-
"host_source_mismatch",
|
|
2153
|
-
"host_session_mismatch",
|
|
2154
|
-
"host_turn_mismatch",
|
|
2155
|
-
"host_input_mismatch",
|
|
2156
|
-
"host_prompt_mismatch"
|
|
2157
|
-
]);
|
|
2158
|
-
var HOST_RELAY_DIRECT_WIRE_MINT_REASONS = vocabulary([
|
|
2159
|
-
"context_stale",
|
|
2160
|
-
"context_expired",
|
|
2161
|
-
"host_source_refused",
|
|
2162
|
-
"host_credential_refused",
|
|
2163
|
-
"gateway_unavailable",
|
|
2164
|
-
"gateway_rate_limited",
|
|
2165
|
-
"gateway_refused",
|
|
2166
|
-
"gateway_response_invalid",
|
|
2167
|
-
"producer_frame_invalid"
|
|
2168
|
-
]);
|
|
2169
|
-
var HOST_RELAY_INVOCATION_MINT_OUTCOMES = vocabulary(["completed", "caller_cancelled", "deadline", "failed", "no_binding"]);
|
|
2170
|
-
var HOST_RELAY_EXCHANGE_FAILURES = vocabulary([
|
|
2171
|
-
"permission_denied",
|
|
2172
|
-
"socket_missing",
|
|
2173
|
-
"connection_refused",
|
|
2174
|
-
"closed_early",
|
|
2175
|
-
"response_invalid",
|
|
2176
|
-
"timed_out",
|
|
2177
|
-
"cancelled",
|
|
2178
|
-
"unavailable"
|
|
2179
|
-
]);
|
|
2180
|
-
var HostRelayIpcError = class extends Error {
|
|
2181
|
-
code;
|
|
2182
|
-
/** Set only when the client-side socket exchange itself failed. */
|
|
2183
|
-
failure;
|
|
2184
|
-
constructor(code, message = code, failure) {
|
|
2185
|
-
super(message);
|
|
2186
|
-
this.code = code;
|
|
2187
|
-
this.failure = failure;
|
|
2188
|
-
}
|
|
2189
|
-
};
|
|
2190
|
-
function hostRelayExchangeFailure(error) {
|
|
2191
|
-
return error instanceof HostRelayIpcError ? error.failure : void 0;
|
|
2192
|
-
}
|
|
2193
|
-
function socketErrorFailure(error) {
|
|
2194
|
-
switch (errorCode(error)) {
|
|
2195
|
-
case "EACCES":
|
|
2196
|
-
case "EPERM":
|
|
2197
|
-
return "permission_denied";
|
|
2198
|
-
case "ENOENT":
|
|
2199
|
-
return "socket_missing";
|
|
2200
|
-
case "ECONNREFUSED":
|
|
2201
|
-
return "connection_refused";
|
|
2202
|
-
default:
|
|
2203
|
-
return "unavailable";
|
|
2204
|
-
}
|
|
2205
|
-
}
|
|
2206
|
-
var HostRelayIpcAdmissionPort = class {
|
|
2207
|
-
endpoint;
|
|
2208
|
-
timeout_ms;
|
|
2209
|
-
constructor(endpoint, timeoutMs = 5e3) {
|
|
2210
|
-
this.endpoint = endpoint;
|
|
2211
|
-
this.timeout_ms = timeoutMs;
|
|
2212
|
-
}
|
|
2213
|
-
async admit(frame) {
|
|
2214
|
-
const response2 = await submitHostProducerFrame(this.endpoint, frame, this.timeout_ms);
|
|
2215
|
-
if (!relayAdmissionCode(response2.code)) throw new HostRelayIpcError(response2.code);
|
|
2216
|
-
return { code: response2.code, admitted_through_sequence: response2.admitted_through_sequence, delivered_through_sequence: response2.delivered_through_sequence };
|
|
2217
|
-
}
|
|
2218
|
-
};
|
|
2219
|
-
var HostRelayCurrentPromptPort = class {
|
|
2220
|
-
endpoint;
|
|
2221
|
-
timeout_ms;
|
|
2222
|
-
constructor(endpoint, timeoutMs = 5e3) {
|
|
2223
|
-
this.endpoint = endpoint;
|
|
2224
|
-
this.timeout_ms = timeoutMs;
|
|
2225
|
-
}
|
|
2226
|
-
async register(input) {
|
|
2227
|
-
const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA, ...input };
|
|
2228
|
-
const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
|
|
2229
|
-
if (response2.status !== "registered") throw new HostRelayIpcError("relay_unavailable", response2.status);
|
|
2424
|
+
}
|
|
2425
|
+
function positive(value, maximum) {
|
|
2426
|
+
return Number.isSafeInteger(value) && Number(value) > 0 && Number(value) <= maximum;
|
|
2427
|
+
}
|
|
2428
|
+
function boundedMilliseconds(value) {
|
|
2429
|
+
return positive(value, 6e4) && Number(value) >= 25;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
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";
|
|
2230
2441
|
}
|
|
2231
2442
|
};
|
|
2232
|
-
function
|
|
2233
|
-
|
|
2234
|
-
}
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
socket.destroy();
|
|
2246
|
-
if (error !== null) reject2(error);
|
|
2247
|
-
else if (response2 !== void 0) resolve(response2);
|
|
2248
|
-
};
|
|
2249
|
-
if (timeoutMs !== void 0) socket.setTimeout(timeoutMs, () => finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC timed out.", "timed_out")));
|
|
2250
|
-
if (signal?.aborted === true) {
|
|
2251
|
-
aborted();
|
|
2252
|
-
return;
|
|
2253
|
-
}
|
|
2254
|
-
signal?.addEventListener("abort", aborted, { once: true });
|
|
2255
|
-
socket.once("error", (error) => {
|
|
2256
|
-
const failure = socketErrorFailure(error);
|
|
2257
|
-
finish(new HostRelayIpcError("relay_unavailable", `Host Relay IPC is unavailable: ${failure}.`, failure));
|
|
2258
|
-
});
|
|
2259
|
-
socket.once("connect", () => socket.write(encoded));
|
|
2260
|
-
socket.on("data", (chunk) => {
|
|
2261
|
-
try {
|
|
2262
|
-
buffer = Buffer.concat([buffer, Buffer.from(chunk)]);
|
|
2263
|
-
const decoded = decode(buffer);
|
|
2264
|
-
if (decoded !== null) finish(null, decoded);
|
|
2265
|
-
} catch (error) {
|
|
2266
|
-
finish(new HostRelayIpcError("relay_unavailable", error instanceof Error ? error.message : "Host Relay IPC response is invalid.", "response_invalid"));
|
|
2267
|
-
}
|
|
2268
|
-
});
|
|
2269
|
-
socket.once("end", () => {
|
|
2270
|
-
if (!settled) finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC closed without a response.", "closed_early"));
|
|
2271
|
-
});
|
|
2272
|
-
});
|
|
2273
|
-
}
|
|
2274
|
-
function decodeHostRelayIpcResponse(input) {
|
|
2275
|
-
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}`)}.`);
|
|
2276
|
-
const length = input.readUInt32BE(0);
|
|
2277
|
-
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}` }])}.`);
|
|
2278
|
-
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
|
+
`;
|
|
2279
2456
|
try {
|
|
2280
|
-
|
|
2457
|
+
(sink ?? defaultHostServiceDiagnosticSink)(line);
|
|
2281
2458
|
} catch {
|
|
2282
|
-
throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC response is invalid.");
|
|
2283
2459
|
}
|
|
2284
|
-
if (!validResponse(value)) throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC response shape is invalid.");
|
|
2285
|
-
return value;
|
|
2286
2460
|
}
|
|
2287
|
-
function
|
|
2288
|
-
|
|
2461
|
+
function defaultHostServiceDiagnosticSink(line) {
|
|
2462
|
+
writeHostServiceLogLine(line);
|
|
2463
|
+
process.stderr.write(line);
|
|
2289
2464
|
}
|
|
2290
|
-
|
|
2291
|
-
|
|
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`;
|
|
2292
2469
|
}
|
|
2293
|
-
function
|
|
2294
|
-
|
|
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;
|
|
2295
2482
|
}
|
|
2296
|
-
function
|
|
2297
|
-
|
|
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);
|
|
2298
2500
|
}
|
|
2299
|
-
function
|
|
2300
|
-
|
|
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);
|
|
2301
2506
|
}
|
|
2302
|
-
function
|
|
2303
|
-
if (
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
if (buffer.length < length + HOST_RELAY_IPC_LENGTH_BYTES) return null;
|
|
2307
|
-
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}` }])}.`);
|
|
2308
|
-
return decodeHostRelayIpcResponse(buffer);
|
|
2309
|
-
}
|
|
2310
|
-
function decodeCurrentPromptResponseBuffer(buffer) {
|
|
2311
|
-
const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES);
|
|
2312
|
-
if (value === null) return null;
|
|
2313
|
-
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");
|
|
2314
|
-
return { schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status: value.status };
|
|
2315
|
-
}
|
|
2316
|
-
function decodeLengthPrefixedJson(input, maximum) {
|
|
2317
|
-
if (input.length < HOST_RELAY_IPC_LENGTH_BYTES) return null;
|
|
2318
|
-
const length = input.readUInt32BE(0);
|
|
2319
|
-
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}`)}.`);
|
|
2320
|
-
if (input.length < length + HOST_RELAY_IPC_LENGTH_BYTES) return null;
|
|
2321
|
-
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}` }])}.`);
|
|
2322
|
-
try {
|
|
2323
|
-
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.subarray(HOST_RELAY_IPC_LENGTH_BYTES)));
|
|
2324
|
-
} catch {
|
|
2325
|
-
throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC message is invalid.");
|
|
2326
|
-
}
|
|
2507
|
+
async function appendHostServiceLogLine(path, line) {
|
|
2508
|
+
if (!isAbsolute(path)) return;
|
|
2509
|
+
await ensureHostServiceLogDirectory(path);
|
|
2510
|
+
await appendFile(path, line);
|
|
2327
2511
|
}
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
return
|
|
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;
|
|
2334
2524
|
}
|
|
2335
|
-
function
|
|
2336
|
-
|
|
2337
|
-
return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => required.includes(key) || optional.includes(key));
|
|
2525
|
+
function isNotFound(error) {
|
|
2526
|
+
return errorCode(error) === "ENOENT";
|
|
2338
2527
|
}
|
|
2339
2528
|
function errorCode(error) {
|
|
2340
|
-
return
|
|
2529
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "unknown";
|
|
2341
2530
|
}
|
|
2342
2531
|
|
|
2343
2532
|
// host/relay/src/host-producer-core.ts
|
|
2533
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
2344
2534
|
var HOST_TRANSCRIPT_CURSOR_REASONS = vocabulary(["transcript_replaced", "transcript_truncated", "transcript_rewritten", "transcript_malformed"]);
|
|
2345
2535
|
function isHostTranscriptCursorReason(value) {
|
|
2346
2536
|
return typeof value === "string" && HOST_TRANSCRIPT_CURSOR_REASONS.some((reason) => reason === value);
|
|
2347
2537
|
}
|
|
2348
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 code = transcriptErrorCode(error);
|
|
2546
|
+
if (code === "EFBIG") return "size_budget";
|
|
2547
|
+
if (code !== null && TRANSCRIPT_SCAN_READ_ERROR_ERRNOS.includes(code)) return "read_error";
|
|
2548
|
+
return "other";
|
|
2549
|
+
}
|
|
2550
|
+
function transcriptErrorCode(error) {
|
|
2551
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
|
|
2552
|
+
}
|
|
2349
2553
|
var HOST_TRANSCRIPT_READ_CHUNK_BYTES = 256 * 1024;
|
|
2350
2554
|
async function readTranscriptCompleteChunk(handle, offset, size) {
|
|
2351
2555
|
const available = Math.max(0, size - offset);
|
|
@@ -2390,10 +2594,10 @@ async function transcriptCursorBoundsReason(handle, cursor, size) {
|
|
|
2390
2594
|
return null;
|
|
2391
2595
|
}
|
|
2392
2596
|
function transcriptSha256(value) {
|
|
2393
|
-
return
|
|
2597
|
+
return createHash4("sha256").update(value).digest("hex");
|
|
2394
2598
|
}
|
|
2395
2599
|
function transcriptObjectSha256(value) {
|
|
2396
|
-
return
|
|
2600
|
+
return createHash4("sha256").update(JSON.stringify(value), "utf8").digest("hex");
|
|
2397
2601
|
}
|
|
2398
2602
|
function isTranscriptRecord(value) {
|
|
2399
2603
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -2462,11 +2666,10 @@ var HostTranscriptWatcher = class {
|
|
|
2462
2666
|
async openTranscript(registration, scan, handlers) {
|
|
2463
2667
|
let handle;
|
|
2464
2668
|
try {
|
|
2465
|
-
handle = await
|
|
2669
|
+
handle = await openNoFollow(this.transcriptPathOf(registration));
|
|
2466
2670
|
return await scan(handle);
|
|
2467
2671
|
} catch (error) {
|
|
2468
2672
|
if (handle === void 0 && isMissingTranscriptError(error)) return handlers.missing();
|
|
2469
|
-
if (hostRelayExchangeFailure(error) !== void 0) throw error;
|
|
2470
2673
|
return handlers.failed(handle, error);
|
|
2471
2674
|
} finally {
|
|
2472
2675
|
await handle?.close();
|
|
@@ -2512,29 +2715,12 @@ var HostTranscriptWatcher = class {
|
|
|
2512
2715
|
return transcriptCursorBoundsReason(handle, cursor, size);
|
|
2513
2716
|
}
|
|
2514
2717
|
};
|
|
2515
|
-
var HOST_PRODUCER_STATE_IDENTITY = {
|
|
2516
|
-
codex: { file_name: "codex-producer-state.json", schema: "ceal.codex_producer_state.v1" },
|
|
2517
|
-
claude: { file_name: "claude-producer-state.json", schema: "ceal.claude_producer_state.v1" }
|
|
2518
|
-
};
|
|
2519
2718
|
var HostProducerDrainCore = class {
|
|
2520
2719
|
host;
|
|
2521
|
-
relay;
|
|
2522
|
-
statePath;
|
|
2523
|
-
stateSchema;
|
|
2524
|
-
installationRef;
|
|
2525
|
-
epoch;
|
|
2526
2720
|
now;
|
|
2527
2721
|
tail = Promise.resolve();
|
|
2528
2722
|
constructor(host, runtime) {
|
|
2529
|
-
if (!isHostSafeRef(runtime.producer_installation_ref)) throw host.fail("configuration", `${host.producer_label} producer installation identity is invalid.`);
|
|
2530
|
-
const epoch = runtime.producer_epoch ?? 1;
|
|
2531
|
-
if (!Number.isSafeInteger(epoch) || epoch <= 0) throw host.fail("configuration", `${host.producer_label} producer epoch is invalid.`);
|
|
2532
2723
|
this.host = host;
|
|
2533
|
-
this.relay = runtime.relay;
|
|
2534
|
-
this.statePath = join2(host.outbox_directory, HOST_PRODUCER_STATE_IDENTITY[host.producer_id].file_name);
|
|
2535
|
-
this.stateSchema = HOST_PRODUCER_STATE_IDENTITY[host.producer_id].schema;
|
|
2536
|
-
this.installationRef = runtime.producer_installation_ref;
|
|
2537
|
-
this.epoch = epoch;
|
|
2538
2724
|
this.now = runtime.now ?? (() => /* @__PURE__ */ new Date());
|
|
2539
2725
|
}
|
|
2540
2726
|
drainOutbox() {
|
|
@@ -2544,145 +2730,29 @@ var HostProducerDrainCore = class {
|
|
|
2544
2730
|
}
|
|
2545
2731
|
async drainLocked() {
|
|
2546
2732
|
const read = await this.host.listEnvelopes();
|
|
2547
|
-
let
|
|
2733
|
+
let removed = 0;
|
|
2548
2734
|
let expired = 0;
|
|
2549
|
-
let retained = 0;
|
|
2550
2735
|
for (const envelope of read.entries) {
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
await this.host.removeEnvelope(envelope);
|
|
2554
|
-
expired += 1;
|
|
2555
|
-
continue;
|
|
2556
|
-
}
|
|
2557
|
-
const frames = await this.reserve(envelope);
|
|
2558
|
-
let complete = true;
|
|
2559
|
-
for (const frame of frames) {
|
|
2560
|
-
const outcome = await this.relay.admit(frame);
|
|
2561
|
-
if (outcome.code !== "admitted" && outcome.code !== "already_admitted") {
|
|
2562
|
-
complete = false;
|
|
2563
|
-
break;
|
|
2564
|
-
}
|
|
2565
|
-
}
|
|
2566
|
-
if (!complete) {
|
|
2567
|
-
retained += 1;
|
|
2568
|
-
break;
|
|
2569
|
-
}
|
|
2736
|
+
if (Date.parse(this.host.expiresAtOf(envelope)) <= this.now().getTime()) expired += 1;
|
|
2737
|
+
else removed += 1;
|
|
2570
2738
|
await this.host.removeEnvelope(envelope);
|
|
2571
|
-
await this.release(this.host.submissionRefOf(envelope));
|
|
2572
|
-
admitted += 1;
|
|
2573
|
-
}
|
|
2574
|
-
return { admitted, retained: retained + Math.max(0, read.entries.length - admitted - expired - retained), expired, malformed: read.malformed };
|
|
2575
|
-
}
|
|
2576
|
-
async hasReservation(submissionRef) {
|
|
2577
|
-
return (await this.readState()).pending[submissionRef] !== void 0;
|
|
2578
|
-
}
|
|
2579
|
-
async reserve(envelope) {
|
|
2580
|
-
const state = await this.readState();
|
|
2581
|
-
const submissionRef = this.host.submissionRefOf(envelope);
|
|
2582
|
-
const existing = state.pending[submissionRef];
|
|
2583
|
-
if (existing !== void 0) return existing;
|
|
2584
|
-
const drafts = await this.host.mapEnvelope(envelope);
|
|
2585
|
-
const frames = drafts.map((draft, offset) => ({
|
|
2586
|
-
schema_version: "ceal.host_producer_frame.v1",
|
|
2587
|
-
producer_id: this.host.producer_id,
|
|
2588
|
-
producer_installation_ref: this.installationRef,
|
|
2589
|
-
producer_epoch: this.epoch,
|
|
2590
|
-
sequence: state.next_sequence + offset,
|
|
2591
|
-
frame_kind: draft.frame_kind,
|
|
2592
|
-
occurred_at: this.host.createdAtOf(envelope),
|
|
2593
|
-
payload: draft.payload
|
|
2594
|
-
}));
|
|
2595
|
-
await this.writeState({ ...state, next_sequence: state.next_sequence + frames.length, pending: { ...state.pending, [submissionRef]: frames } });
|
|
2596
|
-
return frames;
|
|
2597
|
-
}
|
|
2598
|
-
async release(submissionRef) {
|
|
2599
|
-
const state = await this.readState();
|
|
2600
|
-
if (state.pending[submissionRef] === void 0) return;
|
|
2601
|
-
const pending = { ...state.pending };
|
|
2602
|
-
delete pending[submissionRef];
|
|
2603
|
-
await this.writeState({ ...state, pending });
|
|
2604
|
-
}
|
|
2605
|
-
async readState() {
|
|
2606
|
-
try {
|
|
2607
|
-
const value = JSON.parse(await readFile(this.statePath, "utf8"));
|
|
2608
|
-
return this.decodeState(value);
|
|
2609
|
-
} catch (error) {
|
|
2610
|
-
if (errorCode2(error) !== "ENOENT") throw error;
|
|
2611
|
-
return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: 1, pending: {} };
|
|
2612
|
-
}
|
|
2613
|
-
}
|
|
2614
|
-
async writeState(state) {
|
|
2615
|
-
await mkdir2(this.host.outbox_directory, { recursive: true, mode: 448 });
|
|
2616
|
-
const temporary = `${this.statePath}.${process.pid}.new`;
|
|
2617
|
-
await writeFile(temporary, JSON.stringify(state), { mode: 384 });
|
|
2618
|
-
await rename2(temporary, this.statePath);
|
|
2619
|
-
}
|
|
2620
|
-
decodeState(value) {
|
|
2621
|
-
const label = this.host.producer_label;
|
|
2622
|
-
const fail = (code, message) => this.host.fail(code, message);
|
|
2623
|
-
if (!isDrainStateHeader(value, this.stateSchema, this.installationRef)) {
|
|
2624
|
-
throw fail("storage", `${label} producer state header is malformed or belongs to another installation (${refusalFacts([
|
|
2625
|
-
{ name: "schema_version", value: isTranscriptRecord(value) ? value.schema_version : void 0, expected: this.stateSchema },
|
|
2626
|
-
{ name: "producer_installation_ref", value: isTranscriptRecord(value) ? value.producer_installation_ref : void 0, expected: this.installationRef }
|
|
2627
|
-
])}).`);
|
|
2628
2739
|
}
|
|
2629
|
-
|
|
2630
|
-
const pending = {};
|
|
2631
|
-
const storedEpoch = Number(value.producer_epoch);
|
|
2632
|
-
for (const [submissionRef, frames] of Object.entries(value.pending)) {
|
|
2633
|
-
if (!isHostSafeRef(submissionRef) || !Array.isArray(frames) || frames.length === 0) throw fail("storage", `${label} producer pending reservation is malformed.`);
|
|
2634
|
-
pending[submissionRef] = frames.map((frame) => this.decodeStoredFrame(frame, storedEpoch));
|
|
2635
|
-
}
|
|
2636
|
-
return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: Number(value.next_sequence), pending };
|
|
2637
|
-
}
|
|
2638
|
-
decodeStoredFrame(value, storedEpoch) {
|
|
2639
|
-
const label = this.host.producer_label;
|
|
2640
|
-
const json = Buffer.from(JSON.stringify(value), "utf8");
|
|
2641
|
-
const encoded = Buffer.allocUnsafe(json.length + 4);
|
|
2642
|
-
encoded.writeUInt32BE(json.length, 0);
|
|
2643
|
-
json.copy(encoded, 4);
|
|
2644
|
-
const frame = decodeHostProducerFrame(encoded);
|
|
2645
|
-
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([
|
|
2646
|
-
{ name: "producer_id", value: frame.producer_id, expected: this.host.producer_id },
|
|
2647
|
-
{ name: "producer_installation_ref", value: frame.producer_installation_ref, expected: this.installationRef },
|
|
2648
|
-
{ name: "producer_epoch", value: frame.producer_epoch, expected: `at most ${storedEpoch}` }
|
|
2649
|
-
])}).`);
|
|
2650
|
-
return frame;
|
|
2740
|
+
return { removed, expired, malformed: read.malformed };
|
|
2651
2741
|
}
|
|
2652
2742
|
};
|
|
2653
|
-
function isDrainStateHeader(value, schema, installationRef) {
|
|
2654
|
-
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);
|
|
2655
|
-
}
|
|
2656
|
-
function errorCode2(error) {
|
|
2657
|
-
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
|
|
2658
|
-
}
|
|
2659
|
-
|
|
2660
|
-
// host/relay/src/relay-quarantine-store.ts
|
|
2661
|
-
var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
|
|
2662
|
-
var RELAY_QUARANTINE_FRAME_REASONS = ["frame_identity_conflict", "sequence_gap", "gateway_refused"];
|
|
2663
|
-
var RELAY_QUARANTINE_REASONS = [...RELAY_QUARANTINE_FRAME_REASONS, ...HOST_PRODUCER_FRAME_ERROR_CODES];
|
|
2664
|
-
|
|
2665
|
-
// host/relay/src/relay-admission-store.ts
|
|
2666
|
-
var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
|
|
2667
|
-
var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
|
|
2668
|
-
var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
|
|
2669
|
-
|
|
2670
|
-
// host/codex/src/codex-producer.ts
|
|
2671
|
-
import { createHash as createHash4 } from "node:crypto";
|
|
2672
2743
|
|
|
2673
2744
|
// host/codex/src/codex-outbox.ts
|
|
2674
|
-
import {
|
|
2675
|
-
import {
|
|
2676
|
-
import { dirname, isAbsolute, join as join3 } from "node:path";
|
|
2745
|
+
import { mkdir as mkdir3, open as open3, readdir, rename as rename2, stat as stat2, unlink as unlink2 } from "node:fs/promises";
|
|
2746
|
+
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join2 } from "node:path";
|
|
2677
2747
|
|
|
2678
2748
|
// host/invocation-context.ts
|
|
2679
|
-
var
|
|
2749
|
+
var HOST_INVOCATION_TURN_SELECTION_SCHEMA = "ceal.host_invocation_turn_selection.v1";
|
|
2680
2750
|
var HOST_INVOCATION_CONTEXT_REQUIRED_KEYS = ["schema_version", "identity", "prompt_ref", "expires_at"];
|
|
2681
2751
|
function invocationContextKey(identity) {
|
|
2682
2752
|
return cealRequestSha256({ host_source_ref: identity.host_source_ref, host_session_ref: identity.host_session_ref });
|
|
2683
2753
|
}
|
|
2684
2754
|
function hasHostInvocationIdentityKeys(value) {
|
|
2685
|
-
return
|
|
2755
|
+
return exactKeys(value, CEAL_HOST_INVOCATION_IDENTITY_KEYS);
|
|
2686
2756
|
}
|
|
2687
2757
|
function hasHostInvocationContextKeys(value, optional = []) {
|
|
2688
2758
|
return requiredKeys(value, HOST_INVOCATION_CONTEXT_REQUIRED_KEYS, optional);
|
|
@@ -2708,12 +2778,9 @@ function invocationContextFromTurn(identity, promptRef, expiresAt, descriptor) {
|
|
|
2708
2778
|
return Object.freeze(context);
|
|
2709
2779
|
}
|
|
2710
2780
|
function isHostInvocationTurnSelection(value) {
|
|
2711
|
-
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]));
|
|
2712
2782
|
}
|
|
2713
|
-
function
|
|
2714
|
-
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;
|
|
2715
|
-
}
|
|
2716
|
-
function exactKeys3(value, keys) {
|
|
2783
|
+
function exactKeys(value, keys) {
|
|
2717
2784
|
const actual = Object.keys(value);
|
|
2718
2785
|
return actual.length === keys.length && keys.every((key) => Object.hasOwn(value, key)) && actual.every((key) => keys.includes(key));
|
|
2719
2786
|
}
|
|
@@ -2722,16 +2789,68 @@ function requiredKeys(value, required, optional) {
|
|
|
2722
2789
|
const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
|
|
2723
2790
|
return actual.length >= required.length && actual.length <= allowed.size && required.every((key) => Object.hasOwn(value, key)) && actual.every((key) => allowed.has(key));
|
|
2724
2791
|
}
|
|
2725
|
-
function
|
|
2792
|
+
function isRecord3(value) {
|
|
2726
2793
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2727
2794
|
}
|
|
2728
|
-
function
|
|
2795
|
+
function safeRef(value) {
|
|
2729
2796
|
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
|
|
2730
2797
|
}
|
|
2731
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
|
+
var DEFAULT_PORT = {
|
|
2816
|
+
platform: process.platform,
|
|
2817
|
+
mkdir: (path, options) => mkdir2(path, options),
|
|
2818
|
+
lstat: (path) => lstat3(path),
|
|
2819
|
+
open_file: (path) => open2(path, "wx", 384),
|
|
2820
|
+
rename,
|
|
2821
|
+
unlink,
|
|
2822
|
+
open_directory: (path) => open2(path),
|
|
2823
|
+
unique_ref: () => `${process.pid}.${crypto.randomUUID()}`
|
|
2824
|
+
};
|
|
2825
|
+
|
|
2826
|
+
// host/relay/src/relay-quarantine-store.ts
|
|
2827
|
+
var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
|
|
2828
|
+
var RELAY_QUARANTINE_FRAME_REASONS = ["frame_identity_conflict", "sequence_gap", "gateway_refused"];
|
|
2829
|
+
var RELAY_QUARANTINE_REASONS = [...RELAY_QUARANTINE_FRAME_REASONS, ...HOST_PRODUCER_FRAME_ERROR_CODES];
|
|
2830
|
+
|
|
2831
|
+
// host/relay/src/relay-admission-store.ts
|
|
2832
|
+
var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, producer_count: 4096, producer_bytes: 32 * 1024 * 1024 };
|
|
2833
|
+
var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
|
|
2834
|
+
var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
|
|
2835
|
+
|
|
2836
|
+
// host/relay/src/relay-pipe-handshake.ts
|
|
2837
|
+
var RELAY_PIPE_FRAME_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
2838
|
+
|
|
2839
|
+
// host/relay/src/relay-ipc.ts
|
|
2840
|
+
var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
2841
|
+
var HOST_RELAY_EXCHANGE_FAILURES = vocabulary([
|
|
2842
|
+
"permission_denied",
|
|
2843
|
+
"socket_missing",
|
|
2844
|
+
"connection_refused",
|
|
2845
|
+
"closed_early",
|
|
2846
|
+
"response_invalid",
|
|
2847
|
+
"timed_out",
|
|
2848
|
+
"cancelled",
|
|
2849
|
+
"unavailable"
|
|
2850
|
+
]);
|
|
2851
|
+
|
|
2732
2852
|
// host/codex/src/codex-types.ts
|
|
2733
2853
|
var CODEX_OUTBOX_SCHEMA_VERSION = "ceal.codex_observation_outbox.v1";
|
|
2734
|
-
var CODEX_TURN_BINDING_SCHEMA_VERSION = "ceal.codex_turn_binding.v1";
|
|
2735
2854
|
var CODEX_SESSION_SCHEMA_VERSION = "ceal.codex_session.v1";
|
|
2736
2855
|
var CODEX_INVOCATION_CONTEXT_SCHEMA_VERSION = "ceal.codex_invocation_context.v1";
|
|
2737
2856
|
var CODEX_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION = "ceal.codex_transcript_registration.v1";
|
|
@@ -2758,59 +2877,40 @@ function validateCodexHostInvocationContext(value) {
|
|
|
2758
2877
|
validateContextReferences(value);
|
|
2759
2878
|
validateContextTimes(value);
|
|
2760
2879
|
}
|
|
2761
|
-
function invocationContextFromBinding(binding, turnGenerationRef) {
|
|
2762
|
-
const context = {
|
|
2763
|
-
schema_version: CODEX_INVOCATION_CONTEXT_SCHEMA_VERSION,
|
|
2764
|
-
identity: binding.identity,
|
|
2765
|
-
prompt_ref: binding.prompt_ref,
|
|
2766
|
-
request_ref: binding.request_ref,
|
|
2767
|
-
bound_at: binding.bound_at,
|
|
2768
|
-
expires_at: binding.expires_at,
|
|
2769
|
-
...turnGenerationRef === void 0 ? {} : { turn_generation_ref: turnGenerationRef }
|
|
2770
|
-
};
|
|
2771
|
-
validateCodexHostInvocationContext(context);
|
|
2772
|
-
return Object.freeze(context);
|
|
2773
|
-
}
|
|
2774
2880
|
function validateIdentity2(value) {
|
|
2775
|
-
if (!
|
|
2881
|
+
if (!isRecord4(value) || !hasHostInvocationIdentityKeys(value) || ![value.host_session_ref, value.host_turn_ref, value.host_source_ref].every(safeRef2)) {
|
|
2776
2882
|
throw new CodexHostAdapterError("storage", "Codex Host invocation identity is malformed.");
|
|
2777
2883
|
}
|
|
2778
2884
|
}
|
|
2779
2885
|
function isContextShape(value) {
|
|
2780
|
-
return
|
|
2886
|
+
return isRecord4(value) && hasHostInvocationContextKeys(value, ["turn_generation_ref"]) && value.schema_version === CODEX_INVOCATION_CONTEXT_SCHEMA_VERSION;
|
|
2781
2887
|
}
|
|
2782
2888
|
function validateContextReferences(value) {
|
|
2783
|
-
if (!
|
|
2889
|
+
if (!safeRef2(value.prompt_ref) || !optionalSafeRef(value.turn_generation_ref)) throw new CodexHostAdapterError("storage", "Codex Host invocation context is malformed.");
|
|
2784
2890
|
}
|
|
2785
2891
|
function validateContextTimes(value) {
|
|
2786
2892
|
const expiresAt = value.expires_at;
|
|
2787
|
-
|
|
2788
|
-
if (!timestamp2(expiresAt) || !optionalTimestamp(boundAt)) throw new CodexHostAdapterError("storage", "Codex Host invocation context is malformed.");
|
|
2789
|
-
if (typeof boundAt === "string" && Date.parse(boundAt) > Date.parse(expiresAt)) throw new CodexHostAdapterError("storage", `Codex Host invocation context has invalid binding time (${refusalFact("bound_at", boundAt, `at or before ${expiresAt}`)}).`);
|
|
2893
|
+
if (!timestamp(expiresAt)) throw new CodexHostAdapterError("storage", "Codex Host invocation context is malformed.");
|
|
2790
2894
|
}
|
|
2791
2895
|
function optionalSafeRef(value) {
|
|
2792
|
-
return value === void 0 ||
|
|
2896
|
+
return value === void 0 || safeRef2(value);
|
|
2793
2897
|
}
|
|
2794
|
-
function
|
|
2795
|
-
return value === void 0 || timestamp2(value);
|
|
2796
|
-
}
|
|
2797
|
-
function safeRef3(value) {
|
|
2898
|
+
function safeRef2(value) {
|
|
2798
2899
|
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
|
|
2799
2900
|
}
|
|
2800
|
-
function
|
|
2901
|
+
function timestamp(value) {
|
|
2801
2902
|
return typeof value === "string" && value.length <= 64 && Number.isFinite(Date.parse(value));
|
|
2802
2903
|
}
|
|
2803
|
-
function
|
|
2904
|
+
function isRecord4(value) {
|
|
2804
2905
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2805
2906
|
}
|
|
2806
2907
|
|
|
2807
2908
|
// host/codex/src/codex-outbox.ts
|
|
2808
|
-
var
|
|
2909
|
+
var SAFE_REF3 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
2809
2910
|
var CODEX_TRANSCRIPT_PATH_MAX_CHARACTERS = 4096;
|
|
2810
2911
|
var CodexOutboxStore = class {
|
|
2811
2912
|
directory;
|
|
2812
2913
|
entriesDirectory;
|
|
2813
|
-
bindingsDirectory;
|
|
2814
2914
|
sessionsDirectory;
|
|
2815
2915
|
activeContextsDirectory;
|
|
2816
2916
|
turnSelectionsDirectory;
|
|
@@ -2819,17 +2919,16 @@ var CodexOutboxStore = class {
|
|
|
2819
2919
|
constructor(directory) {
|
|
2820
2920
|
if (typeof directory !== "string" || directory.length === 0) throw new CodexHostAdapterError("configuration", "Codex outbox directory is required.");
|
|
2821
2921
|
this.directory = directory;
|
|
2822
|
-
this.entriesDirectory =
|
|
2823
|
-
this.
|
|
2824
|
-
this.
|
|
2825
|
-
this.
|
|
2826
|
-
this.
|
|
2827
|
-
this.
|
|
2828
|
-
this.cursorsDirectory = join3(directory, "transcript-cursors");
|
|
2922
|
+
this.entriesDirectory = join2(directory, "entries");
|
|
2923
|
+
this.sessionsDirectory = join2(directory, "sessions");
|
|
2924
|
+
this.activeContextsDirectory = join2(directory, "active-contexts");
|
|
2925
|
+
this.turnSelectionsDirectory = join2(directory, "turn-selections");
|
|
2926
|
+
this.registrationsDirectory = join2(directory, "transcript-registrations");
|
|
2927
|
+
this.cursorsDirectory = join2(directory, "transcript-cursors");
|
|
2829
2928
|
}
|
|
2830
2929
|
async prepare() {
|
|
2831
|
-
for (const path of [this.directory, this.entriesDirectory, this.
|
|
2832
|
-
await
|
|
2930
|
+
for (const path of [this.directory, this.entriesDirectory, this.sessionsDirectory, this.activeContextsDirectory, this.turnSelectionsDirectory, this.registrationsDirectory, this.cursorsDirectory]) {
|
|
2931
|
+
await ensureOwnerDirectory(path);
|
|
2833
2932
|
}
|
|
2834
2933
|
}
|
|
2835
2934
|
async put(entry) {
|
|
@@ -2844,7 +2943,7 @@ var CodexOutboxStore = class {
|
|
|
2844
2943
|
for (const name of await readdir(this.entriesDirectory)) {
|
|
2845
2944
|
if (!name.endsWith(".json")) continue;
|
|
2846
2945
|
try {
|
|
2847
|
-
const value = JSON.parse(await
|
|
2946
|
+
const value = JSON.parse(await readOwnerStateFile(join2(this.entriesDirectory, name)));
|
|
2848
2947
|
validateEnvelope(value);
|
|
2849
2948
|
entries.push(value);
|
|
2850
2949
|
} catch {
|
|
@@ -2859,14 +2958,9 @@ var CodexOutboxStore = class {
|
|
|
2859
2958
|
try {
|
|
2860
2959
|
await unlink2(this.entryPath(entry.submission_ref));
|
|
2861
2960
|
} catch (error) {
|
|
2862
|
-
if (!
|
|
2961
|
+
if (!isNotFound2(error)) throw storageError("Unable to remove the Codex outbox entry.");
|
|
2863
2962
|
}
|
|
2864
2963
|
}
|
|
2865
|
-
async writeBinding(binding) {
|
|
2866
|
-
validateBinding(binding);
|
|
2867
|
-
await this.prepare();
|
|
2868
|
-
await writeAtomicJson(this.bindingPath(binding.identity), binding);
|
|
2869
|
-
}
|
|
2870
2964
|
/**
|
|
2871
2965
|
* Select the exact Host turn that model-reachable commands may use. A new
|
|
2872
2966
|
* prompt replaces this projection, so it cannot inherit the prior turn's
|
|
@@ -2882,7 +2976,7 @@ var CodexOutboxStore = class {
|
|
|
2882
2976
|
try {
|
|
2883
2977
|
await unlink2(path);
|
|
2884
2978
|
} catch (unlinkError) {
|
|
2885
|
-
if (!
|
|
2979
|
+
if (!isNotFound2(unlinkError)) throw storageError("Unable to clear a failed Codex Host context replacement.");
|
|
2886
2980
|
}
|
|
2887
2981
|
throw error;
|
|
2888
2982
|
}
|
|
@@ -2893,7 +2987,7 @@ var CodexOutboxStore = class {
|
|
|
2893
2987
|
await writeAtomicJson(this.turnSelectionPath(selection.host_source_ref, selection.host_session_ref), selection);
|
|
2894
2988
|
}
|
|
2895
2989
|
async readInvocationTurnSelection(hostSourceRef, hostSessionRef) {
|
|
2896
|
-
if (!
|
|
2990
|
+
if (!SAFE_REF3.test(hostSourceRef) || !SAFE_REF3.test(hostSessionRef)) throw new CodexHostAdapterError("invalid_input", "Codex Host turn selection identity is invalid.");
|
|
2897
2991
|
await this.prepare();
|
|
2898
2992
|
return readOptionalJson(this.turnSelectionPath(hostSourceRef, hostSessionRef), validateTurnSelection);
|
|
2899
2993
|
}
|
|
@@ -2906,23 +3000,11 @@ var CodexOutboxStore = class {
|
|
|
2906
3000
|
await writeAtomicJson(path, { ...active, turn_generation_ref: turnGenerationRef });
|
|
2907
3001
|
return true;
|
|
2908
3002
|
}
|
|
2909
|
-
/**
|
|
2910
|
-
* Publish a Request reference only while the acknowledged binding is still
|
|
2911
|
-
* the active turn. A delayed relay response therefore cannot overwrite a
|
|
2912
|
-
* newer prompt's context.
|
|
2913
|
-
*/
|
|
2914
|
-
async bindInvocationContext(binding, now = /* @__PURE__ */ new Date()) {
|
|
2915
|
-
validateBinding(binding);
|
|
2916
|
-
const active = await this.readInvocationContext(binding.identity.host_source_ref, binding.identity.host_session_ref, now);
|
|
2917
|
-
if (active === null || !sameIdentity(active.identity, binding.identity)) return false;
|
|
2918
|
-
await writeAtomicJson(this.activeContextPath(binding.identity), invocationContextFromBinding(binding, active.turn_generation_ref));
|
|
2919
|
-
return true;
|
|
2920
|
-
}
|
|
2921
3003
|
/** Return only the live session projection; never fall back to another turn. */
|
|
2922
3004
|
async readInvocationContext(hostSourceRef, hostSessionRef, now = /* @__PURE__ */ new Date()) {
|
|
2923
3005
|
if (!(now instanceof Date) || !Number.isFinite(now.getTime())) throw new CodexHostAdapterError("invalid_input", "Codex Host context clock is invalid.");
|
|
2924
3006
|
const context = await this.readRawInvocationContext(hostSourceRef, hostSessionRef);
|
|
2925
|
-
return isLiveSessionProjection(context, now)
|
|
3007
|
+
return isLiveSessionProjection(context, now) ? context : null;
|
|
2926
3008
|
}
|
|
2927
3009
|
/**
|
|
2928
3010
|
* The latest Codex session projection even when past expiry, so the
|
|
@@ -2930,10 +3012,10 @@ var CodexOutboxStore = class {
|
|
|
2930
3012
|
* a missing one. Every other caller keeps the fail-closed read above.
|
|
2931
3013
|
*/
|
|
2932
3014
|
async readRawInvocationContext(hostSourceRef, hostSessionRef) {
|
|
2933
|
-
if (!
|
|
3015
|
+
if (!SAFE_REF3.test(hostSourceRef) || !SAFE_REF3.test(hostSessionRef)) throw new CodexHostAdapterError("invalid_input", "Codex Host session identity is invalid.");
|
|
2934
3016
|
await this.prepare();
|
|
2935
3017
|
const context = await readLatestSessionProjection(this.activeContextPath({ host_source_ref: hostSourceRef, host_session_ref: hostSessionRef }), hostSourceRef, hostSessionRef, (file) => readOptionalJson(file, validateCodexHostInvocationContext));
|
|
2936
|
-
return
|
|
3018
|
+
return context;
|
|
2937
3019
|
}
|
|
2938
3020
|
/** Clear the projection only if the caller still owns its exact active turn. */
|
|
2939
3021
|
async clearInvocationContext(identity, now = /* @__PURE__ */ new Date()) {
|
|
@@ -2943,15 +3025,10 @@ var CodexOutboxStore = class {
|
|
|
2943
3025
|
try {
|
|
2944
3026
|
await unlink2(this.activeContextPath(identity));
|
|
2945
3027
|
} catch (error) {
|
|
2946
|
-
if (!
|
|
3028
|
+
if (!isNotFound2(error)) throw storageError("Unable to clear the Codex Host invocation context.");
|
|
2947
3029
|
}
|
|
2948
3030
|
return true;
|
|
2949
3031
|
}
|
|
2950
|
-
async readBinding(identity) {
|
|
2951
|
-
validateIdentity3(identity);
|
|
2952
|
-
await this.prepare();
|
|
2953
|
-
return readOptionalJson(this.bindingPath(identity), validateBinding);
|
|
2954
|
-
}
|
|
2955
3032
|
async saveSession(snapshot) {
|
|
2956
3033
|
validateSession(snapshot);
|
|
2957
3034
|
await this.prepare();
|
|
@@ -2959,7 +3036,7 @@ var CodexOutboxStore = class {
|
|
|
2959
3036
|
}
|
|
2960
3037
|
async readSession(sourceRef, sessionRef) {
|
|
2961
3038
|
const identity = { host_source_ref: sourceRef, host_session_ref: sessionRef };
|
|
2962
|
-
if (!
|
|
3039
|
+
if (!SAFE_REF3.test(sourceRef) || !SAFE_REF3.test(sessionRef)) throw new CodexHostAdapterError("invalid_input", "Codex session identity is invalid.");
|
|
2963
3040
|
await this.prepare();
|
|
2964
3041
|
return readOptionalJson(this.sessionPath(identity.host_source_ref, identity.host_session_ref), validateSession);
|
|
2965
3042
|
}
|
|
@@ -2992,7 +3069,7 @@ var CodexOutboxStore = class {
|
|
|
2992
3069
|
for (const name of await readdir(this.registrationsDirectory)) {
|
|
2993
3070
|
if (!name.endsWith(".json")) continue;
|
|
2994
3071
|
try {
|
|
2995
|
-
const value = JSON.parse(await
|
|
3072
|
+
const value = JSON.parse(await readOwnerStateFile(join2(this.registrationsDirectory, name)));
|
|
2996
3073
|
validateRegistration(value);
|
|
2997
3074
|
registrations.push(value);
|
|
2998
3075
|
} catch {
|
|
@@ -3011,66 +3088,52 @@ var CodexOutboxStore = class {
|
|
|
3011
3088
|
await writeAtomicJson(this.cursorPath(cursor.host_source_ref, cursor.host_session_ref), cursor);
|
|
3012
3089
|
}
|
|
3013
3090
|
async findByAlias(sourceRef, sessionRef, alias) {
|
|
3014
|
-
if (!
|
|
3091
|
+
if (!SAFE_REF3.test(sourceRef) || !SAFE_REF3.test(sessionRef) || !SAFE_REF3.test(alias)) return null;
|
|
3015
3092
|
const read = await this.list();
|
|
3016
3093
|
const envelope = findEnvelopeByAlias(read.entries, sourceRef, sessionRef, alias);
|
|
3017
3094
|
if (envelope !== null) return envelopeMatch(envelope);
|
|
3018
3095
|
await this.prepare();
|
|
3019
|
-
const binding = await findBindingByAlias(this.bindingsDirectory, sourceRef, sessionRef, alias);
|
|
3020
|
-
if (binding !== null) return bindingMatch(binding);
|
|
3021
3096
|
return null;
|
|
3022
3097
|
}
|
|
3023
3098
|
async hasPriorTurn(sourceRef, sessionRef) {
|
|
3024
|
-
if (!
|
|
3099
|
+
if (!SAFE_REF3.test(sourceRef) || !SAFE_REF3.test(sessionRef)) return false;
|
|
3025
3100
|
const read = await this.list();
|
|
3026
3101
|
if (read.entries.some((entry) => entry.identity.host_source_ref === sourceRef && entry.identity.host_session_ref === sessionRef)) return true;
|
|
3027
|
-
await this.prepare();
|
|
3028
|
-
for (const name of await readdir(this.bindingsDirectory)) {
|
|
3029
|
-
if (!name.endsWith(".json")) continue;
|
|
3030
|
-
const binding = await readBindingCandidate(this.bindingsDirectory, name);
|
|
3031
|
-
if (binding?.identity.host_source_ref === sourceRef && binding.identity.host_session_ref === sessionRef) return true;
|
|
3032
|
-
}
|
|
3033
3102
|
return false;
|
|
3034
3103
|
}
|
|
3035
3104
|
entryPath(submissionRef) {
|
|
3036
|
-
if (!
|
|
3037
|
-
return
|
|
3038
|
-
}
|
|
3039
|
-
bindingPath(identity) {
|
|
3040
|
-
return join3(this.bindingsDirectory, `${identityKey(identity)}.json`);
|
|
3105
|
+
if (!SAFE_REF3.test(submissionRef)) throw storageError("Codex submission identity is malformed.");
|
|
3106
|
+
return join2(this.entriesDirectory, `${cealRequestSha256(submissionRef)}.json`);
|
|
3041
3107
|
}
|
|
3042
3108
|
sessionPath(sourceRef, sessionRef) {
|
|
3043
|
-
return
|
|
3109
|
+
return join2(this.sessionsDirectory, `${identityKey({ host_source_ref: sourceRef, host_session_ref: sessionRef, host_turn_ref: "session" })}.json`);
|
|
3044
3110
|
}
|
|
3045
3111
|
activeContextPath(identity) {
|
|
3046
|
-
return
|
|
3112
|
+
return join2(this.activeContextsDirectory, `${invocationContextKey(identity)}.json`);
|
|
3047
3113
|
}
|
|
3048
3114
|
turnSelectionPath(sourceRef, sessionRef) {
|
|
3049
|
-
return
|
|
3115
|
+
return join2(this.turnSelectionsDirectory, `${invocationContextKey({ host_source_ref: sourceRef, host_session_ref: sessionRef })}.json`);
|
|
3050
3116
|
}
|
|
3051
3117
|
registrationPath(sourceRef, sessionRef) {
|
|
3052
|
-
return
|
|
3118
|
+
return join2(this.registrationsDirectory, `${sessionStateKey(sourceRef, sessionRef)}.json`);
|
|
3053
3119
|
}
|
|
3054
3120
|
cursorPath(sourceRef, sessionRef) {
|
|
3055
|
-
return
|
|
3121
|
+
return join2(this.cursorsDirectory, `${sessionStateKey(sourceRef, sessionRef)}.json`);
|
|
3056
3122
|
}
|
|
3057
3123
|
};
|
|
3058
3124
|
async function removeOwnerState(path, what) {
|
|
3059
3125
|
try {
|
|
3060
3126
|
await unlink2(path);
|
|
3061
3127
|
} catch (error) {
|
|
3062
|
-
if (!
|
|
3128
|
+
if (!isNotFound2(error)) throw storageError(`Unable to retire the Codex transcript ${what}.`);
|
|
3063
3129
|
}
|
|
3064
3130
|
}
|
|
3065
3131
|
function sessionStateKey(sourceRef, sessionRef) {
|
|
3066
|
-
if (!
|
|
3132
|
+
if (!SAFE_REF3.test(sourceRef) || !SAFE_REF3.test(sessionRef)) throw storageError("Codex transcript session identity is malformed.");
|
|
3067
3133
|
return cealRequestSha256({ host_source_ref: sourceRef, host_session_ref: sessionRef });
|
|
3068
3134
|
}
|
|
3069
3135
|
function envelopeMatch(envelope) {
|
|
3070
|
-
return { identity: envelope.identity, prompt_ref: envelope.prompt_ref,
|
|
3071
|
-
}
|
|
3072
|
-
function bindingMatch(binding) {
|
|
3073
|
-
return { identity: binding.identity, prompt_ref: binding.prompt_ref, request_ref: binding.request_ref, binding };
|
|
3136
|
+
return { identity: envelope.identity, prompt_ref: envelope.prompt_ref, envelope };
|
|
3074
3137
|
}
|
|
3075
3138
|
function findEnvelopeByAlias(entries, sourceRef, sessionRef, alias) {
|
|
3076
3139
|
for (const envelope of entries) {
|
|
@@ -3079,24 +3142,6 @@ function findEnvelopeByAlias(entries, sourceRef, sessionRef, alias) {
|
|
|
3079
3142
|
}
|
|
3080
3143
|
return null;
|
|
3081
3144
|
}
|
|
3082
|
-
async function findBindingByAlias(directory, sourceRef, sessionRef, alias) {
|
|
3083
|
-
for (const name of await readdir(directory)) {
|
|
3084
|
-
if (!name.endsWith(".json")) continue;
|
|
3085
|
-
const binding = await readBindingCandidate(directory, name);
|
|
3086
|
-
if (binding === null || binding.identity.host_source_ref !== sourceRef || binding.identity.host_session_ref !== sessionRef) continue;
|
|
3087
|
-
if (binding.codex_turn_ids.includes(alias)) return binding;
|
|
3088
|
-
}
|
|
3089
|
-
return null;
|
|
3090
|
-
}
|
|
3091
|
-
async function readBindingCandidate(directory, name) {
|
|
3092
|
-
try {
|
|
3093
|
-
const value = JSON.parse(await readPrivateFile(join3(directory, name)));
|
|
3094
|
-
validateBinding(value);
|
|
3095
|
-
return value;
|
|
3096
|
-
} catch {
|
|
3097
|
-
return null;
|
|
3098
|
-
}
|
|
3099
|
-
}
|
|
3100
3145
|
function identityKey(identity) {
|
|
3101
3146
|
validateIdentity3(identity);
|
|
3102
3147
|
return cealRequestSha256(identity);
|
|
@@ -3109,15 +3154,15 @@ async function writeAtomicJson(path, value) {
|
|
|
3109
3154
|
if (text === void 0) throw storageError("Codex owner state cannot be serialized.");
|
|
3110
3155
|
const temp = `${path}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
3111
3156
|
try {
|
|
3112
|
-
const handle = await
|
|
3157
|
+
const handle = await open3(temp, "wx");
|
|
3113
3158
|
try {
|
|
3114
3159
|
await handle.writeFile(text, "utf8");
|
|
3115
3160
|
await handle.sync();
|
|
3116
3161
|
} finally {
|
|
3117
3162
|
await handle.close();
|
|
3118
3163
|
}
|
|
3119
|
-
await
|
|
3120
|
-
await syncDirectory(
|
|
3164
|
+
await rename2(temp, path);
|
|
3165
|
+
await syncDirectory(dirname3(path));
|
|
3121
3166
|
} catch (error) {
|
|
3122
3167
|
try {
|
|
3123
3168
|
await unlink2(temp);
|
|
@@ -3129,7 +3174,7 @@ async function writeAtomicJson(path, value) {
|
|
|
3129
3174
|
}
|
|
3130
3175
|
async function syncDirectory(path) {
|
|
3131
3176
|
try {
|
|
3132
|
-
const handle = await
|
|
3177
|
+
const handle = await open3(path, "r");
|
|
3133
3178
|
try {
|
|
3134
3179
|
await handle.sync();
|
|
3135
3180
|
} finally {
|
|
@@ -3140,45 +3185,39 @@ async function syncDirectory(path) {
|
|
|
3140
3185
|
}
|
|
3141
3186
|
async function readOptionalJson(path, validate) {
|
|
3142
3187
|
try {
|
|
3143
|
-
const value = JSON.parse(await
|
|
3188
|
+
const value = JSON.parse(await readOwnerStateFile(path));
|
|
3144
3189
|
validate(value);
|
|
3145
3190
|
return value;
|
|
3146
3191
|
} catch (error) {
|
|
3147
|
-
if (
|
|
3192
|
+
if (isNotFound2(error)) return null;
|
|
3148
3193
|
if (error instanceof CodexHostAdapterError) throw error;
|
|
3149
3194
|
throw storageError("Codex owner state is malformed.");
|
|
3150
3195
|
}
|
|
3151
3196
|
}
|
|
3152
|
-
async function
|
|
3197
|
+
async function ensureOwnerDirectory(path) {
|
|
3153
3198
|
try {
|
|
3154
|
-
await
|
|
3199
|
+
await stat2(path);
|
|
3155
3200
|
} catch (error) {
|
|
3156
|
-
if (!
|
|
3157
|
-
await mkdir3(path, { recursive: true
|
|
3201
|
+
if (!isNotFound2(error)) throw storageError("Codex owner state directory is unavailable.");
|
|
3202
|
+
await mkdir3(path, { recursive: true });
|
|
3158
3203
|
}
|
|
3159
|
-
const stats = await
|
|
3160
|
-
|
|
3161
|
-
if (!stats.isDirectory() || stats.isSymbolicLink()) throw storageError(`Codex owner state path is not a directory (${refusalFact("mode", `0o${stats.mode.toString(8)}`, "a directory mode (0o40xxx)")}).`);
|
|
3162
|
-
if ((stats.mode & 63) !== 0) throw storageError(`Codex owner state directory is readable beyond its owner (${refusalFact("permission_bits", `0o${(stats.mode & 511).toString(8)}`, "no group or other bits")}).`);
|
|
3163
|
-
if (stats.uid !== uid) throw storageError(`Codex owner state directory is owned by another user (${refusalFact("uid", stats.uid, `expected ${uid}`)}).`);
|
|
3204
|
+
const stats = await stat2(path);
|
|
3205
|
+
if (!stats.isDirectory()) throw storageError("Codex owner state path is not a directory.");
|
|
3164
3206
|
}
|
|
3165
|
-
async function
|
|
3207
|
+
async function readOwnerStateFile(path) {
|
|
3166
3208
|
let handle;
|
|
3167
3209
|
try {
|
|
3168
|
-
handle = await
|
|
3210
|
+
handle = await open3(path);
|
|
3169
3211
|
const stats = await handle.stat();
|
|
3170
|
-
|
|
3171
|
-
if (!stats.isFile()) throw storageError(`Codex owner state path is not a regular file (${refusalFact("mode", `0o${stats.mode.toString(8)}`, "a regular-file mode (0o100xxx)")}).`);
|
|
3172
|
-
if ((stats.mode & 63) !== 0) throw storageError(`Codex owner state file is readable beyond its owner (${refusalFact("permission_bits", `0o${(stats.mode & 511).toString(8)}`, "no group or other bits")}).`);
|
|
3173
|
-
if (stats.uid !== uid) throw storageError(`Codex owner state file is owned by another user (${refusalFact("uid", stats.uid, `expected ${uid}`)}).`);
|
|
3212
|
+
if (!stats.isFile()) throw storageError("Codex owner state path is not a regular file.");
|
|
3174
3213
|
return await handle.readFile("utf8");
|
|
3175
3214
|
} finally {
|
|
3176
3215
|
await handle?.close();
|
|
3177
3216
|
}
|
|
3178
3217
|
}
|
|
3179
3218
|
function validateEnvelope(value) {
|
|
3180
|
-
if (!
|
|
3181
|
-
if (!sameKeys(value, ["schema_version", "identity", "host_input_ref", "
|
|
3219
|
+
if (!isRecord5(value)) throw storageError("Codex outbox entry is malformed.");
|
|
3220
|
+
if (!sameKeys(value, ["schema_version", "identity", "host_input_ref", "prompt_ref", "observations", "origin_input", "submission_ref", "created_at", "expires_at"], ["codex_turn_id"])) throw storageError(`Codex outbox entry key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the outbox entry key set")}).`);
|
|
3182
3221
|
if (value.schema_version !== CODEX_OUTBOX_SCHEMA_VERSION) throw storageError(`Codex outbox entry is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CODEX_OUTBOX_SCHEMA_VERSION}`)}).`);
|
|
3183
3222
|
validateIdentity3(value.identity);
|
|
3184
3223
|
validateEnvelopeReferences(value);
|
|
@@ -3186,32 +3225,30 @@ function validateEnvelope(value) {
|
|
|
3186
3225
|
validateEnvelopeTimestamps(value);
|
|
3187
3226
|
}
|
|
3188
3227
|
function validateEnvelopeReferences(value) {
|
|
3189
|
-
if (!
|
|
3190
|
-
if (!
|
|
3191
|
-
if (!
|
|
3192
|
-
if (!
|
|
3193
|
-
if (value.request_ref !== void 0 && !SAFE_REF4.test(String(value.request_ref))) throw storageError("Codex outbox Request reference is malformed.");
|
|
3194
|
-
if (value.codex_turn_id !== void 0 && !SAFE_REF4.test(String(value.codex_turn_id))) throw storageError("Codex outbox turn identity is malformed.");
|
|
3228
|
+
if (!SAFE_REF3.test(String(value.host_input_ref))) throw storageError("Codex outbox input reference is malformed.");
|
|
3229
|
+
if (!SAFE_REF3.test(String(value.prompt_ref))) throw storageError("Codex outbox references are malformed.");
|
|
3230
|
+
if (!SAFE_REF3.test(String(value.submission_ref))) throw storageError("Codex outbox references are malformed.");
|
|
3231
|
+
if (value.codex_turn_id !== void 0 && !SAFE_REF3.test(String(value.codex_turn_id))) throw storageError("Codex outbox turn identity is malformed.");
|
|
3195
3232
|
if (typeof value.origin_input !== "boolean") throw storageError("Codex outbox origin disposition is malformed.");
|
|
3196
3233
|
}
|
|
3197
3234
|
function validateRegistration(value) {
|
|
3198
|
-
if (!
|
|
3235
|
+
if (!isRecord5(value)) throw storageError("Codex transcript registration is malformed.");
|
|
3199
3236
|
if (!sameKeys(value, ["schema_version", "host_source_ref", "host_session_ref", "codex_session_id", "transcript_path", "registered_at", "expires_at"], ["cwd"])) throw storageError(`Codex transcript registration key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the registration key set")}).`);
|
|
3200
3237
|
if (value.schema_version !== CODEX_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION) throw storageError(`Codex transcript registration is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CODEX_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION}`)}).`);
|
|
3201
3238
|
validateRegistrationFields(value);
|
|
3202
3239
|
}
|
|
3203
3240
|
function validateRegistrationFields(value) {
|
|
3204
|
-
if (![value.host_source_ref, value.host_session_ref, value.codex_session_id].every((candidate) => typeof candidate === "string" &&
|
|
3241
|
+
if (![value.host_source_ref, value.host_session_ref, value.codex_session_id].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) throw storageError(`Codex transcript registration references are malformed (${refusalFacts([{ name: "host_source_ref", value: value.host_source_ref }, { name: "host_session_ref", value: value.host_session_ref }, { name: "codex_session_id", value: value.codex_session_id }])}).`);
|
|
3205
3242
|
if (!validRegistrationCwd(value.cwd)) throw storageError(`Codex transcript registration working directory is malformed (${refusalFact("cwd_characters", typeof value.cwd === "string" ? value.cwd.length : 0, `an absolute path of at most ${CODEX_TRANSCRIPT_PATH_MAX_CHARACTERS} characters`)}).`);
|
|
3206
3243
|
if (typeof value.transcript_path !== "string") throw storageError(`Codex transcript registration path is malformed (${refusalFact("kind", typeof value.transcript_path, "string")}).`);
|
|
3207
3244
|
if (value.transcript_path.length === 0 || value.transcript_path.length > CODEX_TRANSCRIPT_PATH_MAX_CHARACTERS) throw storageError(`Codex transcript registration path is malformed (${refusalFact("characters", value.transcript_path.length, `from 1 through ${CODEX_TRANSCRIPT_PATH_MAX_CHARACTERS}`)}).`);
|
|
3208
3245
|
if (!isTimestamp(value.registered_at) || !isTimestamp(value.expires_at)) throw storageError(`Codex transcript registration timestamps are malformed (${refusalFacts([{ name: "registered_at", value: value.registered_at }, { name: "expires_at", value: value.expires_at }])}).`);
|
|
3209
3246
|
}
|
|
3210
3247
|
function validRegistrationCwd(value) {
|
|
3211
|
-
return value === void 0 || typeof value === "string" &&
|
|
3248
|
+
return value === void 0 || typeof value === "string" && isAbsolute2(value) && value.length <= CODEX_TRANSCRIPT_PATH_MAX_CHARACTERS && !value.includes("\0");
|
|
3212
3249
|
}
|
|
3213
3250
|
function validateCursor(value) {
|
|
3214
|
-
if (!
|
|
3251
|
+
if (!isRecord5(value)) throw storageError("Codex transcript cursor is malformed.");
|
|
3215
3252
|
if (!sameKeys(value, ["schema_version", "host_source_ref", "host_session_ref", "transcript_path", "device", "inode", "offset", "last_ordinal", "anchor_start", "anchor_sha256", "header_validated", "status", "updated_at"], ["active_task", "reason"])) throw storageError(`Codex transcript cursor key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the cursor key set")}).`);
|
|
3216
3253
|
if (value.schema_version !== CODEX_TRANSCRIPT_CURSOR_SCHEMA_VERSION) throw storageError(`Codex transcript cursor is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CODEX_TRANSCRIPT_CURSOR_SCHEMA_VERSION}`)}).`);
|
|
3217
3254
|
if (!validCursorSource(value) || !validCursorPosition(value) || !validCursorStatus(value)) throw storageError("Codex transcript cursor fields are malformed.");
|
|
@@ -3219,7 +3256,7 @@ function validateCursor(value) {
|
|
|
3219
3256
|
if (value.reason !== void 0 && !isHostTranscriptCursorReason(value.reason)) throw storageError("Codex transcript cursor reason is malformed.");
|
|
3220
3257
|
}
|
|
3221
3258
|
function validCursorSource(value) {
|
|
3222
|
-
return [value.host_source_ref, value.host_session_ref].every((candidate) => typeof candidate === "string" &&
|
|
3259
|
+
return [value.host_source_ref, value.host_session_ref].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate)) && typeof value.transcript_path === "string" && value.transcript_path.length > 0 && typeof value.device === "string" && typeof value.inode === "string";
|
|
3223
3260
|
}
|
|
3224
3261
|
function validCursorPosition(value) {
|
|
3225
3262
|
return nonNegativeInteger(value.offset) && nonNegativeInteger(value.last_ordinal) && nonNegativeInteger(value.anchor_start) && typeof value.anchor_sha256 === "string" && /^[a-f0-9]{64}$/u.test(value.anchor_sha256) && typeof value.header_validated === "boolean";
|
|
@@ -3228,23 +3265,23 @@ function validCursorStatus(value) {
|
|
|
3228
3265
|
return (value.status === "active" || value.status === "quarantined") && isTimestamp(value.updated_at);
|
|
3229
3266
|
}
|
|
3230
3267
|
function validActiveTask(value) {
|
|
3231
|
-
if (!
|
|
3268
|
+
if (!isRecord5(value) || !sameKeys(value, ["codex_turn_id", "host_turn_ref", "started_at"], ["origin_host_input_ref", "prompt_ref", "model", "token_usage", "last_total_usage", "token_usage_gap_reason"])) return false;
|
|
3232
3269
|
return validActiveTaskIdentity(value) && validActiveTaskReferences(value) && validActiveTaskUsage(value);
|
|
3233
3270
|
}
|
|
3234
3271
|
function validActiveTaskIdentity(value) {
|
|
3235
|
-
return [value.codex_turn_id, value.host_turn_ref].every((candidate) => typeof candidate === "string" &&
|
|
3272
|
+
return [value.codex_turn_id, value.host_turn_ref].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate)) && isTimestamp(value.started_at);
|
|
3236
3273
|
}
|
|
3237
3274
|
function validActiveTaskReferences(value) {
|
|
3238
3275
|
return optionalSafeRef2(value.origin_host_input_ref) && optionalSafeRef2(value.prompt_ref) && (value.model === void 0 || typeof value.model === "string" && value.model.length > 0 && value.model.length <= 256);
|
|
3239
3276
|
}
|
|
3240
3277
|
function optionalSafeRef2(value) {
|
|
3241
|
-
return value === void 0 || typeof value === "string" &&
|
|
3278
|
+
return value === void 0 || typeof value === "string" && SAFE_REF3.test(value);
|
|
3242
3279
|
}
|
|
3243
3280
|
function validActiveTaskUsage(value) {
|
|
3244
3281
|
return (value.token_usage === void 0 || validTokenUsage(value.token_usage)) && (value.last_total_usage === void 0 || validTokenUsage(value.last_total_usage)) && (value.token_usage_gap_reason === void 0 || value.token_usage_gap_reason === "token_count_shape_not_attributable");
|
|
3245
3282
|
}
|
|
3246
3283
|
function validTokenUsage(value) {
|
|
3247
|
-
if (!
|
|
3284
|
+
if (!isRecord5(value) || !sameKeys(value, ["input_tokens", "cached_input_tokens", "cache_write_input_tokens", "output_tokens", "reasoning_output_tokens", "total_tokens"])) return false;
|
|
3248
3285
|
if (![value.input_tokens, value.cached_input_tokens, value.cache_write_input_tokens, value.output_tokens, value.reasoning_output_tokens, value.total_tokens].every(nonNegativeInteger)) return false;
|
|
3249
3286
|
return value.total_tokens === Number(value.input_tokens) + Number(value.output_tokens) && Number(value.cached_input_tokens) + Number(value.cache_write_input_tokens) <= Number(value.input_tokens) && Number(value.reasoning_output_tokens) <= Number(value.output_tokens);
|
|
3250
3287
|
}
|
|
@@ -3254,7 +3291,7 @@ function nonNegativeInteger(value) {
|
|
|
3254
3291
|
function validateEnvelopeObservations(value) {
|
|
3255
3292
|
if (!Array.isArray(value.observations)) throw storageError("Codex outbox observations are malformed.");
|
|
3256
3293
|
try {
|
|
3257
|
-
for (const
|
|
3294
|
+
for (const observation of value.observations) validateCealHostObservationDraft(observation);
|
|
3258
3295
|
} catch {
|
|
3259
3296
|
throw storageError("Codex outbox observations are malformed.");
|
|
3260
3297
|
}
|
|
@@ -3263,34 +3300,23 @@ function validateEnvelopeTimestamps(value) {
|
|
|
3263
3300
|
if (!isTimestamp(value.created_at)) throw storageError("Codex outbox timestamps are malformed.");
|
|
3264
3301
|
if (!isTimestamp(value.expires_at)) throw storageError("Codex outbox timestamps are malformed.");
|
|
3265
3302
|
}
|
|
3266
|
-
function validateBinding(value) {
|
|
3267
|
-
if (!isRecord4(value)) throw storageError("Codex turn binding is malformed.");
|
|
3268
|
-
if (!sameKeys(value, ["schema_version", "identity", "prompt_ref", "request_ref", "codex_turn_ids", "bound_at", "expires_at"])) throw storageError(`Codex turn binding key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the turn binding key set")}).`);
|
|
3269
|
-
if (value.schema_version !== CODEX_TURN_BINDING_SCHEMA_VERSION) throw storageError(`Codex turn binding is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CODEX_TURN_BINDING_SCHEMA_VERSION}`)}).`);
|
|
3270
|
-
validateIdentity3(value.identity);
|
|
3271
|
-
if (!SAFE_REF4.test(String(value.prompt_ref)) || !SAFE_REF4.test(String(value.request_ref))) throw storageError("Codex turn binding references are malformed.");
|
|
3272
|
-
if (!isStringList(value.codex_turn_ids) || !isTimestamp(value.bound_at) || !isTimestamp(value.expires_at)) throw storageError("Codex turn binding fields are malformed.");
|
|
3273
|
-
}
|
|
3274
3303
|
function validateSession(value) {
|
|
3275
|
-
if (!
|
|
3304
|
+
if (!isRecord5(value)) throw storageError("Codex session snapshot is malformed.");
|
|
3276
3305
|
if (!sameKeys(value, ["schema_version", "host_session_ref", "host_source_ref", "observed_at", "value", "expires_at"])) throw storageError(`Codex session snapshot key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the session snapshot key set")}).`);
|
|
3277
3306
|
if (value.schema_version !== CODEX_SESSION_SCHEMA_VERSION) throw storageError(`Codex session snapshot is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CODEX_SESSION_SCHEMA_VERSION}`)}).`);
|
|
3278
|
-
if (!
|
|
3307
|
+
if (!SAFE_REF3.test(String(value.host_session_ref)) || !SAFE_REF3.test(String(value.host_source_ref)) || !isTimestamp(value.observed_at) || !isTimestamp(value.expires_at) || !isJsonValue(value.value)) throw storageError("Codex session snapshot fields are malformed.");
|
|
3279
3308
|
}
|
|
3280
3309
|
function validateTurnSelection(value) {
|
|
3281
3310
|
if (!isHostInvocationTurnSelection(value)) throw storageError("Codex Host turn selection is malformed.");
|
|
3282
3311
|
}
|
|
3283
3312
|
function validateIdentity3(value) {
|
|
3284
|
-
if (!
|
|
3313
|
+
if (!isRecord5(value) || !hasHostInvocationIdentityKeys(value) || ![value.host_session_ref, value.host_turn_ref, value.host_source_ref].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) throw storageError("Codex Host identity is malformed.");
|
|
3285
3314
|
}
|
|
3286
3315
|
function isJsonValue(value) {
|
|
3287
3316
|
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
3288
3317
|
if (typeof value === "number") return Number.isFinite(value);
|
|
3289
3318
|
if (Array.isArray(value)) return value.every(isJsonValue);
|
|
3290
|
-
return
|
|
3291
|
-
}
|
|
3292
|
-
function isStringList(value) {
|
|
3293
|
-
return Array.isArray(value) && value.every((candidate) => typeof candidate === "string" && SAFE_REF4.test(candidate));
|
|
3319
|
+
return isRecord5(value) && Object.entries(value).every(([key, child]) => key.length > 0 && key.length <= 128 && isJsonValue(child));
|
|
3294
3320
|
}
|
|
3295
3321
|
function isTimestamp(value) {
|
|
3296
3322
|
return typeof value === "string" && value.length <= 64 && !Number.isNaN(Date.parse(value));
|
|
@@ -3300,149 +3326,51 @@ function sameKeys(value, required, optional = []) {
|
|
|
3300
3326
|
const keys = Object.keys(value);
|
|
3301
3327
|
return keys.length >= required.length && keys.length <= allowed.size && required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => allowed.has(key));
|
|
3302
3328
|
}
|
|
3303
|
-
function
|
|
3329
|
+
function isRecord5(value) {
|
|
3304
3330
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3305
3331
|
}
|
|
3306
|
-
function
|
|
3307
|
-
return
|
|
3332
|
+
function isNotFound2(error) {
|
|
3333
|
+
return isRecord5(error) && error.code === "ENOENT";
|
|
3308
3334
|
}
|
|
3309
3335
|
function storageError(message) {
|
|
3310
3336
|
return new CodexHostAdapterError("storage", message);
|
|
3311
3337
|
}
|
|
3312
3338
|
|
|
3313
3339
|
// host/codex/src/codex-producer.ts
|
|
3314
|
-
var SAFE_REF5 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
3315
3340
|
var CodexProducer = class {
|
|
3316
|
-
producer_id = "codex";
|
|
3317
|
-
producer_label = "Codex";
|
|
3318
|
-
outbox_directory;
|
|
3319
3341
|
store;
|
|
3320
3342
|
core;
|
|
3321
3343
|
constructor(options) {
|
|
3322
3344
|
this.store = new CodexOutboxStore(options.outbox_dir);
|
|
3323
|
-
this.outbox_directory = this.store.directory;
|
|
3324
3345
|
this.core = new HostProducerDrainCore(this, options);
|
|
3325
3346
|
}
|
|
3326
3347
|
drainOutbox() {
|
|
3327
3348
|
return this.core.drainOutbox();
|
|
3328
3349
|
}
|
|
3329
|
-
fail(code, message) {
|
|
3330
|
-
return new CodexHostAdapterError(code, message);
|
|
3331
|
-
}
|
|
3332
3350
|
listEnvelopes() {
|
|
3333
3351
|
return this.store.list();
|
|
3334
3352
|
}
|
|
3335
3353
|
removeEnvelope(envelope) {
|
|
3336
3354
|
return this.store.remove(envelope);
|
|
3337
3355
|
}
|
|
3338
|
-
submissionRefOf(envelope) {
|
|
3339
|
-
return envelope.submission_ref;
|
|
3340
|
-
}
|
|
3341
3356
|
expiresAtOf(envelope) {
|
|
3342
3357
|
return envelope.expires_at;
|
|
3343
3358
|
}
|
|
3344
|
-
createdAtOf(envelope) {
|
|
3345
|
-
return envelope.created_at;
|
|
3346
|
-
}
|
|
3347
|
-
async mapEnvelope(envelope) {
|
|
3348
|
-
const prompt = observation(envelope, "prompt");
|
|
3349
|
-
if (prompt !== void 0) return this.mapInput(envelope, prompt);
|
|
3350
|
-
return [mapTerminal(envelope)];
|
|
3351
|
-
}
|
|
3352
|
-
async mapInput(envelope, prompt) {
|
|
3353
|
-
const drafts = [];
|
|
3354
|
-
const session = observation(envelope, "session");
|
|
3355
|
-
if (session !== void 0) {
|
|
3356
|
-
const registrations = await this.store.listTranscriptRegistrations();
|
|
3357
|
-
const registration = registrations.find((item) => item.host_source_ref === envelope.identity.host_source_ref && item.host_session_ref === envelope.identity.host_session_ref);
|
|
3358
|
-
const directoryIdentity = registration?.cwd ?? envelope.identity.host_session_ref;
|
|
3359
|
-
drafts.push({ frame_kind: "session_started", payload: compact({
|
|
3360
|
-
local_session_ref: envelope.identity.host_session_ref,
|
|
3361
|
-
working_directory_ref: `workspace:${createHash4("sha256").update(directoryIdentity, "utf8").digest("hex")}`,
|
|
3362
|
-
model: recordText(session.value, "model"),
|
|
3363
|
-
host_version: nestedText(session.value, "host_version"),
|
|
3364
|
-
observations: [session]
|
|
3365
|
-
}) });
|
|
3366
|
-
}
|
|
3367
|
-
const turn = observation(envelope, "turn");
|
|
3368
|
-
if (turn !== void 0) drafts.push({ frame_kind: "turn_started", payload: compact({
|
|
3369
|
-
local_session_ref: envelope.identity.host_session_ref,
|
|
3370
|
-
local_turn_ref: localTurnRef(envelope),
|
|
3371
|
-
observations: [turn]
|
|
3372
|
-
}) });
|
|
3373
|
-
if (!isCealHostPromptObservationValue(prompt.value)) throw new CodexHostAdapterError("storage", "Codex prompt observation is malformed.");
|
|
3374
|
-
drafts.push({ frame_kind: "user_input", payload: compact({
|
|
3375
|
-
local_session_ref: envelope.identity.host_session_ref,
|
|
3376
|
-
local_turn_ref: localTurnRef(envelope),
|
|
3377
|
-
local_input_ref: envelope.host_input_ref,
|
|
3378
|
-
input_kind: envelope.input_kind,
|
|
3379
|
-
origin_input: envelope.origin_input,
|
|
3380
|
-
observations: [prompt],
|
|
3381
|
-
local_turn_aliases: envelope.codex_turn_id === void 0 ? void 0 : [envelope.codex_turn_id],
|
|
3382
|
-
context_expires_at: envelope.expires_at
|
|
3383
|
-
}) });
|
|
3384
|
-
return drafts;
|
|
3385
|
-
}
|
|
3386
3359
|
};
|
|
3387
|
-
function mapTerminal(envelope) {
|
|
3388
|
-
const timing = observation(envelope, "timing");
|
|
3389
|
-
const timingValue = asRecord(timing?.value);
|
|
3390
|
-
const aborted = timingValue?.terminal === "aborted";
|
|
3391
|
-
const usage = envelope.observations.filter((item) => ["token", "cost", "model"].includes(item.observation_kind));
|
|
3392
|
-
return { frame_kind: "turn_terminal", payload: compact({
|
|
3393
|
-
local_session_ref: envelope.identity.host_session_ref,
|
|
3394
|
-
local_turn_ref: localTurnRef(envelope),
|
|
3395
|
-
terminal_state: aborted ? "aborted" : "completed",
|
|
3396
|
-
abort_reason: aborted && typeof timingValue?.reason === "string" ? safeReasonRef(timingValue.reason) : void 0,
|
|
3397
|
-
duration_ms: nonNegativeNumber(timingValue?.duration_ms),
|
|
3398
|
-
time_to_first_token_ms: nonNegativeNumber(timingValue?.time_to_first_token_ms),
|
|
3399
|
-
usage: usage.length === 0 ? void 0 : Object.fromEntries(usage.map((item) => [item.observation_kind, item.value])),
|
|
3400
|
-
observations: envelope.observations
|
|
3401
|
-
}) };
|
|
3402
|
-
}
|
|
3403
|
-
function observation(envelope, kind) {
|
|
3404
|
-
return envelope.observations.find((item) => item.observation_kind === kind);
|
|
3405
|
-
}
|
|
3406
|
-
function localTurnRef(envelope) {
|
|
3407
|
-
return envelope.identity.host_turn_ref;
|
|
3408
|
-
}
|
|
3409
|
-
function compact(value) {
|
|
3410
|
-
return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== void 0));
|
|
3411
|
-
}
|
|
3412
|
-
function asRecord(value) {
|
|
3413
|
-
return isRecord5(value) ? value : void 0;
|
|
3414
|
-
}
|
|
3415
|
-
function recordText(value, key) {
|
|
3416
|
-
const record3 = asRecord(value);
|
|
3417
|
-
return typeof record3?.[key] === "string" ? record3[key] : void 0;
|
|
3418
|
-
}
|
|
3419
|
-
function nestedText(value, key) {
|
|
3420
|
-
const nested = asRecord(asRecord(value)?.[key]);
|
|
3421
|
-
return typeof nested?.value === "string" ? nested.value : void 0;
|
|
3422
|
-
}
|
|
3423
|
-
function nonNegativeNumber(value) {
|
|
3424
|
-
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
3425
|
-
}
|
|
3426
|
-
function safeReasonRef(value) {
|
|
3427
|
-
return SAFE_REF5.test(value) ? value : `reason:${createHash4("sha256").update(value, "utf8").digest("hex")}`;
|
|
3428
|
-
}
|
|
3429
|
-
function isRecord5(value) {
|
|
3430
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3431
|
-
}
|
|
3432
3360
|
|
|
3433
3361
|
// host/codex/src/codex-decoder.ts
|
|
3434
3362
|
import { createHash as createHash5 } from "node:crypto";
|
|
3435
|
-
var
|
|
3363
|
+
var SAFE_REF4 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
3436
3364
|
var MAX_PAYLOAD_BYTES = 512 * 1024;
|
|
3437
|
-
function observationRef(event, identity, value) {
|
|
3438
|
-
return cealHostObservationRef("codex", shortDigest({ event, identity, value }));
|
|
3365
|
+
function observationRef(hostSourceRef, event, identity, value) {
|
|
3366
|
+
return cealHostObservationRef("codex", shortDigest({ host_source_ref: hostSourceRef, event, identity, value }));
|
|
3439
3367
|
}
|
|
3440
3368
|
function hostTurnReference(sourceRef, sessionRef, nativeTurnId) {
|
|
3441
|
-
if (![sourceRef, sessionRef, nativeTurnId].every((value) =>
|
|
3369
|
+
if (![sourceRef, sessionRef, nativeTurnId].every((value) => SAFE_REF4.test(value))) throw new CodexHostAdapterError("invalid_input", "Codex turn identity is invalid.");
|
|
3442
3370
|
return `codex-turn:${shortDigest({ source_ref: sourceRef, session_ref: sessionRef, native_turn_id: nativeTurnId })}`;
|
|
3443
3371
|
}
|
|
3444
3372
|
function hostInputReference(sourceRef, sessionRef, nativeTurnId, messageId, ordinal, prompt) {
|
|
3445
|
-
if (![sourceRef, sessionRef, nativeTurnId, messageId].every((value) =>
|
|
3373
|
+
if (![sourceRef, sessionRef, nativeTurnId, messageId].every((value) => SAFE_REF4.test(value)) || !Number.isSafeInteger(ordinal) || ordinal < 0) {
|
|
3446
3374
|
throw new CodexHostAdapterError("invalid_input", "Codex input identity is invalid.");
|
|
3447
3375
|
}
|
|
3448
3376
|
return `codex-input:${shortDigest({ source_ref: sourceRef, session_ref: sessionRef, native_turn_id: nativeTurnId, message_id: messageId, ordinal, prompt_sha256: sha256(prompt) })}`;
|
|
@@ -3464,7 +3392,7 @@ function shortDigest(value) {
|
|
|
3464
3392
|
import { createHash as createHash6 } from "node:crypto";
|
|
3465
3393
|
|
|
3466
3394
|
// host/codex/src/codex-transcript-decoder.ts
|
|
3467
|
-
var
|
|
3395
|
+
var SAFE_REF5 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;
|
|
3468
3396
|
var MAX_PROMPT_BYTES = 64 * 1024;
|
|
3469
3397
|
function decodeCodexTranscriptLine(line) {
|
|
3470
3398
|
let value;
|
|
@@ -3475,38 +3403,38 @@ function decodeCodexTranscriptLine(line) {
|
|
|
3475
3403
|
}
|
|
3476
3404
|
if (!isRecord6(value)) reject("Codex transcript record envelope is malformed.");
|
|
3477
3405
|
const ordinal = safeOrdinal(value.ordinal);
|
|
3478
|
-
const
|
|
3406
|
+
const timestamp3 = safeTimestamp(value.timestamp);
|
|
3479
3407
|
if (!isRecord6(value.payload) || typeof value.type !== "string") reject("Codex transcript record payload is malformed.");
|
|
3480
|
-
if (value.type === "session_meta") return decodeSessionMeta(value.payload, ordinal,
|
|
3481
|
-
if (value.type === "turn_context") return decodeTurnContext(value.payload, ordinal,
|
|
3482
|
-
if (value.type === "event_msg") return decodeEventMessage(value.payload, ordinal,
|
|
3483
|
-
if (value.type === "response_item") return decodeResponseItem(value.payload, ordinal,
|
|
3484
|
-
return { kind: "ignored", ordinal, timestamp:
|
|
3408
|
+
if (value.type === "session_meta") return decodeSessionMeta(value.payload, ordinal, timestamp3);
|
|
3409
|
+
if (value.type === "turn_context") return decodeTurnContext(value.payload, ordinal, timestamp3);
|
|
3410
|
+
if (value.type === "event_msg") return decodeEventMessage(value.payload, ordinal, timestamp3);
|
|
3411
|
+
if (value.type === "response_item") return decodeResponseItem(value.payload, ordinal, timestamp3);
|
|
3412
|
+
return { kind: "ignored", ordinal, timestamp: timestamp3 };
|
|
3485
3413
|
}
|
|
3486
|
-
function decodeTurnContext(payload, ordinal,
|
|
3414
|
+
function decodeTurnContext(payload, ordinal, timestamp3) {
|
|
3487
3415
|
return {
|
|
3488
3416
|
kind: "turn_context",
|
|
3489
3417
|
ordinal,
|
|
3490
|
-
timestamp:
|
|
3491
|
-
turn_id:
|
|
3418
|
+
timestamp: timestamp3,
|
|
3419
|
+
turn_id: safeRef3(payload.turn_id, "Codex turn context identity is invalid."),
|
|
3492
3420
|
model: boundedNonEmptyText(payload.model, 256, "Codex turn context model is invalid.")
|
|
3493
3421
|
};
|
|
3494
3422
|
}
|
|
3495
|
-
function decodeSessionMeta(payload, ordinal,
|
|
3496
|
-
const sessionId =
|
|
3423
|
+
function decodeSessionMeta(payload, ordinal, timestamp3) {
|
|
3424
|
+
const sessionId = safeRef3(payload.id, "Codex transcript session identity is invalid.");
|
|
3497
3425
|
const cliVersion = boundedNonEmptyText(payload.cli_version, 64, "Codex transcript version is invalid.");
|
|
3498
|
-
return { kind: "session_meta", ordinal, timestamp:
|
|
3426
|
+
return { kind: "session_meta", ordinal, timestamp: timestamp3, session_id: sessionId, cli_version: cliVersion };
|
|
3499
3427
|
}
|
|
3500
|
-
function decodeEventMessage(payload, ordinal,
|
|
3428
|
+
function decodeEventMessage(payload, ordinal, timestamp3) {
|
|
3501
3429
|
if (payload.type === "task_started") {
|
|
3502
|
-
return { kind: "task_started", ordinal, timestamp:
|
|
3430
|
+
return { kind: "task_started", ordinal, timestamp: timestamp3, turn_id: safeRef3(payload.turn_id, "Codex task identity is invalid."), started_at: safeNumber(payload.started_at) };
|
|
3503
3431
|
}
|
|
3504
3432
|
if (payload.type === "task_complete") {
|
|
3505
3433
|
return {
|
|
3506
3434
|
kind: "task_complete",
|
|
3507
3435
|
ordinal,
|
|
3508
|
-
timestamp:
|
|
3509
|
-
turn_id:
|
|
3436
|
+
timestamp: timestamp3,
|
|
3437
|
+
turn_id: safeRef3(payload.turn_id, "Codex task identity is invalid."),
|
|
3510
3438
|
started_at: safeNumber(payload.started_at),
|
|
3511
3439
|
completed_at: safeNumber(payload.completed_at),
|
|
3512
3440
|
duration_ms: safeNumber(payload.duration_ms),
|
|
@@ -3517,20 +3445,20 @@ function decodeEventMessage(payload, ordinal, timestamp4) {
|
|
|
3517
3445
|
return {
|
|
3518
3446
|
kind: "turn_aborted",
|
|
3519
3447
|
ordinal,
|
|
3520
|
-
timestamp:
|
|
3521
|
-
turn_id:
|
|
3448
|
+
timestamp: timestamp3,
|
|
3449
|
+
turn_id: safeRef3(payload.turn_id, "Codex task identity is invalid."),
|
|
3522
3450
|
started_at: safeNumber(payload.started_at),
|
|
3523
3451
|
completed_at: safeNumber(payload.completed_at),
|
|
3524
3452
|
duration_ms: safeNumber(payload.duration_ms),
|
|
3525
3453
|
reason: boundedText(payload.reason, 256, "Codex task abort reason is invalid.")
|
|
3526
3454
|
};
|
|
3527
3455
|
}
|
|
3528
|
-
if (payload.type === "token_count") return decodeTokenCount(payload, ordinal,
|
|
3529
|
-
return { kind: "ignored", ordinal, timestamp:
|
|
3456
|
+
if (payload.type === "token_count") return decodeTokenCount(payload, ordinal, timestamp3);
|
|
3457
|
+
return { kind: "ignored", ordinal, timestamp: timestamp3 };
|
|
3530
3458
|
}
|
|
3531
|
-
function decodeTokenCount(payload, ordinal,
|
|
3532
|
-
if (payload.info === null) return { kind: "token_count", ordinal, timestamp:
|
|
3533
|
-
if (!isRecord6(payload.info)) return { kind: "token_count", ordinal, timestamp:
|
|
3459
|
+
function decodeTokenCount(payload, ordinal, timestamp3) {
|
|
3460
|
+
if (payload.info === null) return { kind: "token_count", ordinal, timestamp: timestamp3 };
|
|
3461
|
+
if (!isRecord6(payload.info)) return { kind: "token_count", ordinal, timestamp: timestamp3, usage_gap_reason: "token_count_shape_not_attributable" };
|
|
3534
3462
|
const lastUsage = optionalTokenUsage(payload.info.last_token_usage);
|
|
3535
3463
|
const totalUsage = optionalTokenUsage(payload.info.total_token_usage);
|
|
3536
3464
|
const gap = lastUsage.status === "invalid" || totalUsage.status === "invalid";
|
|
@@ -3538,7 +3466,7 @@ function decodeTokenCount(payload, ordinal, timestamp4) {
|
|
|
3538
3466
|
return {
|
|
3539
3467
|
kind: "token_count",
|
|
3540
3468
|
ordinal,
|
|
3541
|
-
timestamp:
|
|
3469
|
+
timestamp: timestamp3,
|
|
3542
3470
|
...lastUsage.status === "valid" ? { last_usage: lastUsage.value } : {},
|
|
3543
3471
|
...totalUsage.status === "valid" ? { total_usage: totalUsage.value } : {},
|
|
3544
3472
|
...usageGapReason === void 0 ? {} : { usage_gap_reason: usageGapReason }
|
|
@@ -3566,26 +3494,24 @@ function decodeTokenUsage(value) {
|
|
|
3566
3494
|
if (usage.total_tokens !== usage.input_tokens + usage.output_tokens || usage.cached_input_tokens + usage.cache_write_input_tokens > usage.input_tokens || usage.reasoning_output_tokens > usage.output_tokens) reject("Codex token usage buckets are inconsistent.");
|
|
3567
3495
|
return usage;
|
|
3568
3496
|
}
|
|
3569
|
-
function decodeResponseItem(payload, ordinal,
|
|
3570
|
-
if (payload.type !== "message" || payload.role !== "user") return { kind: "ignored", ordinal, timestamp:
|
|
3497
|
+
function decodeResponseItem(payload, ordinal, timestamp3) {
|
|
3498
|
+
if (payload.type !== "message" || payload.role !== "user") return { kind: "ignored", ordinal, timestamp: timestamp3 };
|
|
3571
3499
|
const metadata = userMessageMetadata(payload.internal_chat_message_metadata_passthrough);
|
|
3572
|
-
const
|
|
3573
|
-
if (
|
|
3500
|
+
const contentItemKind = userTextContentKind(metadata.content_item_kinds);
|
|
3501
|
+
if (contentItemKind === void 0) return { kind: "ignored", ordinal, timestamp: timestamp3 };
|
|
3574
3502
|
const prompt = userTextContent(payload.content);
|
|
3575
3503
|
return {
|
|
3576
3504
|
kind: "user_text",
|
|
3577
3505
|
ordinal,
|
|
3578
|
-
timestamp:
|
|
3506
|
+
timestamp: timestamp3,
|
|
3579
3507
|
prompt,
|
|
3580
|
-
|
|
3581
|
-
message_id:
|
|
3582
|
-
turn_id:
|
|
3508
|
+
content_item_kind: contentItemKind,
|
|
3509
|
+
message_id: safeRef3(payload.id, "Codex user message identity is invalid."),
|
|
3510
|
+
turn_id: safeRef3(metadata.turn_id, "Codex user message task identity is invalid.")
|
|
3583
3511
|
};
|
|
3584
3512
|
}
|
|
3585
|
-
function
|
|
3586
|
-
|
|
3587
|
-
if (kinds[0] === "user.text") return "human";
|
|
3588
|
-
return kinds[0] === "goal.internal_context" ? "host_automatic" : void 0;
|
|
3513
|
+
function userTextContentKind(kinds) {
|
|
3514
|
+
return kinds.length === 1 && (kinds[0] === "user.text" || kinds[0] === "goal.internal_context") ? kinds[0] : void 0;
|
|
3589
3515
|
}
|
|
3590
3516
|
function userMessageMetadata(value) {
|
|
3591
3517
|
if (!isRecord6(value) || !Array.isArray(value.content_item_kinds)) reject("Codex user message provenance is malformed.");
|
|
@@ -3615,8 +3541,8 @@ function safeTimestamp(value) {
|
|
|
3615
3541
|
if (!Number.isFinite(Date.parse(value))) reject(`Codex transcript timestamp is invalid (${refusalFact("parsed_ms", Date.parse(value), "a finite epoch milliseconds value")}).`);
|
|
3616
3542
|
return value;
|
|
3617
3543
|
}
|
|
3618
|
-
function
|
|
3619
|
-
if (typeof value !== "string" || !
|
|
3544
|
+
function safeRef3(value, message) {
|
|
3545
|
+
if (typeof value !== "string" || !SAFE_REF5.test(value)) reject(message);
|
|
3620
3546
|
return value;
|
|
3621
3547
|
}
|
|
3622
3548
|
function boundedText(value, maximum, message) {
|
|
@@ -3654,45 +3580,41 @@ function codexTranscriptInitialCursor(registration, device, inode, now) {
|
|
|
3654
3580
|
anchor_sha256: digest(Buffer.alloc(0)),
|
|
3655
3581
|
header_validated: false,
|
|
3656
3582
|
status: "active",
|
|
3657
|
-
updated_at:
|
|
3583
|
+
updated_at: timestamp2(now)
|
|
3658
3584
|
};
|
|
3659
3585
|
}
|
|
3660
|
-
async function ownedCodexTranscriptFile(handle) {
|
|
3586
|
+
async function ownedCodexTranscriptFile(handle, platform = process.platform) {
|
|
3661
3587
|
const stats = await handle.stat();
|
|
3662
|
-
|
|
3588
|
+
if (!stats.isFile()) throw new CodexHostAdapterError("storage", "Codex transcript source is not a regular file.");
|
|
3589
|
+
if (platform !== "win32" && !isSelfOwned(stats.uid, platform, handle)) throw new CodexHostAdapterError("storage", "Codex transcript source is owned by another user.");
|
|
3663
3590
|
return { size: stats.size, device: String(stats.dev), inode: String(stats.ino) };
|
|
3664
3591
|
}
|
|
3665
|
-
function
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
}
|
|
3670
|
-
function assertCodexTranscriptHeader(registration, record3) {
|
|
3671
|
-
if (record3.kind !== "session_meta" || record3.session_id !== registration.codex_session_id) throw new CodexHostAdapterError("payload_rejected", `Codex transcript header does not match its SessionStart registration (${refusalFacts([
|
|
3672
|
-
{ name: "kind", value: record3.kind, expected: "session_meta" },
|
|
3673
|
-
{ name: "session_id", value: record3.kind === "session_meta" ? record3.session_id : "unavailable", expected: registration.codex_session_id }
|
|
3592
|
+
function assertCodexTranscriptHeader(registration, record) {
|
|
3593
|
+
if (record.kind !== "session_meta" || record.session_id !== registration.codex_session_id) throw new CodexHostAdapterError("payload_rejected", `Codex transcript header does not match its SessionStart registration (${refusalFacts([
|
|
3594
|
+
{ name: "kind", value: record.kind, expected: "session_meta" },
|
|
3595
|
+
{ name: "session_id", value: record.kind === "session_meta" ? record.session_id : "unavailable", expected: registration.codex_session_id }
|
|
3674
3596
|
])}).`);
|
|
3675
3597
|
}
|
|
3676
3598
|
function digest(value) {
|
|
3677
3599
|
return createHash6("sha256").update(value).digest("hex");
|
|
3678
3600
|
}
|
|
3679
|
-
function
|
|
3601
|
+
function timestamp2(now) {
|
|
3680
3602
|
const value = now();
|
|
3681
3603
|
if (!Number.isFinite(value.getTime())) throw new CodexHostAdapterError("storage", "Codex watcher clock is invalid.");
|
|
3682
3604
|
return value.toISOString();
|
|
3683
3605
|
}
|
|
3684
3606
|
|
|
3685
3607
|
// host/codex/src/codex-adapter.ts
|
|
3686
|
-
var
|
|
3608
|
+
var SAFE_REF6 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
3687
3609
|
function codexSubmissionReference(hostInputRef, event, ordinal) {
|
|
3688
|
-
if (!
|
|
3610
|
+
if (!SAFE_REF6.test(hostInputRef) || !Number.isSafeInteger(ordinal) || ordinal < 0) throw new CodexHostAdapterError("invalid_input", "Codex submission identity is invalid.");
|
|
3689
3611
|
return `codex-submission:${cealRequestSha256({ host_input_ref: hostInputRef, event, ordinal })}`;
|
|
3690
3612
|
}
|
|
3691
3613
|
|
|
3692
3614
|
// host/codex/src/codex-transcript-watcher.ts
|
|
3693
3615
|
var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
3694
3616
|
store;
|
|
3695
|
-
|
|
3617
|
+
diagnostic;
|
|
3696
3618
|
constructor(options) {
|
|
3697
3619
|
super(options, {
|
|
3698
3620
|
default_retention_ms: CODEX_DEFAULT_RETENTION_MS,
|
|
@@ -3700,7 +3622,7 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3700
3622
|
clock_failure: () => new CodexHostAdapterError("storage", "Codex watcher clock is invalid.")
|
|
3701
3623
|
});
|
|
3702
3624
|
this.store = new CodexOutboxStore(options.outbox_dir);
|
|
3703
|
-
this.
|
|
3625
|
+
this.diagnostic = options.diagnostic ?? (() => void 0);
|
|
3704
3626
|
}
|
|
3705
3627
|
transcriptPathOf(registration) {
|
|
3706
3628
|
return registration.transcript_path;
|
|
@@ -3716,7 +3638,8 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3716
3638
|
if (complete === "empty" || complete === "partial") return { records: 0, enqueued: 0, quarantined: false };
|
|
3717
3639
|
return await this.consumeCompleteChunk(registration, handle, preflight.cursor, complete, device, inode);
|
|
3718
3640
|
}
|
|
3719
|
-
async scanFailed(handle, registration,
|
|
3641
|
+
async scanFailed(handle, registration, error) {
|
|
3642
|
+
this.diagnostic("watcher_scan_failed", codexTranscriptScanFailureKind(error));
|
|
3720
3643
|
await this.quarantineFailure(registration, handle);
|
|
3721
3644
|
return { records: 0, enqueued: 0, quarantined: true };
|
|
3722
3645
|
}
|
|
@@ -3726,7 +3649,9 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3726
3649
|
async preflight(registration, handle, size, device, inode) {
|
|
3727
3650
|
const existing = await this.store.readTranscriptCursor(registration);
|
|
3728
3651
|
let cursor = existing ?? codexTranscriptInitialCursor(registration, device, inode, this.now);
|
|
3729
|
-
if (cursor.status === "quarantined")
|
|
3652
|
+
if (cursor.status === "quarantined") {
|
|
3653
|
+
return { cursor: null, result: { records: 0, enqueued: 0, quarantined: true } };
|
|
3654
|
+
}
|
|
3730
3655
|
const reason = this.cursorIdentityReason(cursor, registration.transcript_path, device, inode) ?? await this.cursorBoundsReason(handle, cursor, size);
|
|
3731
3656
|
if (reason === null) return { cursor, result: { records: 0, enqueued: 0, quarantined: false } };
|
|
3732
3657
|
await this.quarantine(registration, cursor, device, inode, reason);
|
|
@@ -3738,22 +3663,22 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3738
3663
|
let records = 0;
|
|
3739
3664
|
let enqueued = 0;
|
|
3740
3665
|
for (const line of complete.toString("utf8").split("\n").filter((value) => value.length > 0)) {
|
|
3741
|
-
const
|
|
3742
|
-
assertRecordSequence(registration, cursor, records, lastOrdinal,
|
|
3743
|
-
await this.observeSessionMetadata(registration,
|
|
3744
|
-
const outcome = await this.processRecord(registration, activeTask,
|
|
3666
|
+
const record = decodeCodexTranscriptLine(line);
|
|
3667
|
+
assertRecordSequence(registration, cursor, records, lastOrdinal, record);
|
|
3668
|
+
await this.observeSessionMetadata(registration, record);
|
|
3669
|
+
const outcome = await this.processRecord(registration, activeTask, record);
|
|
3745
3670
|
activeTask = outcome.activeTask;
|
|
3746
3671
|
enqueued += outcome.enqueued;
|
|
3747
|
-
lastOrdinal =
|
|
3672
|
+
lastOrdinal = record.ordinal;
|
|
3748
3673
|
records += 1;
|
|
3749
3674
|
}
|
|
3750
3675
|
await this.commitCursor(registration, handle, cursor, complete.length, device, inode, lastOrdinal, activeTask);
|
|
3751
3676
|
return { records, enqueued, quarantined: false };
|
|
3752
3677
|
}
|
|
3753
|
-
async observeSessionMetadata(registration,
|
|
3754
|
-
if (
|
|
3678
|
+
async observeSessionMetadata(registration, record) {
|
|
3679
|
+
if (record.kind !== "session_meta") return;
|
|
3755
3680
|
const session = await this.store.readSession(registration.host_source_ref, registration.host_session_ref);
|
|
3756
|
-
if (session !== null && this.isRecord(session.value)) await this.store.saveSession({ ...session, value: { ...session.value, host_version:
|
|
3681
|
+
if (session !== null && this.isRecord(session.value)) await this.store.saveSession({ ...session, value: { ...session.value, host_version: record.cli_version } });
|
|
3757
3682
|
}
|
|
3758
3683
|
async commitCursor(registration, handle, cursor, consumedBytes, device, inode, lastOrdinal, activeTask) {
|
|
3759
3684
|
const offset = cursor.offset + consumedBytes;
|
|
@@ -3765,75 +3690,132 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3765
3690
|
const stats = await handle.stat().catch(() => null);
|
|
3766
3691
|
if (stats === null) return;
|
|
3767
3692
|
const existing = await this.store.readTranscriptCursor(registration).catch(() => null);
|
|
3768
|
-
|
|
3693
|
+
const quarantined = {
|
|
3694
|
+
...existing ?? codexTranscriptInitialCursor(registration, String(stats.dev), String(stats.ino), this.now),
|
|
3695
|
+
transcript_path: registration.transcript_path,
|
|
3696
|
+
device: String(stats.dev),
|
|
3697
|
+
inode: String(stats.ino),
|
|
3698
|
+
status: "quarantined",
|
|
3699
|
+
reason: "transcript_malformed",
|
|
3700
|
+
updated_at: this.stamp()
|
|
3701
|
+
};
|
|
3702
|
+
await this.store.writeTranscriptCursor(quarantined);
|
|
3769
3703
|
}
|
|
3770
|
-
async processRecord(registration, activeTask,
|
|
3771
|
-
if (
|
|
3772
|
-
|
|
3773
|
-
}
|
|
3774
|
-
if (
|
|
3775
|
-
if (
|
|
3776
|
-
if (record3.kind === "user_text") return this.processUserInput(registration, activeTask, record3);
|
|
3777
|
-
if (record3.kind === "task_complete" || record3.kind === "turn_aborted") return this.processTerminal(registration, activeTask, record3);
|
|
3704
|
+
async processRecord(registration, activeTask, record) {
|
|
3705
|
+
if (record.kind === "task_started") return this.processTaskStarted(registration, record);
|
|
3706
|
+
if (record.kind === "turn_context") return { ...activeTask === void 0 ? {} : { activeTask: record.turn_id === activeTask.codex_turn_id ? { ...activeTask, model: record.model } : activeTask }, enqueued: 0 };
|
|
3707
|
+
if (record.kind === "token_count") return { ...activeTask === void 0 ? {} : { activeTask: accumulateTokenCount(activeTask, record) }, enqueued: 0 };
|
|
3708
|
+
if (record.kind === "user_text") return this.processUserInput(registration, activeTask, record);
|
|
3709
|
+
if (record.kind === "task_complete" || record.kind === "turn_aborted") return this.processTerminal(registration, activeTask, record);
|
|
3778
3710
|
return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
|
|
3779
3711
|
}
|
|
3780
|
-
async
|
|
3781
|
-
|
|
3782
|
-
|
|
3712
|
+
async processTaskStarted(registration, record) {
|
|
3713
|
+
return { activeTask: { codex_turn_id: record.turn_id, host_turn_ref: hostTurnReference(registration.host_source_ref, registration.host_session_ref, record.turn_id), started_at: record.timestamp }, enqueued: 0 };
|
|
3714
|
+
}
|
|
3715
|
+
async processUserInput(registration, activeTask, record) {
|
|
3716
|
+
if (activeTask === void 0 || activeTask.codex_turn_id !== record.turn_id) {
|
|
3717
|
+
const adopted = await this.adoptHookCorrelatedTurn(registration, record);
|
|
3718
|
+
if (adopted === void 0) return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
|
|
3719
|
+
activeTask = adopted;
|
|
3720
|
+
}
|
|
3721
|
+
const hostInputRef = hostInputReference(registration.host_source_ref, registration.host_session_ref, record.turn_id, record.message_id, record.ordinal, record.prompt);
|
|
3783
3722
|
const origin = activeTask.origin_host_input_ref === void 0;
|
|
3784
|
-
const promptValue = promptObservationValue(
|
|
3723
|
+
const promptValue = promptObservationValue(record.prompt);
|
|
3785
3724
|
const currentPromptRef = hostProducerPromptRef(promptValue.body_sha256);
|
|
3786
3725
|
const promptRef = activeTask.prompt_ref ?? currentPromptRef;
|
|
3726
|
+
const steeredPromptRef = await this.steeredPromptRef(registration, activeTask, origin, currentPromptRef);
|
|
3787
3727
|
const identity = { host_source_ref: registration.host_source_ref, host_session_ref: registration.host_session_ref, host_turn_ref: activeTask.host_turn_ref };
|
|
3788
3728
|
const observations = [{
|
|
3789
|
-
observation_ref: observationRef("transcript_user_text", hostInputRef,
|
|
3729
|
+
observation_ref: observationRef(registration.host_source_ref, "transcript_user_text", hostInputRef, record.prompt),
|
|
3790
3730
|
observation_kind: "prompt",
|
|
3791
|
-
observed_at:
|
|
3731
|
+
observed_at: record.timestamp,
|
|
3792
3732
|
value: promptValue
|
|
3793
3733
|
}];
|
|
3794
3734
|
if (origin) {
|
|
3795
|
-
observations.push({ observation_ref: observationRef("transcript_task_started", activeTask.host_turn_ref,
|
|
3735
|
+
observations.push({ observation_ref: observationRef(registration.host_source_ref, "transcript_task_started", activeTask.host_turn_ref, record.turn_id), observation_kind: "turn", observed_at: record.timestamp, value: turnObservationValue(registration.codex_session_id, activeTask.host_turn_ref, "started") });
|
|
3796
3736
|
const session = await this.store.readSession(registration.host_source_ref, registration.host_session_ref);
|
|
3797
|
-
if (session !== null) observations.push({ observation_ref: observationRef("transcript_session", registration.host_session_ref, session.value), observation_kind: "session", observed_at:
|
|
3737
|
+
if (session !== null) observations.push({ observation_ref: observationRef(registration.host_source_ref, "transcript_session", registration.host_session_ref, session.value), observation_kind: "session", observed_at: record.timestamp, value: session.value });
|
|
3798
3738
|
}
|
|
3799
3739
|
const envelope = {
|
|
3800
3740
|
schema_version: "ceal.codex_observation_outbox.v1",
|
|
3801
3741
|
identity,
|
|
3802
3742
|
host_input_ref: hostInputRef,
|
|
3803
|
-
input_kind: record3.input_kind,
|
|
3804
3743
|
prompt_ref: promptRef,
|
|
3805
3744
|
observations,
|
|
3806
|
-
codex_turn_id:
|
|
3745
|
+
codex_turn_id: record.turn_id,
|
|
3807
3746
|
origin_input: origin,
|
|
3808
|
-
submission_ref: codexSubmissionReference(hostInputRef, "input",
|
|
3809
|
-
created_at:
|
|
3747
|
+
submission_ref: codexSubmissionReference(hostInputRef, "input", record.ordinal),
|
|
3748
|
+
created_at: record.timestamp,
|
|
3810
3749
|
expires_at: this.expire(this.outboxRetentionMs)
|
|
3811
3750
|
};
|
|
3812
|
-
await this.
|
|
3751
|
+
const persisted = await this.persistUserInputTurn(activeTask, hostInputRef, origin, currentPromptRef, promptRef, identity, envelope);
|
|
3752
|
+
if (steeredPromptRef !== null) await this.activateTranscriptTurnContext(identity, steeredPromptRef, envelope.expires_at);
|
|
3753
|
+
return persisted;
|
|
3754
|
+
}
|
|
3755
|
+
/**
|
|
3756
|
+
* The steered prompt to re-activate under the running task, or null when
|
|
3757
|
+
* there is nothing to repair. A steered message joins the running turn
|
|
3758
|
+
* with new prompt bytes but no task_started. When the steer hook already
|
|
3759
|
+
* replaced the turn selection (a turn id the hook did not recognize as
|
|
3760
|
+
* the running one, or a hook that ran before any context was live), the
|
|
3761
|
+
* running task's context no longer matches and the next invocation would
|
|
3762
|
+
* wait for a boundary that never comes. When the hook kept the live
|
|
3763
|
+
* selection, the context still matches and this stays null.
|
|
3764
|
+
*/
|
|
3765
|
+
async steeredPromptRef(registration, activeTask, origin, currentPromptRef) {
|
|
3766
|
+
if (origin || activeTask.prompt_ref === void 0 || activeTask.prompt_ref === currentPromptRef) return null;
|
|
3767
|
+
const selection = await this.store.readInvocationTurnSelection(registration.host_source_ref, registration.host_session_ref).catch(() => null);
|
|
3768
|
+
return selection?.prompt_ref === currentPromptRef ? currentPromptRef : null;
|
|
3769
|
+
}
|
|
3770
|
+
/**
|
|
3771
|
+
* Adopt the live hooked turn when its transcript message arrives before
|
|
3772
|
+
* (or without) its task_started, as an interrupted turn's follow-up can.
|
|
3773
|
+
* The UserPromptSubmit hook names the exact hooked prompt, so only a
|
|
3774
|
+
* message carrying those bytes adopts; inherited history and other turns
|
|
3775
|
+
* stay ignored. A stale active task for another turn releases first, the
|
|
3776
|
+
* same as a task_started supersession.
|
|
3777
|
+
*/
|
|
3778
|
+
async adoptHookCorrelatedTurn(registration, record) {
|
|
3779
|
+
const currentPromptRef = hostProducerPromptRef(promptObservationValue(record.prompt).body_sha256);
|
|
3780
|
+
const selection = await this.store.readInvocationTurnSelection(registration.host_source_ref, registration.host_session_ref);
|
|
3781
|
+
if (selection?.prompt_ref !== currentPromptRef) return void 0;
|
|
3782
|
+
return {
|
|
3783
|
+
codex_turn_id: record.turn_id,
|
|
3784
|
+
host_turn_ref: hostTurnReference(registration.host_source_ref, registration.host_session_ref, record.turn_id),
|
|
3785
|
+
started_at: record.timestamp
|
|
3786
|
+
};
|
|
3787
|
+
}
|
|
3788
|
+
async persistUserInputTurn(activeTask, hostInputRef, origin, currentPromptRef, promptRef, identity, envelope) {
|
|
3813
3789
|
await this.store.put(envelope);
|
|
3814
|
-
if (origin) await activateTranscriptTurnContext(
|
|
3790
|
+
if (origin) await this.activateTranscriptTurnContext(identity, promptRef, envelope.expires_at);
|
|
3815
3791
|
return { activeTask: { ...activeTask, origin_host_input_ref: activeTask.origin_host_input_ref ?? hostInputRef, prompt_ref: activeTask.prompt_ref ?? currentPromptRef }, enqueued: 1 };
|
|
3816
3792
|
}
|
|
3817
|
-
async
|
|
3818
|
-
|
|
3819
|
-
|
|
3793
|
+
async activateTranscriptTurnContext(identity, promptRef, expiresAt) {
|
|
3794
|
+
try {
|
|
3795
|
+
const selection = await this.store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
|
|
3796
|
+
const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
|
|
3797
|
+
await this.store.activateInvocationContext(identity, promptRef, expiresAt, turnGenerationRef);
|
|
3798
|
+
this.diagnostic("activation_recorded");
|
|
3799
|
+
} catch (error) {
|
|
3800
|
+
this.diagnostic("activation_failed");
|
|
3801
|
+
throw error;
|
|
3802
|
+
}
|
|
3820
3803
|
}
|
|
3821
|
-
async processTerminal(registration, activeTask,
|
|
3822
|
-
if (activeTask === void 0 || activeTask.codex_turn_id !==
|
|
3804
|
+
async processTerminal(registration, activeTask, record) {
|
|
3805
|
+
if (activeTask === void 0 || activeTask.codex_turn_id !== record.turn_id || activeTask.origin_host_input_ref === void 0 || activeTask.prompt_ref === void 0) return { ...activeTask === void 0 ? {} : { activeTask }, enqueued: 0 };
|
|
3823
3806
|
const identity = { host_source_ref: registration.host_source_ref, host_session_ref: registration.host_session_ref, host_turn_ref: activeTask.host_turn_ref };
|
|
3824
|
-
const timing =
|
|
3825
|
-
const observations = terminalObservations(registration, activeTask,
|
|
3807
|
+
const timing = record.kind === "task_complete" ? { status: "available", unit: "milliseconds", started_at: record.started_at, completed_at: record.completed_at, duration_ms: record.duration_ms, time_to_first_token_ms: record.time_to_first_token_ms } : { status: "available", unit: "milliseconds", started_at: record.started_at, completed_at: record.completed_at, duration_ms: record.duration_ms, terminal: "aborted", reason: record.reason };
|
|
3808
|
+
const observations = terminalObservations(registration, activeTask, record, timing);
|
|
3826
3809
|
await this.store.put({
|
|
3827
3810
|
schema_version: "ceal.codex_observation_outbox.v1",
|
|
3828
3811
|
identity,
|
|
3829
3812
|
host_input_ref: activeTask.origin_host_input_ref,
|
|
3830
|
-
input_kind: "host_automatic",
|
|
3831
3813
|
prompt_ref: activeTask.prompt_ref,
|
|
3832
3814
|
observations,
|
|
3833
|
-
codex_turn_id:
|
|
3815
|
+
codex_turn_id: record.turn_id,
|
|
3834
3816
|
origin_input: false,
|
|
3835
|
-
submission_ref: codexSubmissionReference(activeTask.origin_host_input_ref, "terminal",
|
|
3836
|
-
created_at:
|
|
3817
|
+
submission_ref: codexSubmissionReference(activeTask.origin_host_input_ref, "terminal", record.ordinal),
|
|
3818
|
+
created_at: record.timestamp,
|
|
3837
3819
|
expires_at: this.expire(this.outboxRetentionMs)
|
|
3838
3820
|
});
|
|
3839
3821
|
await this.store.clearInvocationContext(identity, this.now());
|
|
@@ -3844,15 +3826,18 @@ var CodexTranscriptWatcher = class extends HostTranscriptWatcher {
|
|
|
3844
3826
|
await this.store.writeTranscriptCursor(quarantined);
|
|
3845
3827
|
}
|
|
3846
3828
|
};
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3829
|
+
function codexTranscriptScanFailureKind(error) {
|
|
3830
|
+
return transcriptScanFailureKind(error, (candidate) => {
|
|
3831
|
+
if (!(candidate instanceof CodexHostAdapterError)) return void 0;
|
|
3832
|
+
if (candidate.code === "payload_rejected") return "payload_rejected";
|
|
3833
|
+
if (candidate.code === "storage") return "read_error";
|
|
3834
|
+
return void 0;
|
|
3835
|
+
});
|
|
3836
|
+
}
|
|
3837
|
+
function accumulateTokenCount(activeTask, record) {
|
|
3838
|
+
if (record.usage_gap_reason !== void 0) return { ...activeTask, token_usage_gap_reason: record.usage_gap_reason };
|
|
3839
|
+
const last = record.last_usage;
|
|
3840
|
+
const total = record.total_usage;
|
|
3856
3841
|
if (last === void 0 || ignoreTokenCount(activeTask.last_total_usage, total, last)) return activeTask;
|
|
3857
3842
|
const tokenUsage = addUsage(activeTask.token_usage, last);
|
|
3858
3843
|
return {
|
|
@@ -3866,21 +3851,16 @@ function ignoreTokenCount(previous, total, last) {
|
|
|
3866
3851
|
if (total === void 0 || previous === void 0) return false;
|
|
3867
3852
|
return sameUsage(total, previous) || regressedUsage(total, previous) && looksLikeStaleRegression(total, previous, last);
|
|
3868
3853
|
}
|
|
3869
|
-
function terminalObservations(registration, activeTask,
|
|
3854
|
+
function terminalObservations(registration, activeTask, record, timing) {
|
|
3870
3855
|
const tokenValue = activeTask.token_usage_gap_reason !== void 0 ? notRecorded(activeTask.token_usage_gap_reason) : activeTask.token_usage === void 0 ? notRecorded("token_count_not_observed_for_active_turn") : tokenObservationValue(activeTask.token_usage);
|
|
3871
|
-
const costValue = terminalCostValue();
|
|
3872
3856
|
const observations = [
|
|
3873
|
-
{ observation_ref: observationRef("transcript_terminal", activeTask.host_turn_ref,
|
|
3874
|
-
{ observation_ref: observationRef("transcript_timing", activeTask.host_turn_ref, timing), observation_kind: "timing", observed_at:
|
|
3875
|
-
{ observation_ref: observationRef("transcript_token", activeTask.host_turn_ref, tokenValue), observation_kind: "token", observed_at:
|
|
3876
|
-
{ observation_ref: observationRef("transcript_cost", activeTask.host_turn_ref, costValue), observation_kind: "cost", observed_at: record3.timestamp, value: costValue }
|
|
3857
|
+
{ observation_ref: observationRef(registration.host_source_ref, "transcript_terminal", activeTask.host_turn_ref, record), observation_kind: "turn", observed_at: record.timestamp, value: turnObservationValue(registration.codex_session_id, activeTask.host_turn_ref, "stopped") },
|
|
3858
|
+
{ observation_ref: observationRef(registration.host_source_ref, "transcript_timing", activeTask.host_turn_ref, timing), observation_kind: "timing", observed_at: record.timestamp, value: timing },
|
|
3859
|
+
{ observation_ref: observationRef(registration.host_source_ref, "transcript_token", activeTask.host_turn_ref, tokenValue), observation_kind: "token", observed_at: record.timestamp, value: tokenValue }
|
|
3877
3860
|
];
|
|
3878
|
-
if (activeTask.model !== void 0) observations.push({ observation_ref: observationRef("transcript_model", activeTask.host_turn_ref, activeTask.model), observation_kind: "model", observed_at:
|
|
3861
|
+
if (activeTask.model !== void 0) observations.push({ observation_ref: observationRef(registration.host_source_ref, "transcript_model", activeTask.host_turn_ref, activeTask.model), observation_kind: "model", observed_at: record.timestamp, value: { status: "available", value: activeTask.model, source: "turn_context.model" } });
|
|
3879
3862
|
return observations;
|
|
3880
3863
|
}
|
|
3881
|
-
function terminalCostValue() {
|
|
3882
|
-
return notRecorded("provider_cost_not_observed");
|
|
3883
|
-
}
|
|
3884
3864
|
function notRecorded(reason) {
|
|
3885
3865
|
return hostObservationNotRecorded(reason);
|
|
3886
3866
|
}
|
|
@@ -3927,16 +3907,21 @@ function looksLikeStaleRegression(current, previous, last) {
|
|
|
3927
3907
|
if (previous.total_tokens <= 0 || current.total_tokens <= 0 || last.total_tokens <= 0) return false;
|
|
3928
3908
|
return current.total_tokens * 100 >= previous.total_tokens * 98 || current.total_tokens + last.total_tokens * 2 >= previous.total_tokens;
|
|
3929
3909
|
}
|
|
3930
|
-
function assertRecordSequence(registration, cursor, records, lastOrdinal,
|
|
3931
|
-
if ((cursor.header_validated || records > 0) &&
|
|
3932
|
-
if (!cursor.header_validated && records === 0) assertCodexTranscriptHeader(registration,
|
|
3910
|
+
function assertRecordSequence(registration, cursor, records, lastOrdinal, record) {
|
|
3911
|
+
if ((cursor.header_validated || records > 0) && record.ordinal <= lastOrdinal) throw new CodexHostAdapterError("payload_rejected", `Codex transcript ordinal is not monotonic (${refusalFact("ordinal", record.ordinal, `greater than ${lastOrdinal}`)}).`);
|
|
3912
|
+
if (!cursor.header_validated && records === 0) assertCodexTranscriptHeader(registration, record);
|
|
3933
3913
|
}
|
|
3934
3914
|
|
|
3935
3915
|
// host/codex/src/codex-producer-runtime.ts
|
|
3936
3916
|
async function drainCodexProducerOnce(options) {
|
|
3937
|
-
const
|
|
3938
|
-
|
|
3939
|
-
|
|
3917
|
+
const scan = await new CodexTranscriptWatcher({
|
|
3918
|
+
outbox_dir: options.outbox_dir,
|
|
3919
|
+
diagnostic: (event, failure_kind) => writeHostServiceDiagnostic("codex", event, failure_kind)
|
|
3920
|
+
}).scanOnce().catch((error) => {
|
|
3921
|
+
writeHostServiceDiagnostic("codex", "watcher_scan_failed", codexTranscriptScanFailureKind(error));
|
|
3922
|
+
throw error;
|
|
3923
|
+
});
|
|
3924
|
+
const producer = new CodexProducer({ outbox_dir: options.outbox_dir, ...options.now === void 0 ? {} : { now: options.now } });
|
|
3940
3925
|
return { scanned: scan.enqueued, ...await producer.drainOutbox() };
|
|
3941
3926
|
}
|
|
3942
3927
|
|
|
@@ -3949,11 +3934,7 @@ var CODEX_PRODUCER_DESCRIPTOR = {
|
|
|
3949
3934
|
extra_paths: [CODEX_SESSIONS_ROOT],
|
|
3950
3935
|
config: (shared, extras) => ({ ...shared, codex_sessions_root: hostProducerExtraPath(extras, CODEX_SESSIONS_ROOT) }),
|
|
3951
3936
|
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
|
|
3937
|
+
outbox_dir: config.outbox_dir
|
|
3957
3938
|
}),
|
|
3958
3939
|
drain: drainCodexProducerOnce
|
|
3959
3940
|
};
|
|
@@ -3961,62 +3942,17 @@ function runCodexProducerCli(args, signal, dependencies = {}) {
|
|
|
3961
3942
|
return runHostProducerCli(CODEX_PRODUCER_DESCRIPTOR, args, signal, dependencies);
|
|
3962
3943
|
}
|
|
3963
3944
|
|
|
3964
|
-
// host/service-log.ts
|
|
3965
|
-
import { copyFile, open as open5, stat, truncate } from "node:fs/promises";
|
|
3966
|
-
import { isAbsolute as isAbsolute2 } from "node:path";
|
|
3967
|
-
var HOST_SERVICE_LOG_ENVIRONMENT = "CEAL_HOST_SERVICE_LOG";
|
|
3968
|
-
var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
|
|
3969
|
-
var HOST_SERVICE_LOG_CHECK_INTERVAL_MS = 6e4;
|
|
3970
|
-
function hostServiceLogPreviousPath(path) {
|
|
3971
|
-
return `${path}.1`;
|
|
3972
|
-
}
|
|
3973
|
-
async function rotateHostServiceLog(path, maxBytes = HOST_SERVICE_LOG_MAX_BYTES) {
|
|
3974
|
-
let size;
|
|
3975
|
-
try {
|
|
3976
|
-
size = (await stat(path)).size;
|
|
3977
|
-
} catch (error) {
|
|
3978
|
-
if (isNotFound2(error)) return false;
|
|
3979
|
-
throw error;
|
|
3980
|
-
}
|
|
3981
|
-
if (size <= maxBytes) return false;
|
|
3982
|
-
await copyFile(path, hostServiceLogPreviousPath(path));
|
|
3983
|
-
await truncate(path, 0);
|
|
3984
|
-
return true;
|
|
3985
|
-
}
|
|
3986
|
-
function boundHostServiceLog(environment = process.env) {
|
|
3987
|
-
const path = environment[HOST_SERVICE_LOG_ENVIRONMENT];
|
|
3988
|
-
if (path === void 0 || !isAbsolute2(path)) return () => void 0;
|
|
3989
|
-
let reported = false;
|
|
3990
|
-
const check = () => {
|
|
3991
|
-
rotateHostServiceLog(path).catch((error) => {
|
|
3992
|
-
if (reported) return;
|
|
3993
|
-
reported = true;
|
|
3994
|
-
process.stderr.write(`[host-service-log] rotation_failed code=${errorCode3(error)}
|
|
3995
|
-
`);
|
|
3996
|
-
});
|
|
3997
|
-
};
|
|
3998
|
-
check();
|
|
3999
|
-
const timer = setInterval(check, HOST_SERVICE_LOG_CHECK_INTERVAL_MS);
|
|
4000
|
-
timer.unref();
|
|
4001
|
-
return () => clearInterval(timer);
|
|
4002
|
-
}
|
|
4003
|
-
var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
|
|
4004
|
-
function isNotFound2(error) {
|
|
4005
|
-
return errorCode3(error) === "ENOENT";
|
|
4006
|
-
}
|
|
4007
|
-
function errorCode3(error) {
|
|
4008
|
-
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "unknown";
|
|
4009
|
-
}
|
|
4010
|
-
|
|
4011
3945
|
// host/producer-command.ts
|
|
4012
3946
|
async function runProducerCommand(schemaVersion, run) {
|
|
3947
|
+
const serviceArguments = consumeHostServiceLogArgument(process.argv.slice(2));
|
|
3948
|
+
if (serviceArguments.path !== void 0) process.env[HOST_SERVICE_LOG_ENVIRONMENT] = serviceArguments.path;
|
|
4013
3949
|
const controller = new AbortController();
|
|
4014
3950
|
const stop = () => controller.abort();
|
|
4015
3951
|
process.once("SIGINT", stop);
|
|
4016
3952
|
process.once("SIGTERM", stop);
|
|
4017
3953
|
const stopLogBound = boundHostServiceLog();
|
|
4018
3954
|
try {
|
|
4019
|
-
await run(
|
|
3955
|
+
await run(serviceArguments.args, controller.signal);
|
|
4020
3956
|
} catch (error) {
|
|
4021
3957
|
const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "producer_failed";
|
|
4022
3958
|
process.stderr.write(`${JSON.stringify({ schema_version: schemaVersion, ok: false, code })}
|