@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
|
@@ -29,16 +29,34 @@ function isPlainRecord(value) {
|
|
|
29
29
|
return prototype === Object.prototype || prototype === null;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// packages/ceal-client-protocol/src/client-wire-contract.ts
|
|
33
|
+
import { createHash } from "node:crypto";
|
|
34
|
+
|
|
32
35
|
// packages/ceal-client-protocol/client-wire-contract.json
|
|
33
36
|
var client_wire_contract_default = {
|
|
34
37
|
schema_version: "ceal.client_wire_contract.v1",
|
|
38
|
+
doctor: {
|
|
39
|
+
schema_version: "ceal.doctor.v1"
|
|
40
|
+
},
|
|
41
|
+
client_commands: {
|
|
42
|
+
doctor: ["doctor"],
|
|
43
|
+
version: ["version"],
|
|
44
|
+
access_group_list: ["access-group", "list"]
|
|
45
|
+
},
|
|
46
|
+
client_environment: {
|
|
47
|
+
environment_prefix: "CEAL",
|
|
48
|
+
context_override_suffixes: ["GATEWAY_ORIGIN", "GATEWAY_TRANSPORT_ORIGIN", "ACCESS_TOKEN", "CONTEXT_STORE_PATH"]
|
|
49
|
+
},
|
|
35
50
|
canonical_json: {
|
|
36
51
|
ordering: "utf16_code_unit_ascending"
|
|
37
52
|
},
|
|
38
53
|
artifact_descriptor: {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
54
|
+
operation_contract_bundle_schema_name: "ceal.operation_contract_bundle",
|
|
55
|
+
operation_contract_bundle_fields: ["schema_version", "catalog_revision", "operations", "failure_guidance"],
|
|
56
|
+
schema_name: "ceal.verified_artifact_descriptor",
|
|
57
|
+
fields: ["schema_version", "kind", "media_type", "byte_count", "sha256", "binding"],
|
|
58
|
+
binding_schema_name: "ceal.verified_artifact_binding",
|
|
59
|
+
binding_fields: ["schema_version", "kind", "catalog_revision"],
|
|
42
60
|
kind: "verified_artifact",
|
|
43
61
|
binding_kind: "catalog",
|
|
44
62
|
media_type: "application/json",
|
|
@@ -54,14 +72,16 @@ var client_wire_contract_default = {
|
|
|
54
72
|
length: 72,
|
|
55
73
|
pattern: "^catalog:[a-f0-9]{64}$"
|
|
56
74
|
},
|
|
57
|
-
envelope_max_bytes:
|
|
75
|
+
envelope_max_bytes: 407
|
|
58
76
|
},
|
|
59
77
|
command_request: {
|
|
60
|
-
|
|
78
|
+
schema_name: "ceal.command_request",
|
|
79
|
+
fields: ["schema_version", "argv", "operation", "client_nonce", "context_token"],
|
|
61
80
|
absolute_transport_max_bytes: 65536
|
|
62
81
|
},
|
|
63
82
|
command_resolution: {
|
|
64
|
-
|
|
83
|
+
schema_name: "ceal.gateway_command_resolution",
|
|
84
|
+
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"],
|
|
65
85
|
authority: {
|
|
66
86
|
anonymous: {
|
|
67
87
|
state: "anonymous",
|
|
@@ -101,7 +121,8 @@ var client_wire_contract_default = {
|
|
|
101
121
|
}
|
|
102
122
|
},
|
|
103
123
|
command_response: {
|
|
104
|
-
|
|
124
|
+
schema_name: "ceal.command_response",
|
|
125
|
+
fields: ["schema_version", "exit_code", "document", "operation_id", "visible_output_max_bytes", "local_delivery", "verified_artifact"],
|
|
105
126
|
descriptor_field: "verified_artifact",
|
|
106
127
|
required_fields: [
|
|
107
128
|
"schema_version",
|
|
@@ -125,29 +146,41 @@ var client_wire_contract_default = {
|
|
|
125
146
|
"required_action_sequence",
|
|
126
147
|
"withheld"
|
|
127
148
|
],
|
|
128
|
-
prompt_field: "prompt",
|
|
129
|
-
prompt_schema_version: "ceal.prompt.v1",
|
|
130
|
-
focused_prompt_document_fields: [
|
|
131
|
-
"catalog_revision",
|
|
132
|
-
"prompt"
|
|
133
|
-
],
|
|
134
|
-
focused_prompt_continuation_document_fields: [
|
|
135
|
-
"catalog_revision",
|
|
136
|
-
"remaining_work",
|
|
137
|
-
"prompt"
|
|
138
|
-
],
|
|
139
149
|
discovery_command: "discovery",
|
|
140
|
-
|
|
150
|
+
discovery_manifest_schema_name: "ceal.discovery_manifest",
|
|
151
|
+
discovery_manifest_fields: ["schema_version", "skill", "authority_scope", "connector_kinds", "personal_connector_kinds", "guide_markdown", "renderer_version", "digest"],
|
|
141
152
|
discovery_manifest_document_fields: [
|
|
142
153
|
"discovery_manifest"
|
|
143
154
|
],
|
|
144
155
|
exit_code_max: 125,
|
|
145
156
|
bare_document_max_bytes: 65536,
|
|
146
|
-
complete_max_bytes:
|
|
157
|
+
complete_max_bytes: 66046
|
|
158
|
+
},
|
|
159
|
+
host_invocation_authentication: {
|
|
160
|
+
request_schema_name: "ceal.host_invocation_authentication_request",
|
|
161
|
+
request_fields: [
|
|
162
|
+
"schema_version",
|
|
163
|
+
"client_nonce"
|
|
164
|
+
],
|
|
165
|
+
result_schema_name: "ceal.host_invocation_authentication_result",
|
|
166
|
+
result_fields: [
|
|
167
|
+
"schema_version",
|
|
168
|
+
"context_token",
|
|
169
|
+
"expires_at"
|
|
170
|
+
],
|
|
171
|
+
client_nonce: {
|
|
172
|
+
character_class_source: "A-Za-z0-9_-",
|
|
173
|
+
encoded_characters: 43
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
gateway_host_invocation_context_request: {
|
|
177
|
+
schema_name: "ceal.gateway_host_invocation_context_request",
|
|
178
|
+
fields: ["schema_version", "client_nonce", "context_expires_at", "request_work_context", "current_prompt", "visible_output_max_bytes"]
|
|
147
179
|
},
|
|
148
180
|
resource_stream: {
|
|
149
181
|
path: "/api/ceal/v2/resources",
|
|
150
|
-
|
|
182
|
+
request_schema_name: "ceal.resource_stream_request",
|
|
183
|
+
request_fields: ["schema_version", "resource_ref", "client_nonce", "context_token"],
|
|
151
184
|
ref_pattern: "^resource:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
|
|
152
185
|
media_type_pattern: "^[a-z0-9][a-z0-9!#$&^_.+-]{0,63}/[a-z0-9][a-z0-9!#$&^_.+-]{0,63}$",
|
|
153
186
|
filename_max_bytes: 512,
|
|
@@ -164,92 +197,6 @@ var client_wire_contract_default = {
|
|
|
164
197
|
}
|
|
165
198
|
};
|
|
166
199
|
|
|
167
|
-
// packages/ceal-client-protocol/src/client-wire-contract.ts
|
|
168
|
-
var CEAL_CLIENT_WIRE_CONTRACT = Object.freeze(client_wire_contract_default);
|
|
169
|
-
var commandResolution = CEAL_CLIENT_WIRE_CONTRACT.command_resolution;
|
|
170
|
-
var commandResolutionAuthority = commandResolution.authority;
|
|
171
|
-
var commandResolutionSemanticOptions = commandResolution.semantic_options;
|
|
172
|
-
var CEAL_GATEWAY_COMMAND_RESOLUTION_SCHEMA_VERSION = commandResolution.schema_version;
|
|
173
|
-
var CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES = CEAL_CLIENT_WIRE_CONTRACT.encoded_json_document.max_bytes;
|
|
174
|
-
var artifact = CEAL_CLIENT_WIRE_CONTRACT.artifact_descriptor;
|
|
175
|
-
var request = CEAL_CLIENT_WIRE_CONTRACT.command_request;
|
|
176
|
-
var response = CEAL_CLIENT_WIRE_CONTRACT.command_response;
|
|
177
|
-
var CEAL_OPERATION_CONTRACT_BUNDLE_SCHEMA_VERSION = artifact.operation_contract_bundle_schema_version;
|
|
178
|
-
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_SCHEMA_VERSION = artifact.schema_version;
|
|
179
|
-
var CEAL_VERIFIED_ARTIFACT_BINDING_SCHEMA_VERSION = artifact.binding_schema_version;
|
|
180
|
-
var CEAL_VERIFIED_ARTIFACT_KIND = artifact.kind;
|
|
181
|
-
var CEAL_VERIFIED_ARTIFACT_BINDING_KIND = artifact.binding_kind;
|
|
182
|
-
var CEAL_VERIFIED_ARTIFACT_MEDIA_TYPE = artifact.media_type;
|
|
183
|
-
var CEAL_VERIFIED_ARTIFACT_DIGEST_LENGTH = artifact.digest.length;
|
|
184
|
-
var CEAL_VERIFIED_ARTIFACT_DIGEST_PATTERN = artifact.digest.pattern;
|
|
185
|
-
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_LENGTH = artifact.catalog_revision.length;
|
|
186
|
-
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_PATTERN = artifact.catalog_revision.pattern;
|
|
187
|
-
var CEAL_VERIFIED_ARTIFACT_MAX_BYTES = CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES;
|
|
188
|
-
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_MAX_BYTES = artifact.envelope_max_bytes;
|
|
189
|
-
var CEAL_COMMAND_REQUEST_SCHEMA_VERSION = request.schema_version;
|
|
190
|
-
var CEAL_COMMAND_REQUEST_MAX_BYTES = request.absolute_transport_max_bytes;
|
|
191
|
-
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_MAX_BYTES = response.bare_document_max_bytes;
|
|
192
|
-
var CEAL_COMMAND_RESPONSE_PROMPT_FIELD = response.prompt_field;
|
|
193
|
-
var CEAL_COMMAND_RESPONSE_PROMPT_SCHEMA_VERSION = response.prompt_schema_version;
|
|
194
|
-
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_OPTIONAL_FIELDS = Object.freeze([...response.bare_document_optional_fields]);
|
|
195
|
-
var CEAL_COMMAND_RESPONSE_PROMPT_DOCUMENT_FIELDS = Object.freeze([...response.bare_document_fields, response.prompt_field]);
|
|
196
|
-
var CEAL_COMMAND_RESPONSE_FOCUSED_PROMPT_DOCUMENT_FIELDS = Object.freeze([...response.focused_prompt_document_fields]);
|
|
197
|
-
var CEAL_COMMAND_RESPONSE_FOCUSED_PROMPT_CONTINUATION_DOCUMENT_FIELDS = Object.freeze([...response.focused_prompt_continuation_document_fields]);
|
|
198
|
-
var CEAL_COMMAND_RESPONSE_DISCOVERY_COMMAND = response.discovery_command;
|
|
199
|
-
var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_SCHEMA_VERSION = response.discovery_manifest_schema_version;
|
|
200
|
-
var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_DOCUMENT_FIELDS = Object.freeze([...response.discovery_manifest_document_fields]);
|
|
201
|
-
var CEAL_COMMAND_RESPONSE_MAX_BYTES = CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES;
|
|
202
|
-
|
|
203
|
-
// packages/ceal-client-protocol/src/vocabulary.ts
|
|
204
|
-
function vocabulary(words) {
|
|
205
|
-
return Object.freeze(words);
|
|
206
|
-
}
|
|
207
|
-
function isVocabularyWord(words, value) {
|
|
208
|
-
return words.some((word) => word === value);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// packages/ceal-client-protocol/src/gateway-response-types.ts
|
|
212
|
-
var CEAL_OPERATION_READINESS_VALUES = vocabulary(["ready", "degraded", "unavailable", "unknown"]);
|
|
213
|
-
var CEAL_TARGET_REQUIREMENTS = vocabulary(["required", "optional", "none"]);
|
|
214
|
-
var CEAL_AUDIT_OUTCOMES = vocabulary(["succeeded", "denied", "failed"]);
|
|
215
|
-
var CEAL_GATEWAY_PROVIDER_STEP_OUTCOMES = vocabulary(["completed", "rejected", "throttled", "failed"]);
|
|
216
|
-
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"]);
|
|
217
|
-
var CEAL_GATEWAY_READ_REUSE_UNAVAILABLE_REASONS = vocabulary(["not_reported", "storage_unavailable", "coverage_incomplete", "freshness_refresh_required", "not_reusable", "bypassed"]);
|
|
218
|
-
var CEAL_GATEWAY_DERIVED_STORE_FAILURE_CLASSES = vocabulary(["storage_unavailable", "decode_mismatch", "write_conflict", "quota", "store_error"]);
|
|
219
|
-
var CEAL_POLICY_DECISIONS = vocabulary(["allowed", "denied", "not_evaluated"]);
|
|
220
|
-
var CEAL_CONNECTOR_ROUTE_PHASES = vocabulary(["scope_observation", "target_selection", "route_resolution"]);
|
|
221
|
-
var CEAL_CONNECTOR_ROUTE_CAUSES = vocabulary(["provider_throttled", "provider_unavailable", "binding_invalid", "scope_limit_exceeded"]);
|
|
222
|
-
var CEAL_WRITE_SOURCE_KINDS = vocabulary(["authenticated_registered_client", "provider_authenticated_event"]);
|
|
223
|
-
var CEAL_REFRESH_DELIVERIES = vocabulary(["initial", "recovery", "replay", "terminal_failure", "recovery_unavailable"]);
|
|
224
|
-
var CEAL_CLIENT_OPERATIONS = vocabulary(["handshake", "discover", "call", "readback"]);
|
|
225
|
-
var CEAL_WRITE_IDEMPOTENCY_POSTURES = vocabulary(["required", "optional", "not_required"]);
|
|
226
|
-
var CEAL_WRITE_PROVIDER_READBACK_POSTURES = vocabulary(["required", "best_effort", "not_available"]);
|
|
227
|
-
var CEAL_WRITE_ATTRIBUTIONS = vocabulary(["subject", "requester_event", "connector_integration"]);
|
|
228
|
-
var CEAL_WRITE_RECEIPT_PROVIDER_STATES = vocabulary(["outcome_unknown", "verified", "not_dispatched"]);
|
|
229
|
-
var CEAL_WRITE_RECEIPT_PROVIDER_READBACKS = vocabulary(["outcome_unknown", "verified", "not_applied"]);
|
|
230
|
-
var CEAL_PROTOCOL_VERSION = "1.4.0";
|
|
231
|
-
var CEAL_OPERATION_ACCESS_SCHEMA_VERSION = "ceal.operation_access.v1";
|
|
232
|
-
var CEAL_GATEWAY_CONTINUATION_CURSOR_PHASES = vocabulary(["scan", "delivery", "terminal"]);
|
|
233
|
-
var CEAL_GATEWAY_ACTIVE_CALL_PHASES = vocabulary([
|
|
234
|
-
"audited_call_pending",
|
|
235
|
-
"continuation_read_pending",
|
|
236
|
-
"continuation_write_pending",
|
|
237
|
-
"provider_pacing_pending",
|
|
238
|
-
"corpus_replay_pending",
|
|
239
|
-
"corpus_append_pending",
|
|
240
|
-
"corpus_fence_pending",
|
|
241
|
-
"corpus_content_pending",
|
|
242
|
-
"corpus_transaction_pending"
|
|
243
|
-
]);
|
|
244
|
-
var CEAL_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "phase", "schema_version"]);
|
|
245
|
-
var CEAL_CLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["cause", ...CEAL_CONNECTOR_ROUTE_FAILURE_KEYS]);
|
|
246
|
-
var CEAL_UNCLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "error_class", "phase", "schema_version"]);
|
|
247
|
-
var CEAL_GATEWAY_ORGANIZATION_ROUTE_FACT_CALL_KEYS = Object.freeze([
|
|
248
|
-
"connector_ref",
|
|
249
|
-
"connector_revision",
|
|
250
|
-
"policy_decision_revision"
|
|
251
|
-
]);
|
|
252
|
-
|
|
253
200
|
// packages/ceal-client-protocol/src/operation-next-actions.ts
|
|
254
201
|
var CEAL_REPEAT_REQUEST_NEXT_ACTION = "repeat_request";
|
|
255
202
|
var CEAL_INSPECT_RECEIPT_NEXT_ACTION = "inspect_receipt";
|
|
@@ -316,7 +263,10 @@ var CALLER_RECOVERABLE_NEXT_ACTION_VALUES = [
|
|
|
316
263
|
"follow_required_action_sequence",
|
|
317
264
|
// The command needs caller-owned local materialization (for example an
|
|
318
265
|
// @path file) before the Gateway can receive the argument.
|
|
319
|
-
"materialize_local_argument"
|
|
266
|
+
"materialize_local_argument",
|
|
267
|
+
// A peer's derived schema identifier changed with its field set; install or
|
|
268
|
+
// upgrade the CLI and Host Relay to the Gateway's release before retrying.
|
|
269
|
+
"upgrade_ceal_client"
|
|
320
270
|
];
|
|
321
271
|
var CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS = Object.freeze(CALLER_RECOVERABLE_NEXT_ACTION_VALUES);
|
|
322
272
|
var NON_REMEDY_NEXT_ACTION_VALUES = [
|
|
@@ -429,47 +379,56 @@ var CEAL_OPERATION_WITHHELD_NEXT_ACTIONS = Object.freeze({
|
|
|
429
379
|
personal_authorization_required: "connect_personal_account"
|
|
430
380
|
});
|
|
431
381
|
|
|
432
|
-
// packages/ceal-client-protocol/src/
|
|
433
|
-
|
|
382
|
+
// packages/ceal-client-protocol/src/vocabulary.ts
|
|
383
|
+
function vocabulary(words) {
|
|
384
|
+
return Object.freeze(words);
|
|
385
|
+
}
|
|
386
|
+
function isVocabularyWord(words, value) {
|
|
387
|
+
return words.some((word) => word === value);
|
|
388
|
+
}
|
|
434
389
|
|
|
435
|
-
// packages/ceal-client-protocol/src/
|
|
436
|
-
var
|
|
437
|
-
var
|
|
438
|
-
var
|
|
439
|
-
var
|
|
440
|
-
var
|
|
441
|
-
var
|
|
442
|
-
var
|
|
443
|
-
var
|
|
444
|
-
var
|
|
445
|
-
var
|
|
446
|
-
var
|
|
447
|
-
var
|
|
448
|
-
var
|
|
449
|
-
var
|
|
450
|
-
var
|
|
451
|
-
var
|
|
452
|
-
var
|
|
453
|
-
var
|
|
454
|
-
var
|
|
455
|
-
var
|
|
456
|
-
var
|
|
457
|
-
var
|
|
458
|
-
var
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
"
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
390
|
+
// packages/ceal-client-protocol/src/gateway-response-types.ts
|
|
391
|
+
var CEAL_OPERATION_READINESS_VALUES = vocabulary(["ready", "degraded", "unavailable", "unknown"]);
|
|
392
|
+
var CEAL_TARGET_REQUIREMENTS = vocabulary(["required", "optional", "none"]);
|
|
393
|
+
var CEAL_AUDIT_OUTCOMES = vocabulary(["succeeded", "denied", "failed"]);
|
|
394
|
+
var CEAL_GATEWAY_PROVIDER_STEP_OUTCOMES = vocabulary(["completed", "rejected", "throttled", "failed"]);
|
|
395
|
+
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"]);
|
|
396
|
+
var CEAL_GATEWAY_READ_REUSE_UNAVAILABLE_REASONS = vocabulary(["not_reported", "storage_unavailable", "coverage_incomplete", "freshness_refresh_required", "not_reusable", "bypassed"]);
|
|
397
|
+
var CEAL_GATEWAY_DERIVED_STORE_FAILURE_CLASSES = vocabulary(["storage_unavailable", "decode_mismatch", "write_conflict", "quota", "store_error"]);
|
|
398
|
+
var CEAL_POLICY_DECISIONS = vocabulary(["allowed", "denied", "not_evaluated"]);
|
|
399
|
+
var CEAL_CONNECTOR_ROUTE_PHASES = vocabulary(["scope_observation", "target_selection", "route_resolution"]);
|
|
400
|
+
var CEAL_CONNECTOR_ROUTE_CAUSES = vocabulary(["provider_throttled", "provider_unavailable", "binding_invalid", "scope_limit_exceeded"]);
|
|
401
|
+
var CEAL_WRITE_SOURCE_KINDS = vocabulary(["authenticated_registered_client", "provider_authenticated_event"]);
|
|
402
|
+
var CEAL_REFRESH_DELIVERIES = vocabulary(["initial", "recovery", "replay", "terminal_failure", "recovery_unavailable"]);
|
|
403
|
+
var CEAL_CLIENT_OPERATIONS = vocabulary(["handshake", "discover", "call", "readback"]);
|
|
404
|
+
var CEAL_WRITE_IDEMPOTENCY_POSTURES = vocabulary(["required", "optional", "not_required"]);
|
|
405
|
+
var CEAL_WRITE_PROVIDER_READBACK_POSTURES = vocabulary(["required", "best_effort", "not_available"]);
|
|
406
|
+
var CEAL_WRITE_ATTRIBUTIONS = vocabulary(["subject", "requester_event", "connector_integration"]);
|
|
407
|
+
var CEAL_WRITE_RECEIPT_PROVIDER_STATES = vocabulary(["outcome_unknown", "verified", "not_dispatched"]);
|
|
408
|
+
var CEAL_WRITE_RECEIPT_PROVIDER_READBACKS = vocabulary(["outcome_unknown", "verified", "not_applied"]);
|
|
409
|
+
var CEAL_PROTOCOL_VERSION = "1.4.0";
|
|
410
|
+
var CEAL_OPERATION_ACCESS_SCHEMA_VERSION = "ceal.operation_access.v1";
|
|
411
|
+
var CEAL_GATEWAY_CALL_RESULT_SCHEMA_VERSION = "ceal.gateway_call_result.v1";
|
|
412
|
+
var CEAL_GATEWAY_CONTINUATION_CURSOR_PHASES = vocabulary(["scan", "delivery", "terminal"]);
|
|
413
|
+
var CEAL_GATEWAY_ACTIVE_CALL_PHASES = vocabulary([
|
|
414
|
+
"audited_call_pending",
|
|
415
|
+
"continuation_read_pending",
|
|
416
|
+
"continuation_write_pending",
|
|
417
|
+
"provider_pacing_pending",
|
|
418
|
+
"corpus_replay_pending",
|
|
419
|
+
"corpus_append_pending",
|
|
420
|
+
"corpus_fence_pending",
|
|
421
|
+
"corpus_content_pending",
|
|
422
|
+
"corpus_transaction_pending"
|
|
423
|
+
]);
|
|
424
|
+
var CEAL_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "phase", "schema_version"]);
|
|
425
|
+
var CEAL_CLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["cause", ...CEAL_CONNECTOR_ROUTE_FAILURE_KEYS]);
|
|
426
|
+
var CEAL_UNCLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "error_class", "phase", "schema_version"]);
|
|
427
|
+
var CEAL_GATEWAY_AUDIT_CALL_DETAIL_SCHEMA_VERSION = "ceal.gateway_audit_call_detail.v1";
|
|
428
|
+
var CEAL_GATEWAY_ORGANIZATION_ROUTE_FACT_CALL_KEYS = Object.freeze([
|
|
429
|
+
"connector_ref",
|
|
430
|
+
"connector_revision",
|
|
431
|
+
"policy_decision_revision"
|
|
473
432
|
]);
|
|
474
433
|
|
|
475
434
|
// packages/ceal-client-protocol/src/gateway-validation-primitives.ts
|
|
@@ -609,6 +568,11 @@ function isCealJsonRecord(value) {
|
|
|
609
568
|
}
|
|
610
569
|
var CEAL_VALIDATION_STATUSES = vocabulary(["not_applicable", "valid", "invalid"]);
|
|
611
570
|
var CEAL_PARSE_STATUSES = vocabulary(["not_applicable", "parsed", "parse_failed"]);
|
|
571
|
+
function hasExactKeys(value, expected) {
|
|
572
|
+
const actual = Object.keys(value).sort();
|
|
573
|
+
const wanted = [...expected].sort();
|
|
574
|
+
return actual.length === wanted.length && actual.every((key, index) => key === wanted[index]);
|
|
575
|
+
}
|
|
612
576
|
function invalidByContext(options) {
|
|
613
577
|
if (options.forbidAuthorityKeys) invalidRequest();
|
|
614
578
|
invalidResponse();
|
|
@@ -683,51 +647,6 @@ function cut(text) {
|
|
|
683
647
|
return text.length <= REFUSAL_VALUE_MAX_CHARS ? text : `${text.slice(0, REFUSAL_VALUE_MAX_CHARS)}...(+${text.length - REFUSAL_VALUE_MAX_CHARS} chars)`;
|
|
684
648
|
}
|
|
685
649
|
|
|
686
|
-
// packages/ceal-client-protocol/src/operation-access-class.ts
|
|
687
|
-
var CEAL_OPERATION_ACCESS_CLASSES = vocabulary(["read", "write"]);
|
|
688
|
-
|
|
689
|
-
// packages/ceal-client-protocol/src/domain-vocabulary.ts
|
|
690
|
-
var CEAL_CONNECTOR_STATUSES = vocabulary(["active", "disabled", "disconnected"]);
|
|
691
|
-
var CEAL_CONNECTOR_CUSTODIES = vocabulary(["organization", "personal"]);
|
|
692
|
-
var CEAL_ACCESS_LEVELS = vocabulary(["none", "read", "read_write"]);
|
|
693
|
-
var CEAL_PERSONAL_ACCESS_CEILINGS = vocabulary(["disabled", "read", "read_write"]);
|
|
694
|
-
var CEAL_TARGET_REF_PATTERN = "^[^\\p{Cc}]+$";
|
|
695
|
-
var TARGET_REF = new RegExp(CEAL_TARGET_REF_PATTERN, "u");
|
|
696
|
-
var CEAL_TARGET_VISIBILITIES = vocabulary(["public", "private"]);
|
|
697
|
-
var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "runtime"]);
|
|
698
|
-
var CEAL_PERSISTED_PRINCIPAL_KINDS = vocabulary(["human", "service", "runtime"]);
|
|
699
|
-
var CEAL_ORGANIZATION_ROLES = vocabulary(["member", "admin"]);
|
|
700
|
-
var CEAL_CALENDAR_PERIODS = vocabulary(["day", "week", "month"]);
|
|
701
|
-
var LABEL_EXPECTATION = `public-safe display text of at most ${CEAL_PUBLIC_DISPLAY_NAME_MAX_BYTES} bytes`;
|
|
702
|
-
|
|
703
|
-
// packages/ceal-client-protocol/src/safe-json-budget.ts
|
|
704
|
-
var SAFE_JSON_MIN_BYTES_PER_NODE = 4;
|
|
705
|
-
function safeJsonNodeBudgetForBytes(maxBytes) {
|
|
706
|
-
if (!Number.isSafeInteger(maxBytes) || maxBytes < SAFE_JSON_MIN_BYTES_PER_NODE) {
|
|
707
|
-
throw new RangeError(`safe-JSON byte cap is invalid: ${refusalFact("maxBytes", maxBytes, `at least ${SAFE_JSON_MIN_BYTES_PER_NODE} bytes`)}`);
|
|
708
|
-
}
|
|
709
|
-
return Math.floor(maxBytes / SAFE_JSON_MIN_BYTES_PER_NODE);
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
// packages/ceal-client-protocol/src/protocol-bounds.ts
|
|
713
|
-
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES = 64 * 1024;
|
|
714
|
-
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_NODES = safeJsonNodeBudgetForBytes(CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES);
|
|
715
|
-
var OPERATION_SCHEMA_LITERAL_MAX_BYTES = CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES / 4;
|
|
716
|
-
var CEAL_OPERATION_JSON_SCHEMA_BUDGET = Object.freeze({
|
|
717
|
-
max_depth: 8,
|
|
718
|
-
max_properties: 96,
|
|
719
|
-
max_string_length: OPERATION_SCHEMA_LITERAL_MAX_BYTES / 4,
|
|
720
|
-
max_alternatives: 8,
|
|
721
|
-
max_enum_values: 64,
|
|
722
|
-
max_literal_bytes: OPERATION_SCHEMA_LITERAL_MAX_BYTES,
|
|
723
|
-
/** @basis our_policy -- bounds explanatory schema prose carried in one catalog. */
|
|
724
|
-
max_description_characters: 1024,
|
|
725
|
-
/** @basis our_policy -- bounds regular-expression compilation input from one schema. */
|
|
726
|
-
max_pattern_characters: 256,
|
|
727
|
-
/** @basis our_policy -- bounds traversal work inside one already byte-bounded schema literal. */
|
|
728
|
-
max_literal_nodes: 512
|
|
729
|
-
});
|
|
730
|
-
|
|
731
650
|
// packages/ceal-client-protocol/src/operation-validation-primitives.ts
|
|
732
651
|
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;
|
|
733
652
|
var CEAL_CALENDAR_DATE_PATTERN_SOURCE = "^\\d{4}-\\d{2}-\\d{2}$";
|
|
@@ -795,38 +714,366 @@ function requireIsoTimestamp(value, message) {
|
|
|
795
714
|
if (!isCealIsoTimestamp(value)) throw new TypeError(message);
|
|
796
715
|
}
|
|
797
716
|
|
|
798
|
-
// packages/ceal-client-protocol/src/
|
|
799
|
-
var
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
var
|
|
803
|
-
var
|
|
804
|
-
var
|
|
805
|
-
var
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
717
|
+
// packages/ceal-client-protocol/src/failure-guidance-table.ts
|
|
718
|
+
var CEAL_FAILURE_REDACTION_CLASSES = vocabulary(["model_safe", "withheld"]);
|
|
719
|
+
var CEAL_GATEWAY_STARTING_FAILURE_CODE = "gateway_starting";
|
|
720
|
+
var CEAL_OWNER_BOOTSTRAP_ABSENT_FAILURE_CODE = "bootstrap_absent";
|
|
721
|
+
var CEAL_OWNER_BOOTSTRAP_UNAVAILABLE_FAILURE_CODE = "bootstrap_unavailable";
|
|
722
|
+
var LOCAL_COMMAND_INVALID = "The local command or argument is invalid.";
|
|
723
|
+
var LOCAL_TRANSPORT_INCOMPLETE = "The local Ceal transport could not complete; report this as a gap.";
|
|
724
|
+
var GATEWAY_TRANSPORT_UNAVAILABLE = "The Gateway transport is unavailable; ask an administrator to restore Gateway availability.";
|
|
725
|
+
var HOST_AUTHENTICATION_UNAVAILABLE = "The Host could not authenticate this invocation; report this as a gap.";
|
|
726
|
+
var SESSION_CONTEXT_UNAVAILABLE = "The local Gateway session context is unavailable.";
|
|
727
|
+
function guidance(message, next_action, http_status, retryable = false, redaction_class = "model_safe") {
|
|
728
|
+
return Object.freeze({ message, next_action, http_status, retryable, redaction_class });
|
|
729
|
+
}
|
|
730
|
+
var CEAL_FAILURE_GUIDANCE_TABLE = Object.freeze({
|
|
731
|
+
invalid_command: guidance(LOCAL_COMMAND_INVALID, "correct_argument", 400),
|
|
732
|
+
invalid_argument: guidance(LOCAL_COMMAND_INVALID, "correct_argument", 400),
|
|
733
|
+
command_not_found: guidance(LOCAL_COMMAND_INVALID, "correct_argument", 404),
|
|
734
|
+
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),
|
|
735
|
+
local_route_unavailable: guidance("The requested local route is unavailable or its descriptor is invalid; report this as a gap.", "answer_with_gap", 404),
|
|
736
|
+
local_route_ambiguous: guidance("The requested local route is ambiguous; no local executable was selected. Report this as a gap.", "answer_with_gap", 400),
|
|
737
|
+
local_execution_unavailable: guidance("The selected local route executable is unavailable; report this as a gap.", "answer_with_gap", 500),
|
|
738
|
+
local_pickup_failed: guidance("The Host Request pickup failed; the Gateway command was not sent. Report this as a gap.", "answer_with_gap", 502),
|
|
739
|
+
invocation_authentication_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 401),
|
|
740
|
+
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),
|
|
741
|
+
gateway_origin_required: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
742
|
+
access_token_required: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
743
|
+
context_pair_required: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
744
|
+
context_not_selected: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
745
|
+
context_store_invalid: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
746
|
+
context_store_conflict: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
747
|
+
refresh_invalid: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
748
|
+
refresh_expired: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
749
|
+
refresh_replayed: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
750
|
+
refresh_revoked: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
751
|
+
refresh_recovery_unavailable: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
752
|
+
refresh_custody_invalid: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
753
|
+
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),
|
|
754
|
+
local_output_exists: guidance("The selected local output path already exists; nothing was written to it.", "choose_new_output_path", 400),
|
|
755
|
+
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),
|
|
756
|
+
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),
|
|
757
|
+
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),
|
|
758
|
+
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),
|
|
759
|
+
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),
|
|
760
|
+
response_encoding_failed: guidance("The local response could not be encoded; report this as a gap.", "answer_with_gap", 500),
|
|
761
|
+
request_timeout: guidance(GATEWAY_TRANSPORT_UNAVAILABLE, "ask_admin", 504),
|
|
762
|
+
request_failed: guidance(GATEWAY_TRANSPORT_UNAVAILABLE, "ask_admin", 502),
|
|
763
|
+
gateway_unavailable: guidance(GATEWAY_TRANSPORT_UNAVAILABLE, "ask_admin", 503, true),
|
|
764
|
+
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),
|
|
765
|
+
// `gateway_starting` is the one retryable envelope the serving gate answers on every route while starting
|
|
766
|
+
// (`cli/internal/client/gateway_startup_wait.go` re-drives it); the sentence is the client default that renders
|
|
767
|
+
// when the wait ends still unserved, moved from the `localFailureText` default branch.
|
|
768
|
+
[CEAL_GATEWAY_STARTING_FAILURE_CODE]: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 503, true),
|
|
769
|
+
gateway_internal_error: guidance("The Gateway failed without exposing internal state; report this as a gap.", "answer_with_gap", 500),
|
|
770
|
+
// The serving gate emits this code with no message; the sentence is the owner-normalized form of the
|
|
771
|
+
// `gateway_internal_error` guidance (decided with the operator 2026-10-13).
|
|
772
|
+
internal_error: guidance("The Gateway failed without exposing internal state.", "answer_with_gap", 500),
|
|
773
|
+
// Owned by the Gateway canonical adapter (`gateway/core/src/gateway-canonical-operation-http-adapter.ts`):
|
|
774
|
+
// the client variant of this sentence in `localFailureText` was retired when the client started reading this table.
|
|
775
|
+
request_body_too_large: guidance("The Gateway request body exceeds its bounded limit.", "reduce_request_size", 413),
|
|
776
|
+
// Restored verbatim from the retired `localFailureText` `unknown_outcome`
|
|
777
|
+
// case: the outcome is unknown, so a read may be retried while a write
|
|
778
|
+
// must receipt-check first. Collapsing both to "do not repeat the call"
|
|
779
|
+
// forbids the safe read retry the sentence exists to allow.
|
|
780
|
+
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),
|
|
781
|
+
// Restored verbatim from the retired `localFailureText`
|
|
782
|
+
// `refresh_unknown_outcome` case: a refresh that ends unknown leaves the
|
|
783
|
+
// session context unavailable, so the caller repairs or adopts the
|
|
784
|
+
// session instead of reporting a transport gap.
|
|
785
|
+
refresh_unknown_outcome: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
|
|
786
|
+
request_cancelled: guidance("The caller cancelled the request before it completed.", "retry", 400, true),
|
|
787
|
+
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),
|
|
788
|
+
denied: guidance("An administrator denied this device's activation request; ask an administrator whether to reconsider it.", "ask_admin", 403),
|
|
789
|
+
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),
|
|
790
|
+
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),
|
|
791
|
+
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),
|
|
792
|
+
recovery_required: guidance("The Gateway could not safely complete the activation transaction automatically; ask an administrator to recover this transaction.", "ask_admin", 500),
|
|
793
|
+
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),
|
|
794
|
+
// Activation commit codes rendered through the `localFailureText` default before phase 2b; `retryable` is moved from the
|
|
795
|
+
// emitting sites in `cli/internal/client/gateway_installation_activation.go` and `activation.go:483`.
|
|
796
|
+
activation_state_commit_failed: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500, true),
|
|
797
|
+
activation_response_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 502, true),
|
|
798
|
+
activation_rejected: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 502),
|
|
799
|
+
activation_failed: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
800
|
+
activation_binding: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
801
|
+
activation_crypto: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
802
|
+
activation_state_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
803
|
+
// Long-tail local codes rendered through the `localFailureText` default before phase 2b; sentences moved from that branch.
|
|
804
|
+
invalid_response: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
805
|
+
invalid_configuration: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
806
|
+
activation_state_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
807
|
+
activation_code_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
808
|
+
activation_code_file_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
809
|
+
activation_code_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 404),
|
|
810
|
+
bootstrap_file_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
811
|
+
bootstrap_not_active: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 409),
|
|
812
|
+
bootstrap_unauthorized: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 401),
|
|
813
|
+
// The Gateway owner-bootstrap store file was never created, so the Gateway
|
|
814
|
+
// holds no owner bootstrap to exchange (`gateway/src/host/owner-bootstrap-http-composition.ts`
|
|
815
|
+
// `absent`, stored at `gateway/src/shared/installation/owner-bootstrap-store.ts:162`);
|
|
816
|
+
// the sentence is the owner-normalized form of that store error. Non-retryable:
|
|
817
|
+
// repeating the exchange cannot create the store; only an administrator
|
|
818
|
+
// placing a bootstrap (fresh gateway setup) changes the outcome.
|
|
819
|
+
[CEAL_OWNER_BOOTSTRAP_ABSENT_FAILURE_CODE]: guidance("The Gateway holds no owner bootstrap to exchange; report this as a gap.", "answer_with_gap", 503),
|
|
820
|
+
// The Gateway owner-bootstrap store file exists but cannot be read, so the
|
|
821
|
+
// exchange cannot proceed (`gateway/src/host/owner-bootstrap-http-composition.ts`
|
|
822
|
+
// `unavailable`, stored at `gateway/src/shared/installation/owner-bootstrap-store.ts:163`);
|
|
823
|
+
// the sentence is the owner-normalized form of that store error. Retryable:
|
|
824
|
+
// unlike `absent`, a later exchange may succeed once the store is readable.
|
|
825
|
+
[CEAL_OWNER_BOOTSTRAP_UNAVAILABLE_FAILURE_CODE]: guidance("The Gateway owner bootstrap store is unavailable; retry the exchange.", "retry", 503, true),
|
|
826
|
+
context_selection_ambiguous: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
827
|
+
session_authority_unverified: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 401),
|
|
828
|
+
session_authority_mismatch: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 409),
|
|
829
|
+
local_credential_lost: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 401),
|
|
830
|
+
local_capability_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
831
|
+
host_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 503),
|
|
832
|
+
host_activation_failed: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
|
|
833
|
+
doctor_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 503),
|
|
834
|
+
admin_sign_in_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 502),
|
|
835
|
+
// `invalid_request` renders through the client default today (local encode/path faults); Gateway-answered
|
|
836
|
+
// envelopes carry the Gateway message verbatim and never read this sentence.
|
|
837
|
+
invalid_request: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
|
|
838
|
+
// Gateway canonical-route codes, sentences moved from their minting adapters; statuses moved from the
|
|
839
|
+
// `valid*Failure` decoder maps in `cli/internal/client/gateway_api.go` and `transport.go`.
|
|
840
|
+
invalid_json: guidance("The Gateway request body must be valid JSON.", "answer_with_gap", 400),
|
|
841
|
+
// Per-route variants name their route; the table holds the one stable guidance and the route's `allow`
|
|
842
|
+
// header stays the measured fact (decided with the operator 2026-10-13).
|
|
843
|
+
method_not_allowed: guidance("The HTTP method is not allowed for this Gateway route.", "answer_with_gap", 405),
|
|
844
|
+
request_target_too_large: guidance("The Gateway request target exceeds its bounded limit.", "reduce_request_size", 414),
|
|
845
|
+
route_not_found: guidance("The Gateway route is not present.", "answer_with_gap", 404),
|
|
846
|
+
artifact_unavailable: guidance("The Gateway artifact is unavailable.", "answer_with_gap", 503),
|
|
847
|
+
discovery_unavailable: guidance("The Gateway discovery manifest is unavailable.", "answer_with_gap", 503),
|
|
848
|
+
serialization_failed: guidance("The Gateway response could not be serialized.", "answer_with_gap", 500),
|
|
849
|
+
request_header_too_large: guidance("A consumed Gateway request header exceeds its bounded limit.", "answer_with_gap", 431),
|
|
850
|
+
request_work_context_missing: guidance("The Gateway command requires a Request work context.", "answer_with_gap", 400),
|
|
851
|
+
request_work_context_invalid: guidance("The Gateway Request work context encoding is invalid.", "answer_with_gap", 400),
|
|
852
|
+
request_work_context_refused: guidance("The Host-only discovery manifest takes no Request work context.", "answer_with_gap", 400),
|
|
853
|
+
// Moved from `gateway/src/operations/http-composition.ts` (`unauthorized`) and `command-runtime.ts:827`
|
|
854
|
+
// (`artifact_stale`); both travel with Gateway messages verbatim online, so these sentences are the
|
|
855
|
+
// owner fallback guidance. `unauthorized` re-authenticates through the session, hence its action.
|
|
856
|
+
unauthorized: guidance("Unauthorized.", "repair_or_adopt_session", 401),
|
|
857
|
+
artifact_stale: guidance("The requested contract artifact is not the current catalog artifact.", "answer_with_gap", 409),
|
|
858
|
+
// Host mint classifications. The CLI emits `invocation_authentication_unavailable` with the classification
|
|
859
|
+
// in its detail fact; rows without their own sentence share that code's moved sentence, while `next_action`
|
|
860
|
+
// is moved from `canonicalHostAuthenticationNextActions` (generated from `CEAL_HOST_AUTHENTICATION_POLICY`)
|
|
861
|
+
// and `retryable` from `hostAuthenticationFailureForCause` (`failure.nextAction == "retry"`). The three rows
|
|
862
|
+
// with their own sentences carry the retired policy remedies as Gateway-owned guidance. Statuses are the
|
|
863
|
+
// Gateway-owned mapping decided with the operator 2026-10-13. This covers every wire cause the maps at
|
|
864
|
+
// `host/invocation-authentication-command.ts:217-242` emit, through their policy classifications.
|
|
865
|
+
host_arguments_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 400),
|
|
866
|
+
host_configuration_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 500),
|
|
867
|
+
host_session_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 401),
|
|
868
|
+
// Moved from the retired `CEAL_HOST_AUTHENTICATION_POLICY` remedy: the
|
|
869
|
+
// missing exact-turn fact names the fresh Host turn, not a reinstall.
|
|
870
|
+
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),
|
|
871
|
+
host_invocation_context_stale: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "retry", 401, true),
|
|
872
|
+
host_invocation_context_expired: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "retry", 401, true),
|
|
873
|
+
host_source_refused: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 403),
|
|
874
|
+
host_registration_credential_refused: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "ask_admin", 403),
|
|
875
|
+
host_mint_gateway_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "ask_admin", 503),
|
|
876
|
+
host_mint_gateway_rate_limited: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 429),
|
|
877
|
+
host_mint_gateway_refused: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 502),
|
|
878
|
+
host_mint_gateway_response_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 502),
|
|
879
|
+
// Moved from the retired `CEAL_HOST_AUTHENTICATION_POLICY` remedy: the
|
|
880
|
+
// producer-frame fault names the administrator repair step.
|
|
881
|
+
host_producer_frame_invalid: guidance("Ask an administrator to inspect and repair the producer frame, then start a fresh Host turn.", "ask_admin", 400),
|
|
882
|
+
// Moved from the retired `CEAL_HOST_AUTHENTICATION_POLICY` remedy: the
|
|
883
|
+
// sandboxed Relay socket names the run-alone retry.
|
|
884
|
+
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),
|
|
885
|
+
host_relay_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 503),
|
|
886
|
+
host_mint_transient_retryable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "retry", 503, true),
|
|
887
|
+
host_mint_failed: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 500),
|
|
888
|
+
host_relay_failed: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 502),
|
|
889
|
+
host_refusal_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 500)
|
|
890
|
+
});
|
|
891
|
+
function cealFailureGuidanceForCode(code) {
|
|
892
|
+
const entry2 = CEAL_FAILURE_GUIDANCE_TABLE[code];
|
|
893
|
+
if (entry2 === void 0) throw new TypeError(`Failure guidance code is not guided: ${code}.`);
|
|
894
|
+
return entry2;
|
|
895
|
+
}
|
|
896
|
+
var FAILURE_GUIDANCE_ENTRY_KEYS = Object.freeze(["message", "next_action", "http_status", "retryable", "redaction_class"]);
|
|
811
897
|
|
|
812
|
-
// packages/ceal-client-protocol/src/
|
|
813
|
-
var
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
"
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
"
|
|
829
|
-
"
|
|
898
|
+
// packages/ceal-client-protocol/src/client-wire-contract.ts
|
|
899
|
+
var CEAL_CLIENT_WIRE_CONTRACT = Object.freeze(client_wire_contract_default);
|
|
900
|
+
var CEAL_CLIENT_DOCTOR_SCHEMA_VERSION = CEAL_CLIENT_WIRE_CONTRACT.doctor.schema_version;
|
|
901
|
+
var clientCommands = CEAL_CLIENT_WIRE_CONTRACT.client_commands;
|
|
902
|
+
var CEAL_CLIENT_DOCTOR_COMMAND = Object.freeze([...clientCommands.doctor]);
|
|
903
|
+
var CEAL_CLIENT_VERSION_COMMAND = Object.freeze([...clientCommands.version]);
|
|
904
|
+
var CEAL_CLIENT_ACCESS_GROUP_LIST_COMMAND = Object.freeze([...clientCommands.access_group_list]);
|
|
905
|
+
function deriveCealClientWireSchemaVersion(schema) {
|
|
906
|
+
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))) {
|
|
907
|
+
throw new TypeError("Client wire schema name or field set is invalid.");
|
|
908
|
+
}
|
|
909
|
+
const fields = schema.fields.filter((field) => field !== "schema_version").sort();
|
|
910
|
+
const hash8 = createHash("sha256").update(cealCanonicalJson(fields), "utf8").digest("hex").slice(0, 8);
|
|
911
|
+
return `${schema.schema_name}.v1.${hash8}`;
|
|
912
|
+
}
|
|
913
|
+
var CealClientWireContractMismatchError = class extends Error {
|
|
914
|
+
code = "contract_mismatch";
|
|
915
|
+
next_action = cealFailureGuidanceForCode("contract_mismatch").next_action;
|
|
916
|
+
constructor(expected, actual) {
|
|
917
|
+
super(`The peer wire contract ${actual} does not match this client contract ${expected}.`);
|
|
918
|
+
this.name = "CealClientWireContractMismatchError";
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
function requireCealClientWireSchemaVersion(value, expected) {
|
|
922
|
+
if (typeof value === "string" && value !== expected) throw new CealClientWireContractMismatchError(expected, value);
|
|
923
|
+
}
|
|
924
|
+
var commandResolution = CEAL_CLIENT_WIRE_CONTRACT.command_resolution;
|
|
925
|
+
var commandResolutionAuthority = commandResolution.authority;
|
|
926
|
+
var commandResolutionSemanticOptions = commandResolution.semantic_options;
|
|
927
|
+
var CEAL_GATEWAY_COMMAND_RESOLUTION_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(commandResolution);
|
|
928
|
+
var CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES = CEAL_CLIENT_WIRE_CONTRACT.encoded_json_document.max_bytes;
|
|
929
|
+
var artifact = CEAL_CLIENT_WIRE_CONTRACT.artifact_descriptor;
|
|
930
|
+
var request = CEAL_CLIENT_WIRE_CONTRACT.command_request;
|
|
931
|
+
var response = CEAL_CLIENT_WIRE_CONTRACT.command_response;
|
|
932
|
+
var CEAL_OPERATION_CONTRACT_BUNDLE_SCHEMA_VERSION = deriveCealClientWireSchemaVersion({ schema_name: artifact.operation_contract_bundle_schema_name, fields: artifact.operation_contract_bundle_fields });
|
|
933
|
+
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(artifact);
|
|
934
|
+
var CEAL_VERIFIED_ARTIFACT_BINDING_SCHEMA_VERSION = deriveCealClientWireSchemaVersion({ schema_name: artifact.binding_schema_name, fields: artifact.binding_fields });
|
|
935
|
+
var CEAL_VERIFIED_ARTIFACT_KIND = artifact.kind;
|
|
936
|
+
var CEAL_VERIFIED_ARTIFACT_BINDING_KIND = artifact.binding_kind;
|
|
937
|
+
var CEAL_VERIFIED_ARTIFACT_MEDIA_TYPE = artifact.media_type;
|
|
938
|
+
var CEAL_VERIFIED_ARTIFACT_DIGEST_LENGTH = artifact.digest.length;
|
|
939
|
+
var CEAL_VERIFIED_ARTIFACT_DIGEST_PATTERN = artifact.digest.pattern;
|
|
940
|
+
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_LENGTH = artifact.catalog_revision.length;
|
|
941
|
+
var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_PATTERN = artifact.catalog_revision.pattern;
|
|
942
|
+
var CEAL_VERIFIED_ARTIFACT_MAX_BYTES = CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES;
|
|
943
|
+
var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_MAX_BYTES = artifact.envelope_max_bytes;
|
|
944
|
+
var CEAL_COMMAND_REQUEST_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(request);
|
|
945
|
+
var CEAL_COMMAND_REQUEST_MAX_BYTES = request.absolute_transport_max_bytes;
|
|
946
|
+
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_MAX_BYTES = response.bare_document_max_bytes;
|
|
947
|
+
var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_OPTIONAL_FIELDS = Object.freeze([...response.bare_document_optional_fields]);
|
|
948
|
+
var CEAL_COMMAND_RESPONSE_DISCOVERY_COMMAND = response.discovery_command;
|
|
949
|
+
var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_SCHEMA_VERSION = deriveCealClientWireSchemaVersion({ schema_name: response.discovery_manifest_schema_name, fields: response.discovery_manifest_fields });
|
|
950
|
+
var CEAL_COMMAND_RESPONSE_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(response);
|
|
951
|
+
var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_DOCUMENT_FIELDS = Object.freeze([...response.discovery_manifest_document_fields]);
|
|
952
|
+
var CEAL_COMMAND_RESPONSE_MAX_BYTES = CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES;
|
|
953
|
+
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 });
|
|
954
|
+
var CEAL_RESOURCE_STREAM_REQUEST_FIELDS = Object.freeze([...CEAL_CLIENT_WIRE_CONTRACT.resource_stream.request_fields]);
|
|
955
|
+
|
|
956
|
+
// packages/ceal-client-protocol/src/operation-withheld-cause.ts
|
|
957
|
+
var CEAL_OPERATION_WITHHELD_CAUSES = vocabulary(["provider_permission", "access_level", "personal_authorization_required"]);
|
|
958
|
+
|
|
959
|
+
// packages/ceal-client-protocol/src/operation-contract-types.ts
|
|
960
|
+
var CEAL_JSON_SCHEMA_2020_12 = "https://json-schema.org/draft/2020-12/schema";
|
|
961
|
+
var CEAL_OPERATION_DESCRIPTOR_SCHEMA_VERSION = "ceal.operation_descriptor.v1";
|
|
962
|
+
var CEAL_OPERATION_ERROR_SCHEMA_VERSION = "ceal.operation_error.v1";
|
|
963
|
+
var CEAL_ARTIFACT_STAGE_TERMINALS = vocabulary(["chunk_accepted", "artifact_ready", "idempotency_replayed"]);
|
|
964
|
+
var CEAL_RETRY_AFTER_MAX_MS = 60 * 60 * 1e3;
|
|
965
|
+
var CEAL_JSON_SCHEMA_TYPES = vocabulary(["object", "array", "string", "integer", "number", "boolean", "null"]);
|
|
966
|
+
var OBSERVATION_POSTURES = vocabulary(["direct_readback", "approved_no_readback", "operator_mediated", "excluded"]);
|
|
967
|
+
var EFFECT_DOMAINS = vocabulary(["none", "gateway_state", "provider", "external_world"]);
|
|
968
|
+
var SAFETY_CLASSES = vocabulary(["routine", "sensitive", "high_impact"]);
|
|
969
|
+
var IDEMPOTENCY_CLASSES = vocabulary(["idempotent", "conditionally_idempotent", "non_idempotent"]);
|
|
970
|
+
var REVERSIBILITY_CLASSES = vocabulary(["none", "reversible", "compensatable", "irreversible"]);
|
|
971
|
+
var COMPENSATION_OWNERS = vocabulary(["none", "connector", "operator"]);
|
|
972
|
+
var CANCELLATION_BOUNDARIES = vocabulary(["before_admission", "before_handoff", "after_handoff_reconcile"]);
|
|
973
|
+
var HANDOFF_CLASSES = vocabulary(["not_applicable", "submission", "effect"]);
|
|
974
|
+
var AUTHORITATIVE_READBACK_CLASSES = vocabulary(["required", "unavailable"]);
|
|
975
|
+
var PROVIDER_READBACK_CLASSES = vocabulary(["required", "not_available", "not_applicable"]);
|
|
976
|
+
var RECONCILIATION_CLASSES = vocabulary(["none", "automatic", "operator"]);
|
|
977
|
+
var NO_READBACK_APPROVAL_CLASSES = vocabulary(["not_applicable", "explicit", "operator"]);
|
|
978
|
+
var REPLAY_PREREQUISITES = vocabulary(["never", "fresh_admission", "reconciled_non_application", "operator_approval"]);
|
|
979
|
+
var UNKNOWN_OUTCOMES = vocabulary(["reconcile", "operator_action", "excluded"]);
|
|
980
|
+
var RECOVERY_OWNERS = vocabulary(["gateway", "connector", "operator"]);
|
|
981
|
+
var CEAL_OPERATION_ADMISSION_STATES = vocabulary(["proposed", "refused", "admitted"]);
|
|
982
|
+
var CEAL_OPERATION_EXECUTION_STATES = vocabulary(["not_started", "started", "completed"]);
|
|
983
|
+
var CEAL_OPERATION_PROVIDER_HANDOFF_STATES = vocabulary(["not_offered", "offered", "accepted", "rejected", "unknown"]);
|
|
984
|
+
var CEAL_OPERATION_EFFECT_STATES = vocabulary(["none", "applied", "failed", "unknown", "compensated"]);
|
|
985
|
+
var CEAL_OPERATION_OBSERVATION_STATES = vocabulary(["not_started", "not_required", "pending", "observed", "unavailable"]);
|
|
986
|
+
var CEAL_OPERATION_RECOVERY_KINDS = vocabulary(["none", "reconcile", "compensate", "operator_action"]);
|
|
987
|
+
var CEAL_OPERATION_RECOVERY_STATUSES = vocabulary(["not_started", "active", "completed"]);
|
|
988
|
+
var CEAL_OPERATION_PROVIDER_READBACK_AVAILABILITY = vocabulary(["not_started", "not_applicable", "not_available", "pending", "available"]);
|
|
989
|
+
var CEAL_OPERATION_OFFERED_HANDOFF_STATES = vocabulary(["accepted", "rejected", "unknown"]);
|
|
990
|
+
var CEAL_OPERATION_RECEIPT_AUTHORITY_KEYS = Object.freeze(["instance_ref", "access_group_ref", "principal_ref", "execution_subject_ref", "authority_revision_ref"]);
|
|
991
|
+
var CEAL_OPERATION_APPROVAL_DISPOSITIONS = vocabulary([
|
|
992
|
+
"approval_not_required_by_policy",
|
|
993
|
+
"interactive_pending",
|
|
994
|
+
"interactive_granted",
|
|
995
|
+
"interactive_denied",
|
|
996
|
+
"interactive_expired",
|
|
997
|
+
"interactive_unavailable"
|
|
998
|
+
]);
|
|
999
|
+
|
|
1000
|
+
// packages/ceal-client-protocol/src/operation-access-class.ts
|
|
1001
|
+
var CEAL_OPERATION_ACCESS_CLASSES = vocabulary(["read", "write"]);
|
|
1002
|
+
|
|
1003
|
+
// packages/ceal-client-protocol/src/domain-vocabulary.ts
|
|
1004
|
+
var CEAL_CONNECTOR_STATUSES = vocabulary(["active", "disabled", "disconnected"]);
|
|
1005
|
+
var CEAL_CONNECTOR_CUSTODIES = vocabulary(["organization", "personal"]);
|
|
1006
|
+
var CEAL_ACCESS_LEVELS = vocabulary(["none", "read", "read_write"]);
|
|
1007
|
+
var CEAL_PERSONAL_ACCESS_CEILINGS = vocabulary(["disabled", "read", "read_write"]);
|
|
1008
|
+
var CEAL_TARGET_REF_PATTERN = "^[^\\p{Cc}]+$";
|
|
1009
|
+
var TARGET_REF = new RegExp(CEAL_TARGET_REF_PATTERN, "u");
|
|
1010
|
+
var CEAL_TARGET_VISIBILITIES = vocabulary(["public", "private"]);
|
|
1011
|
+
var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "runtime"]);
|
|
1012
|
+
var CEAL_PERSISTED_PRINCIPAL_KINDS = vocabulary(["human", "service", "runtime"]);
|
|
1013
|
+
var CEAL_ORGANIZATION_ROLES = vocabulary(["member", "admin"]);
|
|
1014
|
+
var CEAL_CALENDAR_PERIODS = vocabulary(["day", "week", "month"]);
|
|
1015
|
+
var LABEL_EXPECTATION = `public-safe display text of at most ${CEAL_PUBLIC_DISPLAY_NAME_MAX_BYTES} bytes`;
|
|
1016
|
+
|
|
1017
|
+
// packages/ceal-client-protocol/src/safe-json-budget.ts
|
|
1018
|
+
var SAFE_JSON_MIN_BYTES_PER_NODE = 4;
|
|
1019
|
+
function safeJsonNodeBudgetForBytes(maxBytes) {
|
|
1020
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < SAFE_JSON_MIN_BYTES_PER_NODE) {
|
|
1021
|
+
throw new RangeError(`safe-JSON byte cap is invalid: ${refusalFact("maxBytes", maxBytes, `at least ${SAFE_JSON_MIN_BYTES_PER_NODE} bytes`)}`);
|
|
1022
|
+
}
|
|
1023
|
+
return Math.floor(maxBytes / SAFE_JSON_MIN_BYTES_PER_NODE);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// packages/ceal-client-protocol/src/protocol-bounds.ts
|
|
1027
|
+
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES = 64 * 1024;
|
|
1028
|
+
var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_NODES = safeJsonNodeBudgetForBytes(CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES);
|
|
1029
|
+
var OPERATION_SCHEMA_LITERAL_MAX_BYTES = CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES / 4;
|
|
1030
|
+
var CEAL_OPERATION_JSON_SCHEMA_BUDGET = Object.freeze({
|
|
1031
|
+
max_depth: 8,
|
|
1032
|
+
max_properties: 96,
|
|
1033
|
+
max_string_length: OPERATION_SCHEMA_LITERAL_MAX_BYTES / 4,
|
|
1034
|
+
max_alternatives: 8,
|
|
1035
|
+
max_enum_values: 64,
|
|
1036
|
+
max_literal_bytes: OPERATION_SCHEMA_LITERAL_MAX_BYTES,
|
|
1037
|
+
/** @basis our_policy -- bounds explanatory schema prose carried in one catalog. */
|
|
1038
|
+
max_description_characters: 1024,
|
|
1039
|
+
/** @basis our_policy -- bounds regular-expression compilation input from one schema. */
|
|
1040
|
+
max_pattern_characters: 256,
|
|
1041
|
+
/** @basis our_policy -- bounds traversal work inside one already byte-bounded schema literal. */
|
|
1042
|
+
max_literal_nodes: 512
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
// packages/ceal-client-protocol/src/operation-json-schema.ts
|
|
1046
|
+
var JSON_DESCRIPTION_CONTROL_CHARACTER = new RegExp("\\p{Cc}", "u");
|
|
1047
|
+
|
|
1048
|
+
// packages/ceal-client-protocol/src/operation-contract-validation.ts
|
|
1049
|
+
var CEAL_OPERATION_AUTHORITY_POLICY_KEYS = Object.freeze(["access_group", "instance_binding", "target", "admission_recheck"]);
|
|
1050
|
+
var MAX_OPERATION_INPUT_RESOLVER_BINDINGS = 96;
|
|
1051
|
+
var CEAL_OPERATION_DESCRIPTOR_REQUIRED_ENVELOPE_KEYS = Object.freeze(["schema_version", "id", "input_contract", "result_contract", "policy"]);
|
|
1052
|
+
var CEAL_OPERATION_DESCRIPTOR_OPTIONAL_ENVELOPE_KEYS = Object.freeze(["input_resolution", "summary"]);
|
|
1053
|
+
|
|
1054
|
+
// packages/ceal-client-protocol/src/operation-catalog.ts
|
|
1055
|
+
var CEAL_OPERATION_CATALOG_SCHEMA_VERSION = "ceal.catalog.v3";
|
|
1056
|
+
var CLIENT_AUDIENCES = Object.freeze(["ceal", "cealctl", "agent"]);
|
|
1057
|
+
var CLIENT_AUDIENCE_SET = new Set(CLIENT_AUDIENCES);
|
|
1058
|
+
|
|
1059
|
+
// packages/ceal-client-protocol/src/schema-literals.ts
|
|
1060
|
+
var CEAL_SCHEMA_LITERALS = [
|
|
1061
|
+
"ceal.activation_transport.v1",
|
|
1062
|
+
CEAL_OPERATION_ACCESS_SCHEMA_VERSION,
|
|
1063
|
+
CEAL_OPERATION_CATALOG_SCHEMA_VERSION,
|
|
1064
|
+
"ceal.client_refresh_request.v1",
|
|
1065
|
+
"ceal.client_refresh_result.v1",
|
|
1066
|
+
"ceal.client_revoke_request.v1",
|
|
1067
|
+
"ceal.client_revoke_result.v1",
|
|
1068
|
+
CEAL_COMMAND_REQUEST_SCHEMA_VERSION,
|
|
1069
|
+
"ceal.device_enrollment_challenge.v1",
|
|
1070
|
+
"ceal.device_enrollment_challenge_request.v1",
|
|
1071
|
+
"ceal.device_enrollment_hpke_aad.v1",
|
|
1072
|
+
"ceal.device_enrollment_hpke_info.v1",
|
|
1073
|
+
"ceal.device_enrollment_poll.v1",
|
|
1074
|
+
"ceal.device_enrollment_poll_result.v1",
|
|
1075
|
+
"ceal.device_enrollment_proof.v1",
|
|
1076
|
+
"ceal.device_enrollment_start.v1",
|
|
830
1077
|
"ceal.device_enrollment_start_result.v1",
|
|
831
1078
|
"ceal.effect_approval.v1",
|
|
832
1079
|
"ceal.effect_approval_confirmation.v1",
|
|
@@ -836,12 +1083,12 @@ var CEAL_SCHEMA_LITERALS = [
|
|
|
836
1083
|
"ceal.enrollment_result.v1",
|
|
837
1084
|
"ceal.first_activation_causal_join.v1",
|
|
838
1085
|
"ceal.gateway_announcement_policy.v1",
|
|
839
|
-
|
|
1086
|
+
CEAL_GATEWAY_AUDIT_CALL_DETAIL_SCHEMA_VERSION,
|
|
840
1087
|
"ceal.gateway_audit_event.v1",
|
|
841
1088
|
"ceal.gateway_audit_readback.v1",
|
|
842
1089
|
"ceal.gateway_authorization_snapshot.v1",
|
|
843
1090
|
"ceal.gateway_cache_origin.v1",
|
|
844
|
-
|
|
1091
|
+
CEAL_GATEWAY_CALL_RESULT_SCHEMA_VERSION,
|
|
845
1092
|
CEAL_GATEWAY_COMMAND_RESOLUTION_SCHEMA_VERSION,
|
|
846
1093
|
"ceal.gateway_connector_route_failure.v1",
|
|
847
1094
|
"ceal.gateway_discovery.v1",
|
|
@@ -866,7 +1113,7 @@ var CEAL_SCHEMA_LITERALS = [
|
|
|
866
1113
|
"ceal.host_observation.v1",
|
|
867
1114
|
"ceal.host_registration_request.v1",
|
|
868
1115
|
"ceal.host_registration_result.v1",
|
|
869
|
-
|
|
1116
|
+
CEAL_OPERATION_DESCRIPTOR_SCHEMA_VERSION,
|
|
870
1117
|
"ceal.operation_error.v1",
|
|
871
1118
|
"ceal.operation_failure.v1",
|
|
872
1119
|
"ceal.operation_failure.compact.v1",
|
|
@@ -975,11 +1222,22 @@ var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_JSON_SCHEMA = Object.freeze({
|
|
|
975
1222
|
var CEAL_OPAQUE_HANDLE_KINDS = vocabulary(["target", "message", "thread", "artifact", "document", "block", "object"]);
|
|
976
1223
|
var CEAL_OPAQUE_REF_KINDS = vocabulary([...CEAL_OPAQUE_HANDLE_KINDS, "result", "operation-receipt", "reply-receipt", "field", "notion-data-source"]);
|
|
977
1224
|
|
|
1225
|
+
// packages/ceal-client-protocol/src/client-environment-generated.ts
|
|
1226
|
+
var CEAL_CLIENT_CONTEXT_OVERRIDE_ENVIRONMENTS = Object.freeze(["CEAL_GATEWAY_ORIGIN", "CEAL_GATEWAY_TRANSPORT_ORIGIN", "CEAL_ACCESS_TOKEN", "CEAL_CONTEXT_STORE_PATH"]);
|
|
1227
|
+
|
|
1228
|
+
// packages/ceal-client-protocol/src/client-process-contract.ts
|
|
1229
|
+
var CEAL_CLIENT_VERSION_SCHEMA_SUFFIX = ".version.v1";
|
|
1230
|
+
var CEAL_CLIENT_VERSION_SCHEMA_VERSION = `ceal${CEAL_CLIENT_VERSION_SCHEMA_SUFFIX}`;
|
|
1231
|
+
|
|
978
1232
|
// packages/ceal-client-protocol/src/bare-document.ts
|
|
979
1233
|
var REQUIRED_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_fields;
|
|
980
1234
|
var OPTIONAL_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_optional_fields;
|
|
981
1235
|
|
|
1236
|
+
// packages/ceal-client-protocol/src/actor-kind.ts
|
|
1237
|
+
var CEAL_ACTOR_KINDS = vocabulary(["human", "bot", "app", "unknown"]);
|
|
1238
|
+
|
|
982
1239
|
// packages/ceal-client-protocol/src/resource-row.ts
|
|
1240
|
+
var CEAL_RESOURCE_ROW_TITLE_MATCH_DISPOSITIONS = vocabulary(["exact", "not_exact", "unknown"]);
|
|
983
1241
|
var CEAL_RESOURCE_SEARCH_ORDERS = vocabulary(["relevance", "modified", "viewed", "created"]);
|
|
984
1242
|
var CEAL_RESOURCE_SEARCH_DIRECTIONS = vocabulary(["ascending", "descending"]);
|
|
985
1243
|
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"]);
|
|
@@ -992,6 +1250,8 @@ var CEAL_ACTIVITY_TIMESTAMP_BASES = vocabulary([
|
|
|
992
1250
|
"resource_created",
|
|
993
1251
|
"review_submitted",
|
|
994
1252
|
"comment_created",
|
|
1253
|
+
"reaction_created",
|
|
1254
|
+
"ceal_write_completed",
|
|
995
1255
|
"event_start",
|
|
996
1256
|
"all_day_start",
|
|
997
1257
|
"file_created",
|
|
@@ -1026,6 +1286,10 @@ var CEAL_ACTIVITY_EVENT_KINDS = vocabulary([
|
|
|
1026
1286
|
"github.review.submitted",
|
|
1027
1287
|
"github.issue_comment.created",
|
|
1028
1288
|
"github.review_comment.created",
|
|
1289
|
+
"github.reaction.created",
|
|
1290
|
+
"github.reaction.deleted",
|
|
1291
|
+
"github.pull_request.review_requested",
|
|
1292
|
+
"github.pull_request.review_request_removed",
|
|
1029
1293
|
"calendar.organized",
|
|
1030
1294
|
"calendar.accepted_invite",
|
|
1031
1295
|
"calendar.recorded",
|
|
@@ -1047,6 +1311,10 @@ var CEAL_ACTIVITY_EVENT_TIMESTAMP_BASIS_MAP = Object.freeze({
|
|
|
1047
1311
|
"github.pull_request.opened": "resource_created",
|
|
1048
1312
|
"github.issue.edited": "resource_modified",
|
|
1049
1313
|
"github.pull_request.edited": "resource_modified",
|
|
1314
|
+
"github.reaction.created": "reaction_created",
|
|
1315
|
+
"github.reaction.deleted": "ceal_write_completed",
|
|
1316
|
+
"github.pull_request.review_requested": "ceal_write_completed",
|
|
1317
|
+
"github.pull_request.review_request_removed": "ceal_write_completed",
|
|
1050
1318
|
"calendar.organized": "resource_created",
|
|
1051
1319
|
"drive.file.created": "file_created",
|
|
1052
1320
|
"drive.file.modified": "file_modified_by_me",
|
|
@@ -1063,12 +1331,14 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
|
|
|
1063
1331
|
next: { required: true, timestamp: false },
|
|
1064
1332
|
kind: { required: true, timestamp: false },
|
|
1065
1333
|
matched_kinds: { required: false, timestamp: false },
|
|
1334
|
+
title_match: { required: false, timestamp: false },
|
|
1066
1335
|
title: { required: true, timestamp: false },
|
|
1067
1336
|
container_label: { required: true, timestamp: false },
|
|
1068
1337
|
container_ref: { required: false, timestamp: false },
|
|
1069
1338
|
source_url: { required: false, timestamp: false },
|
|
1070
1339
|
source: { required: true, timestamp: false },
|
|
1071
1340
|
owner: { required: false, timestamp: false },
|
|
1341
|
+
actor_kind: { required: false, timestamp: false },
|
|
1072
1342
|
counterparts: { required: false, timestamp: false },
|
|
1073
1343
|
attachments: { required: false, timestamp: false },
|
|
1074
1344
|
created_at: { required: false, timestamp: true },
|
|
@@ -1076,9 +1346,18 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
|
|
|
1076
1346
|
modified_at: { required: false, timestamp: true },
|
|
1077
1347
|
modified_at_local: { required: false, timestamp: true },
|
|
1078
1348
|
viewed_by_me_at: { required: false, timestamp: true },
|
|
1349
|
+
modified_by_me_at: { required: false, timestamp: true },
|
|
1079
1350
|
own_ceal_write: { required: false, timestamp: false },
|
|
1080
1351
|
owned_by_me: { required: false, timestamp: false },
|
|
1352
|
+
mime_type: { required: false, timestamp: false },
|
|
1353
|
+
size_bytes: { required: false, timestamp: false },
|
|
1354
|
+
version: { required: false, timestamp: false },
|
|
1355
|
+
trashed: { required: false, timestamp: false },
|
|
1081
1356
|
visibility: { required: false, timestamp: false },
|
|
1357
|
+
archived: { required: false, timestamp: false },
|
|
1358
|
+
tag: { required: false, timestamp: false },
|
|
1359
|
+
labels: { required: false, timestamp: false },
|
|
1360
|
+
labels_incomplete: { required: false, timestamp: false },
|
|
1082
1361
|
start: { required: false, timestamp: false },
|
|
1083
1362
|
end: { required: false, timestamp: false },
|
|
1084
1363
|
status: { required: false, timestamp: false },
|
|
@@ -1151,18 +1430,29 @@ var CEAL_REFRESH_FAILURE_CODES = vocabulary([
|
|
|
1151
1430
|
// The serving gate answers this while the Gateway is still starting and the
|
|
1152
1431
|
// refresh was never attempted; the same attempt stays retryable. It is never
|
|
1153
1432
|
// stored by the session owner, only answered at the serving edge.
|
|
1154
|
-
|
|
1433
|
+
CEAL_GATEWAY_STARTING_FAILURE_CODE
|
|
1434
|
+
]);
|
|
1435
|
+
var CEAL_CLIENT_REVOKE_FAILURE_CODES = vocabulary([
|
|
1436
|
+
"refresh_invalid",
|
|
1437
|
+
"refresh_expired",
|
|
1438
|
+
"refresh_replayed",
|
|
1439
|
+
"refresh_revoked"
|
|
1155
1440
|
]);
|
|
1441
|
+
var CEAL_PERSONAL_CLIENT_ACCESS_TOKEN_PREFIX = "ceal_personal_";
|
|
1442
|
+
var CEAL_PERSONAL_CLIENT_REFRESH_TOKEN_PREFIX = "ceal_refresh_";
|
|
1443
|
+
var PERSONAL_CLIENT_TOKEN_SUFFIX_PATTERN = "[A-Za-z0-9_-]{43}";
|
|
1444
|
+
var CEAL_PERSONAL_CLIENT_ACCESS_TOKEN = new RegExp(`^${CEAL_PERSONAL_CLIENT_ACCESS_TOKEN_PREFIX}${PERSONAL_CLIENT_TOKEN_SUFFIX_PATTERN}$`, "u");
|
|
1445
|
+
var CEAL_PERSONAL_CLIENT_REFRESH_TOKEN = new RegExp(`^${CEAL_PERSONAL_CLIENT_REFRESH_TOKEN_PREFIX}${PERSONAL_CLIENT_TOKEN_SUFFIX_PATTERN}$`, "u");
|
|
1156
1446
|
|
|
1157
1447
|
// packages/ceal-client-protocol/src/request-validation-primitives.ts
|
|
1158
|
-
import { createHash } from "node:crypto";
|
|
1448
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1159
1449
|
var DIGEST = /^[a-f0-9]{64}$/u;
|
|
1160
1450
|
var CEAL_SHA256_DIGEST_PATTERN = DIGEST.source;
|
|
1161
1451
|
function isCealDigest(value) {
|
|
1162
1452
|
return typeof value === "string" && DIGEST.test(value);
|
|
1163
1453
|
}
|
|
1164
1454
|
function cealRequestSha256(value) {
|
|
1165
|
-
return
|
|
1455
|
+
return createHash2("sha256").update(cealCanonicalJson(value), "utf8").digest("hex");
|
|
1166
1456
|
}
|
|
1167
1457
|
function operationRecord(value, message) {
|
|
1168
1458
|
requireOperationRecord(value, message);
|
|
@@ -1178,40 +1468,71 @@ function requirePrefixedRef(value, prefix, message) {
|
|
|
1178
1468
|
// packages/ceal-client-protocol/src/request-authority.ts
|
|
1179
1469
|
var CEAL_REQUEST_SCHEMA_VERSION = "ceal.request.v1";
|
|
1180
1470
|
var CEAL_REQUEST_ARTIFACT_OWNERS = vocabulary(["gateway", "host", "connector"]);
|
|
1181
|
-
var
|
|
1471
|
+
var CEAL_HOST_INVOCATION_IDENTITY_KEYS = vocabulary(["host_session_ref", "host_turn_ref", "host_source_ref"]);
|
|
1182
1472
|
var CEAL_REQUEST_ARTIFACT_RETENTION_CLASSES = vocabulary(["request", "source", "result"]);
|
|
1183
1473
|
var CANCELLATION_STATES = vocabulary(["requested", "cancelled"]);
|
|
1184
|
-
var CEAL_REQUEST_KEYS = Object.freeze(["schema_version", "ceal_request_ref", "created_at", "authority"
|
|
1474
|
+
var CEAL_REQUEST_KEYS = Object.freeze(["schema_version", "ceal_request_ref", "created_at", "authority"]);
|
|
1475
|
+
var CEAL_REQUEST_COMMON_PROPERTIES = Object.freeze({
|
|
1476
|
+
schema_version: { type: "string", const: CEAL_REQUEST_SCHEMA_VERSION, maxLength: 32 },
|
|
1477
|
+
ceal_request_ref: { type: "string", minLength: 1, maxLength: 128, pattern: "^ceal-request:[A-Za-z0-9][A-Za-z0-9._:-]{0,114}$" },
|
|
1478
|
+
created_at: { type: "string", minLength: 1, maxLength: 64 },
|
|
1479
|
+
authority: {
|
|
1480
|
+
type: "object",
|
|
1481
|
+
maxProperties: 5,
|
|
1482
|
+
additionalProperties: false,
|
|
1483
|
+
properties: {
|
|
1484
|
+
instance_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1485
|
+
access_group_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1486
|
+
principal_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1487
|
+
execution_subject_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1488
|
+
authority_revision_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source }
|
|
1489
|
+
},
|
|
1490
|
+
required: ["instance_ref", "access_group_ref", "principal_ref", "execution_subject_ref", "authority_revision_ref"]
|
|
1491
|
+
}
|
|
1492
|
+
});
|
|
1493
|
+
var CEAL_REQUEST_HOST_PROPERTIES = Object.freeze({
|
|
1494
|
+
prompt_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1495
|
+
host_session_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1496
|
+
host_turn_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1497
|
+
host_input_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1498
|
+
host_source_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source }
|
|
1499
|
+
});
|
|
1500
|
+
var CEAL_REQUEST_CALLER_PROPERTIES = Object.freeze({
|
|
1501
|
+
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." },
|
|
1502
|
+
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." }
|
|
1503
|
+
});
|
|
1504
|
+
var CEAL_REQUEST_OPTIONAL_PROPERTIES = Object.freeze({
|
|
1505
|
+
requester_timezone: { type: "string", minLength: 1, maxLength: 128, description: "IANA timezone observed for the requester on this Request." },
|
|
1506
|
+
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." }
|
|
1507
|
+
});
|
|
1508
|
+
var CEAL_REQUEST_HOST_REQUIRED_KEYS = Object.freeze([...CEAL_REQUEST_KEYS, ...CEAL_HOST_INVOCATION_IDENTITY_KEYS, "prompt_ref"]);
|
|
1509
|
+
var CEAL_REQUEST_CALLER_REQUIRED_KEYS = Object.freeze([...CEAL_REQUEST_KEYS, "caller_call_group_ref", "caller_call_input_sha256"]);
|
|
1185
1510
|
var CEAL_REQUEST_JSON_SCHEMA = {
|
|
1186
1511
|
type: "object",
|
|
1187
|
-
|
|
1512
|
+
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.",
|
|
1513
|
+
maxProperties: 13,
|
|
1188
1514
|
additionalProperties: false,
|
|
1189
1515
|
properties: {
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1516
|
+
...CEAL_REQUEST_COMMON_PROPERTIES,
|
|
1517
|
+
...CEAL_REQUEST_HOST_PROPERTIES,
|
|
1518
|
+
...CEAL_REQUEST_CALLER_PROPERTIES,
|
|
1519
|
+
...CEAL_REQUEST_OPTIONAL_PROPERTIES
|
|
1520
|
+
},
|
|
1521
|
+
required: CEAL_REQUEST_KEYS,
|
|
1522
|
+
anyOf: [
|
|
1523
|
+
{
|
|
1194
1524
|
type: "object",
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
instance_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1199
|
-
access_group_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1200
|
-
principal_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1201
|
-
execution_subject_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
|
|
1202
|
-
authority_revision_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source }
|
|
1203
|
-
},
|
|
1204
|
-
required: ["instance_ref", "access_group_ref", "principal_ref", "execution_subject_ref", "authority_revision_ref"]
|
|
1525
|
+
required: CEAL_REQUEST_HOST_REQUIRED_KEYS,
|
|
1526
|
+
properties: { ...CEAL_REQUEST_COMMON_PROPERTIES, ...CEAL_REQUEST_HOST_PROPERTIES, ...CEAL_REQUEST_OPTIONAL_PROPERTIES },
|
|
1527
|
+
additionalProperties: false
|
|
1205
1528
|
},
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
},
|
|
1214
|
-
required: CEAL_REQUEST_KEYS
|
|
1529
|
+
{
|
|
1530
|
+
type: "object",
|
|
1531
|
+
required: CEAL_REQUEST_CALLER_REQUIRED_KEYS,
|
|
1532
|
+
properties: { ...CEAL_REQUEST_COMMON_PROPERTIES, ...CEAL_REQUEST_CALLER_PROPERTIES, ...CEAL_REQUEST_OPTIONAL_PROPERTIES },
|
|
1533
|
+
additionalProperties: false
|
|
1534
|
+
}
|
|
1535
|
+
]
|
|
1215
1536
|
};
|
|
1216
1537
|
|
|
1217
1538
|
// packages/ceal-client-protocol/src/request-operands.ts
|
|
@@ -1322,10 +1643,6 @@ var CEAL_REQUEST_RUN_BATCH_JSON_SCHEMA = {
|
|
|
1322
1643
|
};
|
|
1323
1644
|
|
|
1324
1645
|
// packages/ceal-client-protocol/src/host-observation.ts
|
|
1325
|
-
var CEAL_HOST_INPUT_KINDS = vocabulary(["human", "host_automatic"]);
|
|
1326
|
-
function isCealHostInputKind(value) {
|
|
1327
|
-
return typeof value === "string" && CEAL_HOST_INPUT_KINDS.some((kind) => kind === value);
|
|
1328
|
-
}
|
|
1329
1646
|
var CEAL_HOST_OBSERVATION_REF_PREFIX = "host-observation:";
|
|
1330
1647
|
var HOST_TOKEN_OBSERVATION_BREAKDOWN_KEYS = vocabulary(["cached_input", "cache_write_input", "reasoning_output"]);
|
|
1331
1648
|
function validateCealHostObservationDraft(value) {
|
|
@@ -1336,7 +1653,7 @@ function validateCealHostObservationDraft(value) {
|
|
|
1336
1653
|
assertHostObservationValue(record6.value, record6.observation_kind);
|
|
1337
1654
|
}
|
|
1338
1655
|
var CEAL_REQUEST_MAX_OBSERVATION_VALUE_BYTES = 64 * 1024;
|
|
1339
|
-
var CEAL_HOST_OBSERVATION_KINDS = vocabulary(["prompt", "session", "turn", "model", "token", "
|
|
1656
|
+
var CEAL_HOST_OBSERVATION_KINDS = vocabulary(["prompt", "session", "turn", "model", "token", "timing"]);
|
|
1340
1657
|
function isCealHostPromptObservationValue(value) {
|
|
1341
1658
|
return isOperationRecord(value) && Object.keys(value).length === 1 && isCealDigest(value.body_sha256);
|
|
1342
1659
|
}
|
|
@@ -1363,10 +1680,6 @@ function inspectHostKeys(value) {
|
|
|
1363
1680
|
// packages/ceal-client-protocol/src/host-prompt-ref.ts
|
|
1364
1681
|
var CEAL_HOST_PROMPT_DIGEST_PATTERN = "^[a-f0-9]{64}$";
|
|
1365
1682
|
var PROMPT_DIGEST = new RegExp(CEAL_HOST_PROMPT_DIGEST_PATTERN, "u");
|
|
1366
|
-
function hostProducerPromptRef(promptSha256) {
|
|
1367
|
-
if (!PROMPT_DIGEST.test(promptSha256)) throw new TypeError("Canonical Host prompt digest is invalid.");
|
|
1368
|
-
return `ceal-host-prompt:${promptSha256}`;
|
|
1369
|
-
}
|
|
1370
1683
|
|
|
1371
1684
|
// packages/ceal-client-protocol/src/request-effect-approval.ts
|
|
1372
1685
|
var CEAL_EFFECT_APPROVAL_DECISION_CHECK_PHRASES = Object.freeze([
|
|
@@ -1459,18 +1772,115 @@ var CEAL_REQUEST_GRAPH_NODE_PREPARE_READINESS_JSON_SCHEMA = {
|
|
|
1459
1772
|
};
|
|
1460
1773
|
|
|
1461
1774
|
// packages/ceal-client-protocol/src/host-invocation-authentication.ts
|
|
1462
|
-
var
|
|
1775
|
+
var hostInvocationAuthenticationContract = CEAL_CLIENT_WIRE_CONTRACT.host_invocation_authentication;
|
|
1776
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_REQUEST_SCHEMA = deriveCealClientWireSchemaVersion({ schema_name: hostInvocationAuthenticationContract.request_schema_name, fields: hostInvocationAuthenticationContract.request_fields });
|
|
1777
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_REQUEST_FIELDS = Object.freeze([...hostInvocationAuthenticationContract.request_fields]);
|
|
1778
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_SCHEMA = deriveCealClientWireSchemaVersion({ schema_name: hostInvocationAuthenticationContract.result_schema_name, fields: hostInvocationAuthenticationContract.result_fields });
|
|
1779
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_FIELDS = Object.freeze([...hostInvocationAuthenticationContract.result_fields]);
|
|
1780
|
+
var CEAL_HOST_AUTHENTICATION_BUDGET_PATTERN_SOURCE = "^(?:0|[1-9][0-9]{0,8})$";
|
|
1781
|
+
var CEAL_HOST_AUTHENTICATION_BUDGET_PATTERN = new RegExp(CEAL_HOST_AUTHENTICATION_BUDGET_PATTERN_SOURCE, "u");
|
|
1463
1782
|
var CEAL_HOST_INVOCATION_CONTEXT_PATH = "/api/ceal/v2/host-invocation-context";
|
|
1464
|
-
var
|
|
1465
|
-
var
|
|
1466
|
-
var
|
|
1467
|
-
var
|
|
1468
|
-
var
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1783
|
+
var CEAL_HOST_INVOCATION_CONTEXT_REQUEST_SCHEMA = "ceal.gateway_host_invocation_context_request.v2";
|
|
1784
|
+
var CEAL_HOST_INVOCATION_CONTEXT_REQUEST_MAX_BYTES = 16 * 1024;
|
|
1785
|
+
var CEAL_HOST_INVOCATION_HUMAN_MESSAGE_WINDOW_MAX_BYTES = 2 * 1024;
|
|
1786
|
+
var CEAL_HOST_INVOCATION_HUMAN_MESSAGE_WINDOW_MAX_MESSAGES = 32;
|
|
1787
|
+
var HOST_CONTEXT_EXPIRY_MAX_CHARS = 64;
|
|
1788
|
+
var CEAL_HOST_INVOCATION_CONTEXT_VISIBLE_OUTPUT_MAX_BYTES = CEAL_CANONICAL_HTTP_MAX_RESPONSE_BYTES;
|
|
1789
|
+
var CEAL_HOST_CONTEXT_TOKEN_PREFIX = "ceal_context_";
|
|
1790
|
+
function decodeCealHostInvocationContextRequest(value) {
|
|
1791
|
+
if (!isCealJsonRecord(value)) throw invalidInvocationContextRequest();
|
|
1792
|
+
requireCealClientWireSchemaVersion(value.schema_version, CEAL_HOST_INVOCATION_CONTEXT_REQUEST_SCHEMA);
|
|
1793
|
+
if (!hasInvocationContextRequestKeys(value)) throw invalidInvocationContextRequest();
|
|
1794
|
+
const clientNonce = requireClientNonce(value.client_nonce);
|
|
1795
|
+
const expiresAt = requireContextExpiration(value.context_expires_at);
|
|
1796
|
+
const hostBinding = requireHostBinding(value.host_binding);
|
|
1797
|
+
const messageWindow = requireHumanMessageWindow(value.human_message_window);
|
|
1798
|
+
const lastRequestRef = optionalSafeReference(value.last_seen_request_ref);
|
|
1799
|
+
const visibleOutputMaxBytes = optionalVisibleOutputBudget(value.visible_output_max_bytes);
|
|
1800
|
+
return Object.freeze({
|
|
1801
|
+
schema_version: CEAL_HOST_INVOCATION_CONTEXT_REQUEST_SCHEMA,
|
|
1802
|
+
client_nonce: clientNonce,
|
|
1803
|
+
context_expires_at: expiresAt,
|
|
1804
|
+
host_binding: Object.freeze({ ...hostBinding }),
|
|
1805
|
+
human_message_window: messageWindow,
|
|
1806
|
+
...lastRequestRef === void 0 ? {} : { last_seen_request_ref: lastRequestRef },
|
|
1807
|
+
...visibleOutputMaxBytes === void 0 ? {} : { visible_output_max_bytes: visibleOutputMaxBytes }
|
|
1808
|
+
});
|
|
1809
|
+
}
|
|
1810
|
+
function hasInvocationContextRequestKeys(value) {
|
|
1811
|
+
const required = ["schema_version", "client_nonce", "context_expires_at", "host_binding", "human_message_window"];
|
|
1812
|
+
const optional = ["last_seen_request_ref", "visible_output_max_bytes"];
|
|
1813
|
+
const optionalKeys = Object.keys(value).filter((key) => optional.includes(key));
|
|
1814
|
+
return hasExactKeys(value, [...required, ...optionalKeys]) && required.every((key) => Object.hasOwn(value, key)) && value.schema_version === CEAL_HOST_INVOCATION_CONTEXT_REQUEST_SCHEMA;
|
|
1815
|
+
}
|
|
1816
|
+
function requireClientNonce(value) {
|
|
1817
|
+
if (typeof value !== "string" || !CEAL_HOST_CLIENT_NONCE_PATTERN.test(value)) throw invalidInvocationContextRequest();
|
|
1818
|
+
return value;
|
|
1819
|
+
}
|
|
1820
|
+
function requireContextExpiration(value) {
|
|
1821
|
+
if (typeof value !== "string" || value.length > HOST_CONTEXT_EXPIRY_MAX_CHARS || !Number.isFinite(Date.parse(value))) throw invalidInvocationContextRequest();
|
|
1822
|
+
return value;
|
|
1823
|
+
}
|
|
1824
|
+
function requireHostBinding(value) {
|
|
1825
|
+
if (!isCealJsonRecord(value) || !exactOptionalKeys(value, ["host_source_ref", "host_session_ref", "host_turn_ref"], []) || !safeReference(value.host_source_ref) || !safeReference(value.host_session_ref) || !safeReference(value.host_turn_ref)) throw invalidInvocationContextRequest();
|
|
1826
|
+
return { host_source_ref: value.host_source_ref, host_session_ref: value.host_session_ref, host_turn_ref: value.host_turn_ref };
|
|
1827
|
+
}
|
|
1828
|
+
function requireHumanMessageWindow(value) {
|
|
1829
|
+
if (!isCealJsonRecord(value) || !exactOptionalKeys(value, ["messages", "truncated"], []) || !Array.isArray(value.messages) || value.messages.length > CEAL_HOST_INVOCATION_HUMAN_MESSAGE_WINDOW_MAX_MESSAGES || typeof value.truncated !== "boolean") throw invalidInvocationContextRequest();
|
|
1830
|
+
const messages = value.messages.map(decodeHumanMessage);
|
|
1831
|
+
const bytes2 = new TextEncoder().encode(JSON.stringify(messages)).byteLength;
|
|
1832
|
+
if (bytes2 > CEAL_HOST_INVOCATION_HUMAN_MESSAGE_WINDOW_MAX_BYTES) throw invalidInvocationContextRequest();
|
|
1833
|
+
return Object.freeze({ messages: Object.freeze(messages), truncated: value.truncated });
|
|
1834
|
+
}
|
|
1835
|
+
function decodeHumanMessage(candidate) {
|
|
1836
|
+
if (!isCealJsonRecord(candidate) || !exactOptionalKeys(candidate, ["host_turn_ref", "host_input_ref", "text"], []) || !safeReference(candidate.host_turn_ref) || !safeReference(candidate.host_input_ref) || typeof candidate.text !== "string") throw invalidInvocationContextRequest();
|
|
1837
|
+
return Object.freeze({ host_turn_ref: candidate.host_turn_ref, host_input_ref: candidate.host_input_ref, text: candidate.text });
|
|
1838
|
+
}
|
|
1839
|
+
function optionalSafeReference(value) {
|
|
1840
|
+
if (value === void 0) return void 0;
|
|
1841
|
+
if (!safeReference(value)) throw invalidInvocationContextRequest();
|
|
1842
|
+
return value;
|
|
1843
|
+
}
|
|
1844
|
+
function safeReference(value) {
|
|
1845
|
+
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
|
|
1846
|
+
}
|
|
1847
|
+
function exactOptionalKeys(value, required, optional = []) {
|
|
1848
|
+
const keys = Object.keys(value);
|
|
1849
|
+
return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => required.includes(key) || optional.includes(key));
|
|
1850
|
+
}
|
|
1851
|
+
function optionalVisibleOutputBudget(value) {
|
|
1852
|
+
if (value === void 0) return void 0;
|
|
1853
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1 || value > CEAL_HOST_INVOCATION_CONTEXT_VISIBLE_OUTPUT_MAX_BYTES) throw invalidInvocationContextRequest();
|
|
1854
|
+
return value;
|
|
1855
|
+
}
|
|
1856
|
+
function invalidInvocationContextRequest() {
|
|
1857
|
+
return new TypeError("The Host invocation context request is invalid.");
|
|
1858
|
+
}
|
|
1859
|
+
function deriveCealHostInvocationNonceContract(contract = CEAL_CLIENT_WIRE_CONTRACT) {
|
|
1860
|
+
const clientNonce = contract.host_invocation_authentication.client_nonce;
|
|
1861
|
+
return {
|
|
1862
|
+
encoded_characters: clientNonce.encoded_characters,
|
|
1863
|
+
client_nonce_pattern_source: `^[${clientNonce.character_class_source}]{${clientNonce.encoded_characters}}$`,
|
|
1864
|
+
context_token_pattern_source: `^${CEAL_HOST_CONTEXT_TOKEN_PREFIX}[${clientNonce.character_class_source}]{${clientNonce.encoded_characters}}$`
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
var hostNonceContract = deriveCealHostInvocationNonceContract();
|
|
1868
|
+
var CEAL_HOST_NONCE_ENCODED_CHARACTERS = hostNonceContract.encoded_characters;
|
|
1869
|
+
var CEAL_HOST_CLIENT_NONCE_PATTERN = new RegExp(hostNonceContract.client_nonce_pattern_source, "u");
|
|
1870
|
+
var CEAL_HOST_CONTEXT_TOKEN_PATTERN = new RegExp(hostNonceContract.context_token_pattern_source, "u");
|
|
1871
|
+
var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_MAX_BYTES = deriveCealHostInvocationAuthenticationResultMaxBytes();
|
|
1872
|
+
function deriveCealHostInvocationAuthenticationResultMaxBytes(contract = CEAL_CLIENT_WIRE_CONTRACT) {
|
|
1873
|
+
const hostAuthentication = contract.host_invocation_authentication;
|
|
1874
|
+
const values = {
|
|
1875
|
+
schema_version: deriveCealClientWireSchemaVersion({ schema_name: hostAuthentication.result_schema_name, fields: hostAuthentication.result_fields }),
|
|
1876
|
+
context_token: `${CEAL_HOST_CONTEXT_TOKEN_PREFIX}${"A".repeat(hostAuthentication.client_nonce.encoded_characters)}`,
|
|
1877
|
+
expires_at: (/* @__PURE__ */ new Date("+010000-01-01T00:00:00.000Z")).toISOString()
|
|
1878
|
+
};
|
|
1879
|
+
const fields = hostAuthentication.result_fields;
|
|
1880
|
+
if (fields.some((field) => !Object.hasOwn(values, field))) throw new TypeError("Host invocation result fields have no bounded sample value.");
|
|
1881
|
+
return new TextEncoder().encode(`${JSON.stringify(Object.fromEntries(fields.map((field) => [field, values[field]])))}
|
|
1473
1882
|
`).byteLength;
|
|
1883
|
+
}
|
|
1474
1884
|
var CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS = vocabulary([
|
|
1475
1885
|
"request_authority_unavailable",
|
|
1476
1886
|
"request_missing",
|
|
@@ -1494,6 +1904,7 @@ var CEAL_HOST_AUTHENTICATION_CAUSES = vocabulary([
|
|
|
1494
1904
|
"host_credential_refused",
|
|
1495
1905
|
"gateway_unavailable",
|
|
1496
1906
|
"gateway_rate_limited",
|
|
1907
|
+
"contract_mismatch",
|
|
1497
1908
|
"gateway_refused",
|
|
1498
1909
|
"gateway_response_invalid",
|
|
1499
1910
|
"producer_frame_invalid",
|
|
@@ -1508,76 +1919,43 @@ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
|
|
|
1508
1919
|
invalid_arguments: Object.freeze({ classification: "host_arguments_invalid", next_action: "answer_with_gap" }),
|
|
1509
1920
|
options_invalid: Object.freeze({ classification: "host_configuration_invalid", next_action: "answer_with_gap" }),
|
|
1510
1921
|
session_unavailable: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
|
|
1511
|
-
context_unavailable: Object.freeze({ classification: "host_context_unavailable", next_action: "
|
|
1512
|
-
context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "
|
|
1513
|
-
no_binding
|
|
1514
|
-
|
|
1515
|
-
|
|
1922
|
+
context_unavailable: Object.freeze({ classification: "host_context_unavailable", next_action: "retry" }),
|
|
1923
|
+
context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "retry" }),
|
|
1924
|
+
// `no_binding` now means the producer has no real current Host session. The
|
|
1925
|
+
// Host Relay never uses it for an uncreated Gateway Request; there is no
|
|
1926
|
+
// useful retry until the consumer is running inside a Host session.
|
|
1927
|
+
no_binding: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
|
|
1928
|
+
context_stale: Object.freeze({ classification: "host_invocation_context_stale", next_action: "retry" }),
|
|
1929
|
+
context_expired: Object.freeze({ classification: "host_invocation_context_expired", next_action: "retry" }),
|
|
1516
1930
|
host_source_refused: Object.freeze({ classification: "host_source_refused", next_action: "answer_with_gap" }),
|
|
1517
1931
|
host_credential_refused: Object.freeze({ classification: "host_registration_credential_refused", next_action: "ask_admin" }),
|
|
1518
1932
|
gateway_unavailable: Object.freeze({ classification: "host_mint_gateway_unavailable", next_action: "ask_admin" }),
|
|
1519
1933
|
gateway_rate_limited: Object.freeze({ classification: "host_mint_gateway_rate_limited", next_action: "answer_with_gap" }),
|
|
1934
|
+
contract_mismatch: Object.freeze({ classification: "contract_mismatch", next_action: "upgrade_ceal_client" }),
|
|
1520
1935
|
gateway_refused: Object.freeze({ classification: "host_mint_gateway_refused", next_action: "answer_with_gap" }),
|
|
1521
1936
|
gateway_response_invalid: Object.freeze({ classification: "host_mint_gateway_response_invalid", next_action: "answer_with_gap" }),
|
|
1522
|
-
producer_frame_invalid: Object.freeze({ classification: "host_producer_frame_invalid", next_action: "ask_admin"
|
|
1523
|
-
relay_permission_denied: Object.freeze({ classification: "host_relay_permission_denied", next_action: "rerun_outside_agent_sandbox"
|
|
1937
|
+
producer_frame_invalid: Object.freeze({ classification: "host_producer_frame_invalid", next_action: "ask_admin" }),
|
|
1938
|
+
relay_permission_denied: Object.freeze({ classification: "host_relay_permission_denied", next_action: "rerun_outside_agent_sandbox" }),
|
|
1524
1939
|
relay_unavailable: Object.freeze({ classification: "host_relay_unavailable", next_action: "answer_with_gap" }),
|
|
1525
1940
|
transient_retryable: Object.freeze({ classification: "host_mint_transient_retryable", next_action: "retry" }),
|
|
1526
1941
|
mint_failed: Object.freeze({ classification: "host_mint_failed", next_action: "answer_with_gap" }),
|
|
1527
1942
|
relay_failed: Object.freeze({ classification: "host_relay_failed", next_action: "answer_with_gap" }),
|
|
1528
1943
|
host_refusal_invalid: Object.freeze({ classification: "host_refusal_invalid", next_action: "answer_with_gap" })
|
|
1529
1944
|
});
|
|
1530
|
-
function projectCealHostAuthenticationCause(diagnostic) {
|
|
1531
|
-
return projectRelayCodeCause(diagnostic) ?? projectGatewayCodeCause(diagnostic) ?? projectGatewayStatusCause(diagnostic) ?? "mint_failed";
|
|
1532
|
-
}
|
|
1533
|
-
function projectRelayCodeCause(diagnostic) {
|
|
1534
|
-
switch (diagnostic.relayCode) {
|
|
1535
|
-
case "credential":
|
|
1536
|
-
return "host_credential_refused";
|
|
1537
|
-
case "configuration":
|
|
1538
|
-
return "options_invalid";
|
|
1539
|
-
case "payload_rejected":
|
|
1540
|
-
return "producer_frame_invalid";
|
|
1541
|
-
case "invalid_response":
|
|
1542
|
-
return "gateway_response_invalid";
|
|
1543
|
-
case "gateway_refused":
|
|
1544
|
-
return projectGatewayRefusedCause(diagnostic);
|
|
1545
|
-
case "gateway_unavailable":
|
|
1546
|
-
return diagnostic.exhausted === true ? void 0 : "gateway_unavailable";
|
|
1547
|
-
case void 0:
|
|
1548
|
-
case "":
|
|
1549
|
-
return void 0;
|
|
1550
|
-
default:
|
|
1551
|
-
return "mint_failed";
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
function projectGatewayRefusedCause(diagnostic) {
|
|
1555
|
-
if (diagnostic.gatewayStatus === 401 || diagnostic.gatewayStatus === 403) return "host_credential_refused";
|
|
1556
|
-
const stale = projectGatewayCodeCause(diagnostic);
|
|
1557
|
-
if (stale !== void 0) return stale;
|
|
1558
|
-
if (diagnostic.exhausted === true && (diagnostic.gatewayStatus === 408 || diagnostic.gatewayStatus === 429)) return "gateway_rate_limited";
|
|
1559
|
-
return "gateway_refused";
|
|
1560
|
-
}
|
|
1561
|
-
function projectGatewayCodeCause(diagnostic) {
|
|
1562
|
-
if (diagnostic.gatewayCode === "request_context_refused" && diagnostic.gatewayReason !== void 0 && isVocabularyWord(CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS, diagnostic.gatewayReason)) return "context_stale";
|
|
1563
|
-
if (diagnostic.gatewayCode === "context_expired") return "context_expired";
|
|
1564
|
-
if (diagnostic.gatewayCode === "host_source_refused") return "host_source_refused";
|
|
1565
|
-
return void 0;
|
|
1566
|
-
}
|
|
1567
|
-
function projectGatewayStatusCause(diagnostic) {
|
|
1568
|
-
if (diagnostic.gatewayStatus === 401 || diagnostic.gatewayStatus === 403) return "host_credential_refused";
|
|
1569
|
-
if (diagnostic.exhausted === true && (diagnostic.gatewayStatus === 408 || diagnostic.gatewayStatus === 429)) return "gateway_rate_limited";
|
|
1570
|
-
return void 0;
|
|
1571
|
-
}
|
|
1572
1945
|
function decodeCealHostInvocationAuthenticationResult(value, now = /* @__PURE__ */ new Date()) {
|
|
1573
|
-
|
|
1946
|
+
return decodeHostInvocationAuthenticationResult(value, CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_FIELDS, now);
|
|
1947
|
+
}
|
|
1948
|
+
function decodeHostInvocationAuthenticationResult(value, fields, now) {
|
|
1949
|
+
if (!isCealJsonRecord(value)) throw new TypeError("The Host invocation authentication result is invalid.");
|
|
1950
|
+
requireCealClientWireSchemaVersion(value.schema_version, CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_SCHEMA);
|
|
1951
|
+
if (!exactKeys(value, fields) || typeof value.context_token !== "string" || !CEAL_HOST_CONTEXT_TOKEN_PATTERN.test(value.context_token) || typeof value.expires_at !== "string" || !validFutureTimestamp(value.expires_at, now)) {
|
|
1574
1952
|
throw new TypeError("The Host invocation authentication result is invalid.");
|
|
1575
1953
|
}
|
|
1576
1954
|
return Object.freeze({ schema_version: CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_SCHEMA, context_token: value.context_token, expires_at: value.expires_at });
|
|
1577
1955
|
}
|
|
1578
1956
|
function validFutureTimestamp(value, now) {
|
|
1579
|
-
const
|
|
1580
|
-
return Number.isFinite(
|
|
1957
|
+
const timestamp2 = Date.parse(value);
|
|
1958
|
+
return Number.isFinite(timestamp2) && new Date(timestamp2).toISOString() === value && timestamp2 > now.getTime();
|
|
1581
1959
|
}
|
|
1582
1960
|
function exactKeys(value, expected) {
|
|
1583
1961
|
const keys = Object.keys(value);
|
|
@@ -1590,9 +1968,6 @@ var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
|
|
|
1590
1968
|
"hook_invoked",
|
|
1591
1969
|
"registration_written",
|
|
1592
1970
|
"hook_failed",
|
|
1593
|
-
"watcher_turn_observed",
|
|
1594
|
-
"current_prompt_registered",
|
|
1595
|
-
"current_prompt_registration_failed",
|
|
1596
1971
|
"activation_recorded",
|
|
1597
1972
|
"activation_failed",
|
|
1598
1973
|
"watcher_scan_failed",
|
|
@@ -1615,6 +1990,9 @@ var CEAL_UNREAD_REASONS = vocabulary(["blocked", "unavailable", "too_large", "un
|
|
|
1615
1990
|
var REQUIRED_FIELDS2 = Object.freeze(["schema_version", "operation_id", "path", "sha256"]);
|
|
1616
1991
|
var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "items_path", "row_count", "result_complete", "coverage", "local_result_root", "model_disclosure", "continue_argv", "how_to_read"]);
|
|
1617
1992
|
|
|
1993
|
+
// packages/ceal-client-protocol/src/discovery-manifest.ts
|
|
1994
|
+
var DISCOVERY_MANIFEST_KEYS = CEAL_CLIENT_WIRE_CONTRACT.command_response.discovery_manifest_fields;
|
|
1995
|
+
|
|
1618
1996
|
// packages/ceal-client-protocol/src/gateway-cache-origin-validation.ts
|
|
1619
1997
|
var CEAL_MAX_CACHE_ORIGIN_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1620
1998
|
|
|
@@ -1673,6 +2051,7 @@ var ADMIN_UNAVAILABLE_REASONS = vocabulary([
|
|
|
1673
2051
|
"legacy",
|
|
1674
2052
|
"invalid_observation",
|
|
1675
2053
|
"redacted",
|
|
2054
|
+
"organization_setting_disabled",
|
|
1676
2055
|
"owner_unavailable",
|
|
1677
2056
|
"unit_unknown",
|
|
1678
2057
|
"retention_gap",
|
|
@@ -1702,15 +2081,13 @@ var ADMIN_APPROVAL_DISPOSITIONS = CEAL_OPERATION_APPROVAL_DISPOSITIONS;
|
|
|
1702
2081
|
var ADMIN_APPROVAL_POSTURES = vocabulary([...ADMIN_APPROVAL_DISPOSITIONS, "not_applicable"]);
|
|
1703
2082
|
var ADMIN_LINEAGE_NODE_KINDS = vocabulary(["source_read", "synthesis_preview", "approval", "effect", "readback"]);
|
|
1704
2083
|
|
|
1705
|
-
// packages/ceal-client-protocol/src/actor-kind.ts
|
|
1706
|
-
var CEAL_ACTOR_KINDS = vocabulary(["human", "bot", "app", "unknown"]);
|
|
1707
|
-
|
|
1708
2084
|
// packages/ceal-client-protocol/src/device-enrollment.ts
|
|
1709
2085
|
var CEAL_DEVICE_ENROLLMENT_POLL_FAILURE_CODES = Object.freeze(["unsupported_feature", "recovery_required", "expired", "denied"]);
|
|
1710
2086
|
var POLL_RESULT_KEYS = Object.freeze(["retry_after_ms", "schema_version", "status"]);
|
|
1711
2087
|
|
|
1712
2088
|
// packages/ceal-client-protocol/src/personal-client-session.ts
|
|
1713
2089
|
var REFRESH_FAILURE_CODES = new Set(CEAL_REFRESH_FAILURE_CODES);
|
|
2090
|
+
var REVOKE_FAILURE_CODES = new Set(CEAL_CLIENT_REVOKE_FAILURE_CODES);
|
|
1714
2091
|
|
|
1715
2092
|
// packages/ceal-client-protocol/src/index.ts
|
|
1716
2093
|
var MAX_REQUEST_BYTES = 32 * 1024;
|
|
@@ -1746,60 +2123,251 @@ function errorCode(error) {
|
|
|
1746
2123
|
}
|
|
1747
2124
|
|
|
1748
2125
|
// host/service-log.ts
|
|
1749
|
-
import { copyFile,
|
|
1750
|
-
import { isAbsolute } from "node:path";
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
|
|
2126
|
+
import { copyFile, mkdir, stat, truncate, appendFile } from "node:fs/promises";
|
|
2127
|
+
import { dirname as dirname2, isAbsolute, posix, win32 as win322 } from "node:path";
|
|
2128
|
+
|
|
2129
|
+
// host/secure-file.ts
|
|
2130
|
+
import { execFile } from "node:child_process";
|
|
2131
|
+
import { constants } from "node:fs";
|
|
2132
|
+
import { lstat, open } from "node:fs/promises";
|
|
2133
|
+
import { dirname, win32 } from "node:path";
|
|
2134
|
+
import { fileURLToPath } from "node:url";
|
|
2135
|
+
var SecureFileCustodyError = class extends Error {
|
|
2136
|
+
name = "SecureFileCustodyError";
|
|
2137
|
+
reason;
|
|
2138
|
+
constructor(reason, message) {
|
|
2139
|
+
super(message);
|
|
2140
|
+
this.reason = reason;
|
|
2141
|
+
}
|
|
2142
|
+
get code() {
|
|
2143
|
+
if (this.reason === "reparse_point") return "ELOOP";
|
|
2144
|
+
if (this.reason === "not_found") return "ENOENT";
|
|
2145
|
+
if (this.reason === "too_large") return "secure_file_too_large";
|
|
2146
|
+
if (this.reason === "already_exists") return "EEXIST";
|
|
2147
|
+
return this.reason === "not_private" ? "secure_file_not_private" : "secure_file_unavailable";
|
|
2148
|
+
}
|
|
2149
|
+
};
|
|
2150
|
+
var WINDOWS_SECUREFS_REFUSAL_SCHEMA = "ceal.host_securefs_refusal.v1";
|
|
2151
|
+
var WINDOWS_SECUREFS_MAX_RANDOM_FILE_BYTES = 4096;
|
|
2152
|
+
var WINDOWS_SECUREFS_TIMEOUT_MS = 1e4;
|
|
2153
|
+
var WINDOWS_SECUREFS_DIAGNOSTIC_BYTES = 4096;
|
|
2154
|
+
function secureOpenFlags(flags, platform) {
|
|
2155
|
+
if (platform === "win32") return flags;
|
|
2156
|
+
const nofollow = constants.O_NOFOLLOW;
|
|
2157
|
+
if (typeof nofollow === "number" && nofollow !== 0) return flags | nofollow;
|
|
2158
|
+
throw new Error("Secure file reads require O_NOFOLLOW on this platform.");
|
|
2159
|
+
}
|
|
2160
|
+
async function openNoFollow(path, flags = constants.O_RDONLY, mode, platform = process.platform, _custody = {}) {
|
|
2161
|
+
if (platform !== "win32") return await open(path, secureOpenFlags(flags, platform), mode);
|
|
2162
|
+
return await openWindowsIdentityScreened(path, flags, mode);
|
|
2163
|
+
}
|
|
2164
|
+
async function openWindowsIdentityScreened(path, flags, mode) {
|
|
2165
|
+
const handle = await open(path, flags, mode);
|
|
1759
2166
|
try {
|
|
1760
|
-
|
|
2167
|
+
const first = await handle.stat();
|
|
2168
|
+
if (!first.isFile()) throw new SecureFileCustodyError("not_private", "The Windows Host file is not a regular file.");
|
|
2169
|
+
await assertWindowsLinkFree(path, first);
|
|
2170
|
+
await assertWindowsIdentityStable(handle, first, path);
|
|
2171
|
+
return handle;
|
|
1761
2172
|
} catch (error) {
|
|
1762
|
-
|
|
2173
|
+
await handle.close();
|
|
1763
2174
|
throw error;
|
|
1764
2175
|
}
|
|
1765
|
-
if (size <= maxBytes) return false;
|
|
1766
|
-
await copyFile(path, hostServiceLogPreviousPath(path));
|
|
1767
|
-
await truncate(path, 0);
|
|
1768
|
-
return true;
|
|
1769
2176
|
}
|
|
1770
|
-
function
|
|
1771
|
-
const
|
|
1772
|
-
if (
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
rotateHostServiceLog(path).catch((error) => {
|
|
1776
|
-
if (reported) return;
|
|
1777
|
-
reported = true;
|
|
1778
|
-
process.stderr.write(`[host-service-log] rotation_failed code=${errorCode2(error)}
|
|
1779
|
-
`);
|
|
1780
|
-
});
|
|
1781
|
-
};
|
|
1782
|
-
check();
|
|
1783
|
-
const timer = setInterval(check, HOST_SERVICE_LOG_CHECK_INTERVAL_MS);
|
|
1784
|
-
timer.unref();
|
|
1785
|
-
return () => clearInterval(timer);
|
|
2177
|
+
async function assertWindowsLinkFree(path, first) {
|
|
2178
|
+
const entry2 = await lstat(path);
|
|
2179
|
+
if (entry2.isSymbolicLink() || entry2.dev !== first.dev || entry2.ino !== first.ino) {
|
|
2180
|
+
throw new SecureFileCustodyError("reparse_point", "The Windows Host file path resolves through a reparse point.");
|
|
2181
|
+
}
|
|
1786
2182
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
2183
|
+
async function assertWindowsIdentityStable(handle, first, path) {
|
|
2184
|
+
const second = await handle.stat();
|
|
2185
|
+
if (second.dev !== first.dev || second.ino !== first.ino || second.size !== first.size || second.mtimeMs !== first.mtimeMs) {
|
|
2186
|
+
throw new SecureFileCustodyError("unavailable", "The Windows Host file changed while its identity was checked.");
|
|
2187
|
+
}
|
|
2188
|
+
const entry2 = await lstat(path);
|
|
2189
|
+
if (entry2.isSymbolicLink()) throw new SecureFileCustodyError("reparse_point", "The Windows Host file path resolves through a reparse point.");
|
|
2190
|
+
if (entry2.dev !== second.dev || entry2.ino !== second.ino) throw new SecureFileCustodyError("unavailable", "The Windows Host file path no longer names the opened file.");
|
|
2191
|
+
}
|
|
2192
|
+
async function readPrivateFileWithSecurefs(path, maxBytes, custody = {}) {
|
|
2193
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) throw new TypeError("The securefs read bound must be a positive safe integer.");
|
|
2194
|
+
const result2 = await runWindowsSecurefs(["internal", "securefs", "read-private-file", path, String(maxBytes)], maxBytes, custody);
|
|
2195
|
+
if (result2.exit_code === 0 && result2.stderr.length === 0 && result2.stdout.length <= maxBytes) return result2.stdout;
|
|
2196
|
+
if (result2.stdout.length !== 0) throw new SecureFileCustodyError("unavailable", "The Windows Host securefs command returned bytes with a refusal.");
|
|
2197
|
+
if (result2.exit_code === 0) throw new SecureFileCustodyError("unavailable", "The Windows Host securefs command exceeded its output contract.");
|
|
2198
|
+
throw securefsRefusal(result2.stderr);
|
|
2199
|
+
}
|
|
2200
|
+
async function createRandomPrivateFileWithSecurefs(path, byteLength2, custody = {}) {
|
|
2201
|
+
if (!Number.isSafeInteger(byteLength2) || byteLength2 < 1 || byteLength2 > WINDOWS_SECUREFS_MAX_RANDOM_FILE_BYTES) throw new TypeError(`The securefs random-file size must be between 1 and ${WINDOWS_SECUREFS_MAX_RANDOM_FILE_BYTES} bytes.`);
|
|
2202
|
+
const result2 = await runWindowsSecurefs(["internal", "securefs", "create-random-private-file", path, String(byteLength2)], 0, custody);
|
|
2203
|
+
if (result2.exit_code === 0 && result2.stdout.length === 0 && result2.stderr.length === 0) return;
|
|
2204
|
+
if (result2.stdout.length !== 0) throw new SecureFileCustodyError("unavailable", "The Windows Host securefs command returned unexpected creation bytes.");
|
|
2205
|
+
if (result2.exit_code === 0) throw new SecureFileCustodyError("unavailable", "The Windows Host securefs command returned an invalid creation result.");
|
|
2206
|
+
throw securefsRefusal(result2.stderr);
|
|
2207
|
+
}
|
|
2208
|
+
async function isPrivateWindowsDirectory(path, custody = {}) {
|
|
2209
|
+
await lstat(path);
|
|
2210
|
+
try {
|
|
2211
|
+
const result2 = await runWindowsSecurefs(["internal", "securefs", "verify-private-dir", path], 0, custody);
|
|
2212
|
+
if (result2.exit_code === 0 && result2.stdout.length === 0 && result2.stderr.length === 0) return true;
|
|
2213
|
+
if (result2.stdout.length !== 0) throw new SecureFileCustodyError("unavailable", "The Windows Host directory proof returned unexpected bytes.");
|
|
2214
|
+
if (result2.exit_code === 0) throw new SecureFileCustodyError("unavailable", "The Windows Host directory proof returned an invalid result.");
|
|
2215
|
+
const refusal = securefsRefusal(result2.stderr);
|
|
2216
|
+
if (refusal.reason === "not_private" || refusal.reason === "reparse_point") return false;
|
|
2217
|
+
throw refusal;
|
|
2218
|
+
} catch (error) {
|
|
2219
|
+
if (error instanceof SecureFileCustodyError) throw error;
|
|
2220
|
+
throw new SecureFileCustodyError("unavailable", "The Windows Host directory custody proof is unavailable.");
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
async function runWindowsSecurefs(args, maxBytes, custody) {
|
|
2224
|
+
if (custody.run_securefs_command !== void 0) return await custody.run_securefs_command(args);
|
|
2225
|
+
const executable = custody.securefs_executable ?? resolveInstalledHostExecutable();
|
|
2226
|
+
const processArgs = [...custody.securefs_args_prefix ?? [], ...args];
|
|
2227
|
+
return await new Promise((resolve, reject) => {
|
|
2228
|
+
execFile(executable, processArgs, { encoding: "buffer", timeout: WINDOWS_SECUREFS_TIMEOUT_MS, maxBuffer: maxBytes + WINDOWS_SECUREFS_DIAGNOSTIC_BYTES }, (error, stdout, stderr) => {
|
|
2229
|
+
const exitCode = error === null ? 0 : typeof error.code === "number" ? error.code : null;
|
|
2230
|
+
if (exitCode === null) {
|
|
2231
|
+
reject(new SecureFileCustodyError("unavailable", "The installed Windows Host securefs command could not run."));
|
|
2232
|
+
return;
|
|
2233
|
+
}
|
|
2234
|
+
resolve({ exit_code: exitCode, stdout: Buffer.from(stdout), stderr: Buffer.from(stderr) });
|
|
2235
|
+
});
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
function securefsRefusal(stderr) {
|
|
2239
|
+
let value;
|
|
2240
|
+
try {
|
|
2241
|
+
value = JSON.parse(stderr.toString("utf8"));
|
|
2242
|
+
} catch {
|
|
2243
|
+
return new SecureFileCustodyError("unavailable", "The Windows Host securefs command returned an unreadable refusal.");
|
|
2244
|
+
}
|
|
2245
|
+
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.");
|
|
2246
|
+
if (value.code === "reparse_point") return new SecureFileCustodyError("reparse_point", "The Windows Host securefs file is a reparse point.");
|
|
2247
|
+
if (value.code === "not_found") return new SecureFileCustodyError("not_found", "The Windows Host securefs file was not found.");
|
|
2248
|
+
if (value.code === "too_large") return new SecureFileCustodyError("too_large", "The Windows Host securefs file exceeds its read bound.");
|
|
2249
|
+
if (value.code === "already_exists") return new SecureFileCustodyError("already_exists", "The Windows Host securefs target already exists.");
|
|
2250
|
+
if (value.code === "not_private") return new SecureFileCustodyError("not_private", "The Windows Host securefs file is not private to the current user.");
|
|
2251
|
+
return new SecureFileCustodyError("unavailable", "The Windows Host securefs command refused the file read.");
|
|
2252
|
+
}
|
|
2253
|
+
function resolveInstalledHostExecutable() {
|
|
2254
|
+
const privateRuntimeRoot = dirname(fileURLToPath(import.meta.url));
|
|
2255
|
+
return win32.join(privateRuntimeRoot, "..", "..", "bin", "ceal-host.exe");
|
|
2256
|
+
}
|
|
2257
|
+
function isRecord(value) {
|
|
2258
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2259
|
+
}
|
|
2260
|
+
function isOwnedBy(uid, expectedUid) {
|
|
2261
|
+
return Number.isSafeInteger(uid) && Number.isSafeInteger(expectedUid) && uid === expectedUid;
|
|
2262
|
+
}
|
|
2263
|
+
function isSelfOwned(uid, platform = process.platform, _handle) {
|
|
2264
|
+
if (platform === "win32" || typeof process.geteuid !== "function") return false;
|
|
2265
|
+
return isOwnedBy(uid, process.geteuid());
|
|
2266
|
+
}
|
|
2267
|
+
function isPrivateFileMode(mode, platform = process.platform, _handle) {
|
|
2268
|
+
if (platform === "win32") return false;
|
|
2269
|
+
return (mode & 63) === 0;
|
|
2270
|
+
}
|
|
2271
|
+
async function isPrivateDirectory(path, platform = process.platform, custody = {}) {
|
|
2272
|
+
if (platform === "win32") return await isPrivateWindowsDirectory(path, custody);
|
|
2273
|
+
const entry2 = await lstat(path);
|
|
2274
|
+
return entry2.isDirectory() && !entry2.isSymbolicLink() && isSelfOwned(entry2.uid, platform) && isPrivateFileMode(entry2.mode, platform);
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
// host/service-log.ts
|
|
2278
|
+
var HOST_SERVICE_LOG_ENVIRONMENT = "CEAL_HOST_SERVICE_LOG";
|
|
2279
|
+
var HOST_SERVICE_LOG_ARGUMENT = "--ceal-host-service-log";
|
|
2280
|
+
var HostServiceLogArgumentError = class extends Error {
|
|
2281
|
+
constructor() {
|
|
2282
|
+
super("The Host service log argument must occur once with an absolute normalized path.");
|
|
2283
|
+
this.name = "HostServiceLogArgumentError";
|
|
2284
|
+
}
|
|
2285
|
+
};
|
|
2286
|
+
function consumeHostServiceLogArgument(args, platform = process.platform) {
|
|
2287
|
+
const indexes = args.flatMap((value, index2) => value === HOST_SERVICE_LOG_ARGUMENT ? [index2] : []);
|
|
2288
|
+
if (indexes.length === 0) return { args: [...args] };
|
|
2289
|
+
const index = indexes[0];
|
|
2290
|
+
if (index === void 0) throw new HostServiceLogArgumentError();
|
|
2291
|
+
const path = args[index + 1];
|
|
2292
|
+
const pathApi = platform === "win32" ? win322 : posix;
|
|
2293
|
+
if (indexes.length !== 1 || path === void 0 || !pathApi.isAbsolute(path) || pathApi.normalize(path) !== path) throw new HostServiceLogArgumentError();
|
|
2294
|
+
return { args: [...args.slice(0, index), ...args.slice(index + 2)], path };
|
|
2295
|
+
}
|
|
2296
|
+
var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
|
|
2297
|
+
var HOST_SERVICE_LOG_CHECK_INTERVAL_MS = 6e4;
|
|
2298
|
+
function hostServiceLogPreviousPath(path) {
|
|
2299
|
+
return `${path}.1`;
|
|
2300
|
+
}
|
|
2301
|
+
async function rotateHostServiceLog(path, maxBytes = HOST_SERVICE_LOG_MAX_BYTES) {
|
|
2302
|
+
let size;
|
|
2303
|
+
try {
|
|
2304
|
+
size = (await stat(path)).size;
|
|
2305
|
+
} catch (error) {
|
|
2306
|
+
if (isNotFound(error)) return false;
|
|
2307
|
+
throw error;
|
|
2308
|
+
}
|
|
2309
|
+
if (size <= maxBytes) return false;
|
|
2310
|
+
await copyFile(path, hostServiceLogPreviousPath(path));
|
|
2311
|
+
await truncate(path, 0);
|
|
2312
|
+
return true;
|
|
2313
|
+
}
|
|
2314
|
+
function boundHostServiceLog(environment = process.env) {
|
|
2315
|
+
const path = environment[HOST_SERVICE_LOG_ENVIRONMENT];
|
|
2316
|
+
if (path === void 0 || !isAbsolute(path)) return () => void 0;
|
|
2317
|
+
let reported = false;
|
|
2318
|
+
const ready = ensureHostServiceLogDirectory(path);
|
|
2319
|
+
const check = () => {
|
|
2320
|
+
ready.then(() => rotateHostServiceLog(path)).catch((error) => {
|
|
2321
|
+
if (reported) return;
|
|
2322
|
+
reported = true;
|
|
2323
|
+
process.stderr.write(`[host-service-log] rotation_failed code=${errorCode2(error)}
|
|
2324
|
+
`);
|
|
2325
|
+
});
|
|
2326
|
+
};
|
|
2327
|
+
check();
|
|
2328
|
+
const timer = setInterval(check, HOST_SERVICE_LOG_CHECK_INTERVAL_MS);
|
|
2329
|
+
timer.unref();
|
|
2330
|
+
return () => clearInterval(timer);
|
|
2331
|
+
}
|
|
2332
|
+
function writeHostServiceLogLine(line, environment = process.env) {
|
|
2333
|
+
if (process.platform !== "win32") return;
|
|
2334
|
+
const path = environment[HOST_SERVICE_LOG_ENVIRONMENT];
|
|
2335
|
+
if (path === void 0 || !win322.isAbsolute(path) || win322.normalize(path) !== path) return;
|
|
2336
|
+
void appendHostServiceLogLine(path, line).catch(() => void 0);
|
|
2337
|
+
}
|
|
2338
|
+
async function appendHostServiceLogLine(path, line) {
|
|
2339
|
+
if (!isAbsolute(path)) return;
|
|
2340
|
+
await ensureHostServiceLogDirectory(path);
|
|
2341
|
+
await appendFile(path, line);
|
|
2342
|
+
}
|
|
2343
|
+
var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
|
|
2344
|
+
var hostServiceLogDirectories = /* @__PURE__ */ new Map();
|
|
2345
|
+
function ensureHostServiceLogDirectory(path) {
|
|
2346
|
+
const directory = dirname2(path);
|
|
2347
|
+
const existing = hostServiceLogDirectories.get(directory);
|
|
2348
|
+
if (existing !== void 0) return existing;
|
|
2349
|
+
const pending = mkdir(directory, { recursive: true, mode: 448 }).then(() => void 0).catch((error) => {
|
|
2350
|
+
hostServiceLogDirectories.delete(directory);
|
|
2351
|
+
throw error;
|
|
2352
|
+
});
|
|
2353
|
+
hostServiceLogDirectories.set(directory, pending);
|
|
2354
|
+
return pending;
|
|
2355
|
+
}
|
|
2356
|
+
function isNotFound(error) {
|
|
2357
|
+
return errorCode2(error) === "ENOENT";
|
|
1790
2358
|
}
|
|
1791
2359
|
function errorCode2(error) {
|
|
1792
2360
|
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "unknown";
|
|
1793
2361
|
}
|
|
1794
2362
|
|
|
1795
2363
|
// host/runtime-source-paths.ts
|
|
1796
|
-
import { createHash as
|
|
1797
|
-
import { posix as
|
|
2364
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2365
|
+
import { posix as posix3, win32 as win324 } from "node:path";
|
|
1798
2366
|
|
|
1799
2367
|
// host/absolute-path.ts
|
|
1800
|
-
import { posix, win32 } from "node:path";
|
|
2368
|
+
import { posix as posix2, win32 as win323 } from "node:path";
|
|
1801
2369
|
function isAbsoluteNormalizedNonRootPath(value, flavor = process.platform, allowRoot = false) {
|
|
1802
|
-
const pathApi = flavor === "win32" || flavor === "windows" ?
|
|
2370
|
+
const pathApi = flavor === "win32" || flavor === "windows" ? win323 : posix2;
|
|
1803
2371
|
return typeof value === "string" && value.length <= 4096 && !value.includes("\0") && pathApi.isAbsolute(value) && pathApi.normalize(value) === value && (allowRoot || pathApi.parse(value).root !== value);
|
|
1804
2372
|
}
|
|
1805
2373
|
|
|
@@ -1811,28 +2379,22 @@ function hostSourceUuid(sourceRef) {
|
|
|
1811
2379
|
function hostRuntimeSourcePaths(stateRoot, sourceRef, platform = process.platform) {
|
|
1812
2380
|
const sourceId = hostSourceUuid(sourceRef);
|
|
1813
2381
|
if (!isAbsoluteNormalizedNonRootPath(stateRoot, platform) || sourceId === null) throw new TypeError("Host runtime source identity is invalid.");
|
|
1814
|
-
const path = platform === "win32" ?
|
|
2382
|
+
const path = platform === "win32" ? win324 : posix3;
|
|
1815
2383
|
const sourcesRoot = path.join(stateRoot, "sources");
|
|
1816
2384
|
const sourceRoot = path.join(sourcesRoot, sourceId);
|
|
1817
2385
|
const runtimeRoot = path.join(stateRoot, "run");
|
|
1818
|
-
const endpoint = platform === "win32" ? `\\\\.\\pipe\\ceal-host-${
|
|
1819
|
-
assertHostRuntimeIpcLength(endpoint, platform);
|
|
2386
|
+
const endpoint = platform === "win32" ? `\\\\.\\pipe\\ceal-host-${createHash3("sha256").update(`${stateRoot}\0${sourceRef}`).digest("hex").slice(0, 24)}` : path.join(runtimeRoot, `${sourceId}.sock`);
|
|
1820
2387
|
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 };
|
|
1821
2388
|
}
|
|
1822
|
-
function assertHostRuntimeIpcLength(endpoint, platform) {
|
|
1823
|
-
if (platform === "win32") return;
|
|
1824
|
-
const maximum = platform === "linux" ? 107 : 103;
|
|
1825
|
-
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" });
|
|
1826
|
-
}
|
|
1827
2389
|
function assertHostRelaySourcePaths(stateRoot, config, platform = process.platform) {
|
|
1828
2390
|
const paths = hostRuntimeSourcePaths(stateRoot, config.host_source_ref, platform);
|
|
1829
|
-
if (config.state_root !== paths.relayRoot || config.ipc_endpoint !== paths.endpoint) throw new TypeError("Host Relay state and IPC must belong to its configured source.");
|
|
2391
|
+
if (config.state_root !== paths.relayRoot || platform !== "win32" && config.ipc_endpoint !== paths.endpoint) throw new TypeError("Host Relay state and IPC must belong to its configured source.");
|
|
2392
|
+
return paths.endpoint;
|
|
1830
2393
|
}
|
|
1831
2394
|
|
|
1832
2395
|
// host/relay/src/host-relay-runtime.ts
|
|
1833
|
-
import {
|
|
1834
|
-
import {
|
|
1835
|
-
import { dirname as dirname4, join as join7 } from "node:path";
|
|
2396
|
+
import { constants as constants4 } from "node:fs";
|
|
2397
|
+
import { dirname as dirname7, join as join6 } from "node:path";
|
|
1836
2398
|
|
|
1837
2399
|
// host/abortable-wait.ts
|
|
1838
2400
|
function waitForAbort(signal, milliseconds) {
|
|
@@ -1918,13 +2480,13 @@ function skipWhitespace(text, start) {
|
|
|
1918
2480
|
}
|
|
1919
2481
|
|
|
1920
2482
|
// host/relay/src/producer-frame.ts
|
|
1921
|
-
import { createHash as
|
|
2483
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
1922
2484
|
var HOST_PRODUCER_FRAME_SCHEMA = "ceal.host_producer_frame.v1";
|
|
1923
2485
|
var HOST_PRODUCER_MAX_JSON_BYTES = 262144;
|
|
1924
2486
|
var HOST_PRODUCER_FRAME_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
1925
2487
|
var SAFE_REF2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
1926
2488
|
var PRODUCERS = /* @__PURE__ */ new Set(["codex", "claude"]);
|
|
1927
|
-
var HOST_PRODUCER_FRAME_KINDS = ["session_started", "turn_started", "
|
|
2489
|
+
var HOST_PRODUCER_FRAME_KINDS = ["session_started", "turn_started", "turn_terminal", "producer_status"];
|
|
1928
2490
|
var FRAME_KINDS = new Set(HOST_PRODUCER_FRAME_KINDS);
|
|
1929
2491
|
function isHostProducerFrameKind(value) {
|
|
1930
2492
|
return typeof value === "string" && FRAME_KINDS.has(value);
|
|
@@ -1976,58 +2538,15 @@ function decodeHostProducerFrame(input) {
|
|
|
1976
2538
|
return value;
|
|
1977
2539
|
}
|
|
1978
2540
|
function hostProducerFrameHash(frame) {
|
|
1979
|
-
return
|
|
1980
|
-
}
|
|
1981
|
-
function extractHostBindingSelector(frame) {
|
|
1982
|
-
if (frame.frame_kind !== "user_input") return null;
|
|
1983
|
-
if (!PRODUCERS.has(frame.producer_id) || !safeRef(frame.producer_installation_ref)) return null;
|
|
1984
|
-
const payload = frame.payload;
|
|
1985
|
-
if (!safeRef(payload.local_session_ref) || !safeRef(payload.local_turn_ref)) return null;
|
|
1986
|
-
const aliases = payload.local_turn_aliases;
|
|
1987
|
-
if (aliases === void 0) {
|
|
1988
|
-
return Object.freeze({
|
|
1989
|
-
producer_id: frame.producer_id,
|
|
1990
|
-
producer_installation_ref: frame.producer_installation_ref,
|
|
1991
|
-
local_session_ref: payload.local_session_ref,
|
|
1992
|
-
local_turn_ref: payload.local_turn_ref,
|
|
1993
|
-
local_turn_aliases: Object.freeze([])
|
|
1994
|
-
});
|
|
1995
|
-
}
|
|
1996
|
-
if (!Array.isArray(aliases) || aliases.length > 16 || !aliases.every(safeRef) || new Set(aliases).size !== aliases.length) return null;
|
|
1997
|
-
return Object.freeze({
|
|
1998
|
-
producer_id: frame.producer_id,
|
|
1999
|
-
producer_installation_ref: frame.producer_installation_ref,
|
|
2000
|
-
local_session_ref: payload.local_session_ref,
|
|
2001
|
-
local_turn_ref: payload.local_turn_ref,
|
|
2002
|
-
local_turn_aliases: Object.freeze([...aliases])
|
|
2003
|
-
});
|
|
2004
|
-
}
|
|
2005
|
-
function hostProducerIdentityRefs(hostSourceRef, frame, localSessionRef, localTurnRef, localInputRef) {
|
|
2006
|
-
if (!opaqueHostSourceRef(hostSourceRef)) throw new TypeError("Gateway host source reference is invalid.");
|
|
2007
|
-
const host_session_ref = localSessionRef === void 0 ? void 0 : `ceal-host-session:${cealRequestSha256({ host_source_ref: hostSourceRef, producer_id: frame.producer_id, producer_installation_ref: frame.producer_installation_ref, local_session_ref: localSessionRef })}`;
|
|
2008
|
-
const host_turn_ref = host_session_ref === void 0 || localTurnRef === void 0 ? void 0 : `ceal-host-turn:${cealRequestSha256({ host_session_ref, local_turn_ref: localTurnRef })}`;
|
|
2009
|
-
const host_input_ref = host_turn_ref === void 0 || localInputRef === void 0 ? void 0 : `ceal-host-input:${cealRequestSha256({ host_turn_ref, local_input_ref: localInputRef })}`;
|
|
2010
|
-
const submission_ref = `ceal-host-frame:${cealRequestSha256({ host_source_ref: hostSourceRef, producer_id: frame.producer_id, producer_installation_ref: frame.producer_installation_ref, sequence: frame.sequence })}`;
|
|
2011
|
-
return { host_session_ref: host_session_ref ?? "", ...host_turn_ref === void 0 ? {} : { host_turn_ref }, ...host_input_ref === void 0 ? {} : { host_input_ref }, submission_ref };
|
|
2012
|
-
}
|
|
2013
|
-
function hostProducerSessionRef(hostSourceRef, frame, localSessionRef) {
|
|
2014
|
-
return requiredIdentity(hostProducerIdentityRefs(hostSourceRef, frame, localSessionRef).host_session_ref);
|
|
2015
|
-
}
|
|
2016
|
-
function hostProducerTurnRef(hostSourceRef, frame, localSessionRef, localTurnRef) {
|
|
2017
|
-
return requiredIdentity(hostProducerIdentityRefs(hostSourceRef, frame, localSessionRef, localTurnRef).host_turn_ref);
|
|
2018
|
-
}
|
|
2019
|
-
function hostProducerInputRef(hostSourceRef, frame, localSessionRef, localTurnRef, localInputRef) {
|
|
2020
|
-
return requiredIdentity(hostProducerIdentityRefs(hostSourceRef, frame, localSessionRef, localTurnRef, localInputRef).host_input_ref);
|
|
2541
|
+
return createHash4("sha256").update(JSON.stringify(frame), "utf8").digest("hex");
|
|
2021
2542
|
}
|
|
2022
2543
|
function hostProducerSubmissionRef(hostSourceRef, frame) {
|
|
2023
|
-
|
|
2544
|
+
if (!opaqueHostSourceRef(hostSourceRef)) throw new TypeError("Gateway host source reference is invalid.");
|
|
2545
|
+
return `ceal-host-frame:${cealRequestSha256({ host_source_ref: hostSourceRef, producer_id: frame.producer_id, producer_installation_ref: frame.producer_installation_ref, sequence: frame.sequence })}`;
|
|
2024
2546
|
}
|
|
2025
2547
|
function opaqueHostSourceRef(value) {
|
|
2026
2548
|
return typeof value === "string" && SAFE_REF2.test(value) && !value.startsWith("ceal-host-source:");
|
|
2027
2549
|
}
|
|
2028
|
-
function isHostSafeRef(value) {
|
|
2029
|
-
return safeRef(value);
|
|
2030
|
-
}
|
|
2031
2550
|
var BOUNDED_REFUSAL_WORD = /^[a-z][a-z0-9_]{0,63}$/u;
|
|
2032
2551
|
function isBoundedRefusalWord(value) {
|
|
2033
2552
|
return typeof value === "string" && BOUNDED_REFUSAL_WORD.test(value);
|
|
@@ -2044,16 +2563,13 @@ var HOST_RELAY_GATEWAY_ERROR_CODES = [
|
|
|
2044
2563
|
"gateway_unavailable",
|
|
2045
2564
|
"gateway_refused",
|
|
2046
2565
|
"invalid_response",
|
|
2566
|
+
"contract_mismatch",
|
|
2047
2567
|
"payload_rejected"
|
|
2048
2568
|
];
|
|
2049
2569
|
function isRelayGatewayErrorCode(value) {
|
|
2050
2570
|
return isVocabularyWord(HOST_RELAY_GATEWAY_ERROR_CODES, value);
|
|
2051
2571
|
}
|
|
2052
2572
|
var RELAY_DELIVERY_DIAGNOSTIC_KEYS = ["relayCode", "gatewayStatus", "gatewayCode", "gatewayReason", "exhausted"];
|
|
2053
|
-
function requiredIdentity(value) {
|
|
2054
|
-
if (value === void 0) throw new TypeError("Canonical Host identity is incomplete.");
|
|
2055
|
-
return value;
|
|
2056
|
-
}
|
|
2057
2573
|
function validateFrame(value) {
|
|
2058
2574
|
if (!record(value)) throw new HostProducerFrameError("invalid_frame_shape");
|
|
2059
2575
|
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)}`);
|
|
@@ -2063,7 +2579,7 @@ function validateFrame(value) {
|
|
|
2063
2579
|
validatePayload(value.frame_kind, value.payload);
|
|
2064
2580
|
}
|
|
2065
2581
|
function validatePayload(kind, value) {
|
|
2066
|
-
const valid = kind === "session_started" ? validSession(value) : kind === "turn_started" ? validTurn(value) : kind === "
|
|
2582
|
+
const valid = kind === "session_started" ? validSession(value) : kind === "turn_started" ? validTurn(value) : kind === "turn_terminal" ? validTerminal(value) : validStatus(value);
|
|
2067
2583
|
if (!valid) throw new HostProducerFrameError("invalid_frame_shape");
|
|
2068
2584
|
}
|
|
2069
2585
|
function validSession(value) {
|
|
@@ -2072,15 +2588,6 @@ function validSession(value) {
|
|
|
2072
2588
|
function validTurn(value) {
|
|
2073
2589
|
return exactOptional(value, ["local_session_ref", "local_turn_ref"], ["model", "observations"]) && safeRef(value.local_session_ref) && safeRef(value.local_turn_ref) && optionalText(value.model, 256) && validObservations(value.observations);
|
|
2074
2590
|
}
|
|
2075
|
-
function validInput(value) {
|
|
2076
|
-
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);
|
|
2077
|
-
}
|
|
2078
|
-
function validInputObservations(value) {
|
|
2079
|
-
return validObservations(value) && Array.isArray(value) && value.filter((item) => record(item) && item.observation_kind === "prompt").length === 1;
|
|
2080
|
-
}
|
|
2081
|
-
function validInputOptionals(value) {
|
|
2082
|
-
return optionalRefs(value.local_turn_aliases) && (value.context_expires_at === void 0 || timestamp(value.context_expires_at));
|
|
2083
|
-
}
|
|
2084
2591
|
function validTerminal(value) {
|
|
2085
2592
|
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);
|
|
2086
2593
|
}
|
|
@@ -2096,9 +2603,6 @@ function optionalText(value, maxLength) {
|
|
|
2096
2603
|
function optionalNonNegative(value) {
|
|
2097
2604
|
return value === void 0 || typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
2098
2605
|
}
|
|
2099
|
-
function optionalRefs(value) {
|
|
2100
|
-
return value === void 0 || Array.isArray(value) && value.length <= 16 && value.every(safeRef);
|
|
2101
|
-
}
|
|
2102
2606
|
function validObservations(value) {
|
|
2103
2607
|
if (value === void 0) return true;
|
|
2104
2608
|
if (!Array.isArray(value) || value.length > 32) return false;
|
|
@@ -2143,27 +2647,78 @@ function exactOptional(value, required, optional) {
|
|
|
2143
2647
|
|
|
2144
2648
|
// host/relay/src/relay-admission-store.ts
|
|
2145
2649
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
2146
|
-
import { join as
|
|
2650
|
+
import { join as join3 } from "node:path";
|
|
2651
|
+
|
|
2652
|
+
// host/relay/src/durable-atomic-replacement.ts
|
|
2653
|
+
import { lstat as lstat2, mkdir as mkdir2, open as open2, rename, unlink } from "node:fs/promises";
|
|
2654
|
+
import { dirname as dirname3 } from "node:path";
|
|
2655
|
+
|
|
2656
|
+
// host/windows-rename-retry.ts
|
|
2657
|
+
import { setTimeout as setTimeout2 } from "node:timers/promises";
|
|
2658
|
+
var RETRY_WINDOW_MS = 1e4;
|
|
2659
|
+
var RETRY_START_DELAY_MS = 100;
|
|
2660
|
+
var RETRY_MAX_DELAY_MS = 2e3;
|
|
2661
|
+
var RETRYABLE_CODES = /* @__PURE__ */ new Set(["EBUSY", "EPERM", "EACCES"]);
|
|
2662
|
+
var WindowsRenameRetryError = class extends Error {
|
|
2663
|
+
code = "rename_unavailable";
|
|
2664
|
+
constructor(target, attempts, options = {}) {
|
|
2665
|
+
super(`Windows did not permit renaming into place within the bounded retry window: ${target} (after ${attempts} attempts). Wait for the process holding the path to release it, then rerun the same install or update command.`, options);
|
|
2666
|
+
this.name = "WindowsRenameRetryError";
|
|
2667
|
+
}
|
|
2668
|
+
};
|
|
2669
|
+
function resolveRetrySchedule(tuning) {
|
|
2670
|
+
return {
|
|
2671
|
+
windowMs: tuning.windowMs ?? RETRY_WINDOW_MS,
|
|
2672
|
+
startDelayMs: tuning.startDelayMs ?? RETRY_START_DELAY_MS,
|
|
2673
|
+
maxDelayMs: tuning.maxDelayMs ?? RETRY_MAX_DELAY_MS,
|
|
2674
|
+
sleep: tuning.sleep ?? ((ms) => setTimeout2(ms)),
|
|
2675
|
+
now: tuning.now ?? (() => Date.now())
|
|
2676
|
+
};
|
|
2677
|
+
}
|
|
2678
|
+
async function renameWithWindowsRetry(operation, tuning) {
|
|
2679
|
+
const schedule = resolveRetrySchedule(tuning);
|
|
2680
|
+
const deadline = schedule.now() + schedule.windowMs;
|
|
2681
|
+
let delayMs = schedule.startDelayMs;
|
|
2682
|
+
let attempts = 0;
|
|
2683
|
+
for (; ; ) {
|
|
2684
|
+
attempts += 1;
|
|
2685
|
+
try {
|
|
2686
|
+
await operation();
|
|
2687
|
+
return;
|
|
2688
|
+
} catch (error) {
|
|
2689
|
+
if (tuning.platform !== "win32" || !isRetryableRenameError(error)) throw error;
|
|
2690
|
+
await sleepWithinWindow(schedule, deadline, delayMs, () => new WindowsRenameRetryError(tuning.target, attempts, { cause: error }));
|
|
2691
|
+
delayMs = Math.min(delayMs * 2, schedule.maxDelayMs);
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
async function sleepWithinWindow(schedule, deadline, delayMs, exhausted) {
|
|
2696
|
+
if (schedule.now() >= deadline) throw exhausted();
|
|
2697
|
+
await schedule.sleep(Math.min(delayMs, deadline - schedule.now()));
|
|
2698
|
+
if (schedule.now() >= deadline) throw exhausted();
|
|
2699
|
+
}
|
|
2700
|
+
function isRetryableRenameError(error) {
|
|
2701
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" && RETRYABLE_CODES.has(error.code);
|
|
2702
|
+
}
|
|
2147
2703
|
|
|
2148
2704
|
// host/relay/src/durable-atomic-replacement.ts
|
|
2149
|
-
import { O_RDONLY } from "node:constants";
|
|
2150
|
-
import { lstat, mkdir, open as open2, rename, unlink } from "node:fs/promises";
|
|
2151
|
-
import { dirname } from "node:path";
|
|
2152
2705
|
var DEFAULT_PORT = {
|
|
2153
2706
|
platform: process.platform,
|
|
2154
|
-
mkdir: (path, options) =>
|
|
2155
|
-
lstat: (path) =>
|
|
2707
|
+
mkdir: (path, options) => mkdir2(path, options),
|
|
2708
|
+
lstat: (path) => lstat2(path),
|
|
2156
2709
|
open_file: (path) => open2(path, "wx", 384),
|
|
2157
2710
|
rename,
|
|
2158
2711
|
unlink,
|
|
2159
|
-
open_directory: (path) => open2(path
|
|
2712
|
+
open_directory: (path) => open2(path),
|
|
2160
2713
|
unique_ref: () => `${process.pid}.${crypto.randomUUID()}`
|
|
2161
2714
|
};
|
|
2162
2715
|
async function durableAtomicReplace(target, data, port = DEFAULT_PORT) {
|
|
2163
|
-
const parent =
|
|
2716
|
+
const parent = dirname3(target);
|
|
2164
2717
|
await port.mkdir(parent, { recursive: true, mode: 448 });
|
|
2165
2718
|
const parentStats = await port.lstat(parent);
|
|
2166
|
-
|
|
2719
|
+
const expectedUid = typeof process.geteuid === "function" ? process.geteuid() : parentStats.uid;
|
|
2720
|
+
const ownerPrivate = port.platform === "win32" || isOwnedBy(parentStats.uid, expectedUid) && isPrivateFileMode(parentStats.mode, port.platform);
|
|
2721
|
+
if (!parentStats.isDirectory() || parentStats.isSymbolicLink() || !ownerPrivate) throw new TypeError(`Host Relay durable-state directory is unsafe: ${refusalFacts([{ name: "parent_stats", value: parentStats, expected: "an owner-private directory" }, { name: "platform", value: port.platform }])}.`);
|
|
2167
2722
|
const temporary = `${target}.${port.unique_ref()}.new`;
|
|
2168
2723
|
let staged;
|
|
2169
2724
|
const failures = [];
|
|
@@ -2174,7 +2729,7 @@ async function durableAtomicReplace(target, data, port = DEFAULT_PORT) {
|
|
|
2174
2729
|
const closing = staged;
|
|
2175
2730
|
staged = void 0;
|
|
2176
2731
|
await closing.close();
|
|
2177
|
-
await port.rename(temporary, target);
|
|
2732
|
+
await renameWithWindowsRetry(() => port.rename(temporary, target), { ...port.rename_retry, platform: port.platform, target });
|
|
2178
2733
|
await syncParentDirectory(parent, port);
|
|
2179
2734
|
} catch (error) {
|
|
2180
2735
|
failures.push(error);
|
|
@@ -2200,11 +2755,6 @@ async function syncParentDirectory(path, port) {
|
|
|
2200
2755
|
}
|
|
2201
2756
|
if (failure !== void 0) throw failure;
|
|
2202
2757
|
}
|
|
2203
|
-
function ownerPrivate(stats, platform) {
|
|
2204
|
-
if (platform === "win32") return true;
|
|
2205
|
-
const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
|
|
2206
|
-
return stats.uid === uid && (stats.mode & 63) === 0;
|
|
2207
|
-
}
|
|
2208
2758
|
async function closeStaged(staged, failures) {
|
|
2209
2759
|
if (staged !== void 0) try {
|
|
2210
2760
|
await staged.close();
|
|
@@ -2230,249 +2780,10 @@ function errorCode3(error) {
|
|
|
2230
2780
|
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
|
|
2231
2781
|
}
|
|
2232
2782
|
|
|
2233
|
-
// host/relay/src/relay-request-binding-store.ts
|
|
2234
|
-
import { O_NOFOLLOW, O_RDONLY as O_RDONLY2 } from "node:constants";
|
|
2235
|
-
import { open as open3 } from "node:fs/promises";
|
|
2236
|
-
import { join as join2 } from "node:path";
|
|
2237
|
-
var HOST_RELAY_REQUEST_BINDING_SCHEMA = "ceal.host_relay_request_binding.v1";
|
|
2238
|
-
var STATE_SCHEMA = "ceal.host_relay_request_binding_state.v1";
|
|
2239
|
-
var SAFE_REF3 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
2240
|
-
var MAX_BINDINGS = 8192;
|
|
2241
|
-
var MAX_STATE_BYTES = 8 * 1024 * 1024;
|
|
2242
|
-
var HostRelayBindingError = class extends TypeError {
|
|
2243
|
-
name = "HostRelayBindingError";
|
|
2244
|
-
code;
|
|
2245
|
-
constructor(code, message) {
|
|
2246
|
-
super(message);
|
|
2247
|
-
this.code = code;
|
|
2248
|
-
}
|
|
2249
|
-
};
|
|
2250
|
-
var RelayRequestBindingStore = class {
|
|
2251
|
-
tail = Promise.resolve();
|
|
2252
|
-
bindingWaiters = /* @__PURE__ */ new Set();
|
|
2253
|
-
root;
|
|
2254
|
-
constructor(root) {
|
|
2255
|
-
if (typeof root !== "string" || root.length === 0) throw new TypeError("Host Relay binding root is invalid.");
|
|
2256
|
-
this.root = root;
|
|
2257
|
-
}
|
|
2258
|
-
bind(input, now = /* @__PURE__ */ new Date()) {
|
|
2259
|
-
const run = this.tail.then(() => this.bindLocked(input, now));
|
|
2260
|
-
this.tail = run.then(() => void 0, () => void 0);
|
|
2261
|
-
return run;
|
|
2262
|
-
}
|
|
2263
|
-
query(input) {
|
|
2264
|
-
const run = this.tail.then(() => this.queryLocked(input));
|
|
2265
|
-
this.tail = run.then(() => void 0, () => void 0);
|
|
2266
|
-
return run;
|
|
2267
|
-
}
|
|
2268
|
-
waitForBinding(input, signal) {
|
|
2269
|
-
validateQuery(input);
|
|
2270
|
-
if (signal.aborted) return Promise.reject(new Error("Host Relay binding wait cancelled."));
|
|
2271
|
-
return new Promise((resolve, reject) => {
|
|
2272
|
-
let settled = false;
|
|
2273
|
-
const finish = (result2, error) => {
|
|
2274
|
-
if (settled) return;
|
|
2275
|
-
settled = true;
|
|
2276
|
-
this.bindingWaiters.delete(waiter);
|
|
2277
|
-
signal.removeEventListener("abort", waiter.abort);
|
|
2278
|
-
if (error !== void 0) reject(error);
|
|
2279
|
-
else if (result2?.status === "ready") resolve(result2);
|
|
2280
|
-
};
|
|
2281
|
-
const abort = () => finish(void 0, new Error("Host Relay binding wait cancelled."));
|
|
2282
|
-
const waiter = { query: input, resolve: (result2) => finish(result2), abort };
|
|
2283
|
-
this.bindingWaiters.add(waiter);
|
|
2284
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
2285
|
-
void this.query(input).then((result2) => {
|
|
2286
|
-
if (result2.status === "ready") finish(result2);
|
|
2287
|
-
}, (error) => finish(void 0, error));
|
|
2288
|
-
});
|
|
2289
|
-
}
|
|
2290
|
-
async bindLocked(input, now) {
|
|
2291
|
-
const candidate = bindingFromGateway(input, now);
|
|
2292
|
-
const state = await this.read();
|
|
2293
|
-
const existing = state.bindings.find((item) => sameCanonicalIdentity(item, candidate));
|
|
2294
|
-
if (existing !== void 0) {
|
|
2295
|
-
if (!sameBinding(existing, candidate)) throw new HostRelayBindingError("canonical_turn_rebound", `Host Relay canonical turn is already bound to another Request: ${refusalFacts([{ name: "candidate_request_ref", value: candidate.request_ref, expected: existing.request_ref }, { name: "candidate_prompt_ref", value: candidate.prompt_ref, expected: existing.prompt_ref }, { name: "candidate_context_expires_at", value: candidate.context_expires_at, expected: String(existing.context_expires_at) }, { name: "candidate_local_turn_aliases", value: candidate.local_turn_aliases, expected: JSON.stringify(existing.local_turn_aliases) }])}.`);
|
|
2296
|
-
return existing;
|
|
2297
|
-
}
|
|
2298
|
-
if (state.bindings.length >= MAX_BINDINGS) throw new HostRelayBindingError("binding_capacity_exhausted", `Host Relay request binding capacity is exhausted: ${refusalFact("binding_count", state.bindings.length, `below ${MAX_BINDINGS}`)}.`);
|
|
2299
|
-
const next = { schema_version: STATE_SCHEMA, bindings: [...state.bindings, candidate] };
|
|
2300
|
-
await this.write(next);
|
|
2301
|
-
this.resolveBindingWaiters(candidate);
|
|
2302
|
-
return structuredClone(candidate);
|
|
2303
|
-
}
|
|
2304
|
-
resolveBindingWaiters(binding) {
|
|
2305
|
-
for (const waiter of this.bindingWaiters) {
|
|
2306
|
-
if (relayRequestBindingMatchesQuery(waiter.query, binding)) waiter.resolve({ status: "ready", binding: structuredClone(binding) });
|
|
2307
|
-
}
|
|
2308
|
-
}
|
|
2309
|
-
async queryLocked(input) {
|
|
2310
|
-
validateQuery(input);
|
|
2311
|
-
const state = await this.read();
|
|
2312
|
-
const matches = state.bindings.filter((binding2) => relayRequestBindingMatchesQuery(input, binding2));
|
|
2313
|
-
if (matches.length === 0) return { status: "no_binding" };
|
|
2314
|
-
const binding = matches[0];
|
|
2315
|
-
if (binding === void 0) throw new TypeError("Host Relay binding query result is invalid.");
|
|
2316
|
-
return { status: "ready", binding: structuredClone(binding) };
|
|
2317
|
-
}
|
|
2318
|
-
async read() {
|
|
2319
|
-
let handle;
|
|
2320
|
-
try {
|
|
2321
|
-
handle = await open3(join2(this.root, "request-bindings.json"), O_RDONLY2 | O_NOFOLLOW);
|
|
2322
|
-
const stats = await handle.stat();
|
|
2323
|
-
if (!stats.isFile() || !ownerPrivate2(stats)) throw new TypeError(`Host Relay binding state is unsafe: ${refusalFacts([{ name: "state_is_file", value: stats.isFile(), expected: "true" }, { name: "state_uid", value: stats.uid, expected: String(process.geteuid?.() ?? stats.uid) }, { name: "state_mode", value: (stats.mode & 511).toString(8), expected: "no group or other permission bits" }])}.`);
|
|
2324
|
-
if (stats.size > MAX_STATE_BYTES) throw new TypeError(`Host Relay binding state is unsafe: ${refusalFact("state_bytes", stats.size, `at most ${MAX_STATE_BYTES}`)}.`);
|
|
2325
|
-
return decodeState(JSON.parse(await handle.readFile("utf8")));
|
|
2326
|
-
} catch (error) {
|
|
2327
|
-
if (errorCode4(error) === "ENOENT") return { schema_version: STATE_SCHEMA, bindings: [] };
|
|
2328
|
-
throw error;
|
|
2329
|
-
} finally {
|
|
2330
|
-
await handle?.close();
|
|
2331
|
-
}
|
|
2332
|
-
}
|
|
2333
|
-
async write(state) {
|
|
2334
|
-
const encoded = JSON.stringify(state);
|
|
2335
|
-
if (Buffer.byteLength(encoded, "utf8") > MAX_STATE_BYTES) throw new TypeError(`Host Relay binding state is too large: ${refusalFact("state_bytes", Buffer.byteLength(encoded, "utf8"), `at most ${MAX_STATE_BYTES}`)}.`);
|
|
2336
|
-
await durableAtomicReplace(join2(this.root, "request-bindings.json"), encoded);
|
|
2337
|
-
}
|
|
2338
|
-
};
|
|
2339
|
-
function relayRequestWorkContextFromBinding(binding) {
|
|
2340
|
-
return createRelayRequestWorkContext({ request_ref: binding.request_ref, host_binding: {
|
|
2341
|
-
host_source_ref: binding.host_source_ref,
|
|
2342
|
-
host_session_ref: binding.host_session_ref,
|
|
2343
|
-
host_turn_ref: binding.host_turn_ref,
|
|
2344
|
-
host_input_ref: binding.host_input_ref,
|
|
2345
|
-
prompt_ref: binding.prompt_ref
|
|
2346
|
-
} });
|
|
2347
|
-
}
|
|
2348
|
-
function createRelayRequestWorkContext(input) {
|
|
2349
|
-
if (!record2(input) || !exactKeys3(input, ["request_ref", "host_binding"]) || !safeRef2(input.request_ref) || !record2(input.host_binding)) {
|
|
2350
|
-
throw new HostRelayBindingError("context_identity_missing", "Host Relay work context does not contain the complete acknowledged Host tuple.");
|
|
2351
|
-
}
|
|
2352
|
-
const hostBinding = input.host_binding;
|
|
2353
|
-
if (!exactKeys3(hostBinding, ["host_source_ref", "host_session_ref", "host_turn_ref", "host_input_ref", "prompt_ref"]) || ![hostBinding.host_source_ref, hostBinding.host_session_ref, hostBinding.host_turn_ref, hostBinding.host_input_ref, hostBinding.prompt_ref].every(safeRef2)) {
|
|
2354
|
-
throw new HostRelayBindingError("context_identity_missing", "Host Relay work context does not contain the complete acknowledged Host tuple.");
|
|
2355
|
-
}
|
|
2356
|
-
const hostSourceRef = requiredRef(hostBinding.host_source_ref);
|
|
2357
|
-
const hostSessionRef = requiredRef(hostBinding.host_session_ref);
|
|
2358
|
-
const hostTurnRef = requiredRef(hostBinding.host_turn_ref);
|
|
2359
|
-
const hostInputRef = requiredRef(hostBinding.host_input_ref);
|
|
2360
|
-
const promptRef = requiredRef(hostBinding.prompt_ref);
|
|
2361
|
-
return Object.freeze({ request_ref: input.request_ref, host_binding: Object.freeze({
|
|
2362
|
-
host_source_ref: hostSourceRef,
|
|
2363
|
-
host_session_ref: hostSessionRef,
|
|
2364
|
-
host_turn_ref: hostTurnRef,
|
|
2365
|
-
host_input_ref: hostInputRef,
|
|
2366
|
-
prompt_ref: promptRef
|
|
2367
|
-
}) });
|
|
2368
|
-
}
|
|
2369
|
-
function bindingFromGateway(input, now) {
|
|
2370
|
-
const payload = input.frame.payload;
|
|
2371
|
-
const candidate = {
|
|
2372
|
-
schema_version: HOST_RELAY_REQUEST_BINDING_SCHEMA,
|
|
2373
|
-
producer_id: input.frame.producer_id,
|
|
2374
|
-
producer_installation_ref: input.frame.producer_installation_ref,
|
|
2375
|
-
local_session_ref: requiredRef(payload.local_session_ref),
|
|
2376
|
-
local_turn_ref: requiredRef(payload.local_turn_ref),
|
|
2377
|
-
local_turn_aliases: optionalRefs2(payload.local_turn_aliases),
|
|
2378
|
-
prompt_ref: requiredRef(input.prompt_ref),
|
|
2379
|
-
request_ref: requiredRef(input.request_ref),
|
|
2380
|
-
bound_at: timestamp2(now.toISOString()),
|
|
2381
|
-
...payload.context_expires_at === void 0 ? {} : { context_expires_at: timestamp2(payload.context_expires_at) },
|
|
2382
|
-
host_source_ref: requiredRef(input.host_binding.host_source_ref),
|
|
2383
|
-
host_session_ref: requiredRef(input.host_binding.host_session_ref),
|
|
2384
|
-
host_turn_ref: requiredRef(input.host_binding.host_turn_ref),
|
|
2385
|
-
host_input_ref: requiredRef(input.host_binding.host_input_ref)
|
|
2386
|
-
};
|
|
2387
|
-
if (input.host_binding.prompt_ref !== input.prompt_ref) throw new HostRelayBindingError("context_identity_mismatch", "Host Relay binding prompt reference does not match the acknowledged submission.");
|
|
2388
|
-
validateBinding(candidate);
|
|
2389
|
-
return candidate;
|
|
2390
|
-
}
|
|
2391
|
-
function decodeState(value) {
|
|
2392
|
-
if (!record2(value) || !exactKeys3(value, ["schema_version", "bindings"])) throw new TypeError(`Host Relay binding state is invalid: ${refusalFact("state_keys", record2(value) ? Object.keys(value) : [], "exactly schema_version and bindings")}.`);
|
|
2393
|
-
if (value.schema_version !== STATE_SCHEMA) throw new TypeError(`Host Relay binding state is invalid: ${refusalFact("schema_version", value.schema_version, STATE_SCHEMA)}.`);
|
|
2394
|
-
if (!Array.isArray(value.bindings)) throw new TypeError(`Host Relay binding state is invalid: ${refusalFact("bindings", value.bindings, "an array")}.`);
|
|
2395
|
-
const bindings = value.bindings.map((item) => {
|
|
2396
|
-
validateBinding(item);
|
|
2397
|
-
return item;
|
|
2398
|
-
});
|
|
2399
|
-
const keys = bindings.map(bindingKey);
|
|
2400
|
-
if (new Set(keys).size !== keys.length) throw new TypeError("Host Relay binding state contains duplicate canonical turns.");
|
|
2401
|
-
return { schema_version: STATE_SCHEMA, bindings };
|
|
2402
|
-
}
|
|
2403
|
-
function validateBinding(value) {
|
|
2404
|
-
if (!record2(value) || !exactOptionalKeys(value, ["schema_version", "producer_id", "producer_installation_ref", "local_session_ref", "local_turn_ref", "local_turn_aliases", "prompt_ref", "request_ref", "bound_at", "host_source_ref", "host_session_ref", "host_turn_ref", "host_input_ref"], ["context_expires_at"]) || !validBindingIdentity(value) || !validBindingRequest(value)) throw new TypeError("Host Relay request binding is invalid.");
|
|
2405
|
-
}
|
|
2406
|
-
function validBindingIdentity(value) {
|
|
2407
|
-
return value.schema_version === HOST_RELAY_REQUEST_BINDING_SCHEMA && producer(value.producer_id) && [value.producer_installation_ref, value.local_session_ref, value.local_turn_ref].every(safeRef2) && refList(value.local_turn_aliases);
|
|
2408
|
-
}
|
|
2409
|
-
function validBindingRequest(value) {
|
|
2410
|
-
const tuple = [value.host_source_ref, value.host_session_ref, value.host_turn_ref, value.host_input_ref];
|
|
2411
|
-
return safeRef2(value.prompt_ref) && safeRef2(value.request_ref) && validTimestamp(value.bound_at) && (value.context_expires_at === void 0 || validTimestamp(value.context_expires_at)) && tuple.every(safeRef2);
|
|
2412
|
-
}
|
|
2413
|
-
function validateQuery(value) {
|
|
2414
|
-
if (!record2(value) || !exactKeys3(value, ["producer_id", "producer_installation_ref", "local_session_ref", "local_turn_ref"]) || !producer(value.producer_id) || !safeRef2(value.producer_installation_ref) || !safeRef2(value.local_session_ref) || !safeRef2(value.local_turn_ref)) throw new TypeError("Host Relay binding query is invalid.");
|
|
2415
|
-
}
|
|
2416
|
-
function relayRequestBindingMatchesQuery(query, binding) {
|
|
2417
|
-
return query.producer_id === binding.producer_id && query.local_session_ref === binding.local_session_ref && (query.local_turn_ref === binding.local_turn_ref || binding.local_turn_aliases.includes(query.local_turn_ref)) && query.producer_installation_ref === binding.producer_installation_ref;
|
|
2418
|
-
}
|
|
2419
|
-
function sameCanonicalIdentity(left, right) {
|
|
2420
|
-
return bindingKey(left) === bindingKey(right);
|
|
2421
|
-
}
|
|
2422
|
-
function sameBinding(left, right) {
|
|
2423
|
-
return left.request_ref === right.request_ref && left.prompt_ref === right.prompt_ref && left.context_expires_at === right.context_expires_at && left.host_source_ref === right.host_source_ref && left.host_session_ref === right.host_session_ref && left.host_turn_ref === right.host_turn_ref && left.host_input_ref === right.host_input_ref && JSON.stringify(left.local_turn_aliases) === JSON.stringify(right.local_turn_aliases);
|
|
2424
|
-
}
|
|
2425
|
-
function bindingKey(value) {
|
|
2426
|
-
return `${value.producer_id}\0${value.producer_installation_ref}\0${value.local_session_ref}\0${value.local_turn_ref}`;
|
|
2427
|
-
}
|
|
2428
|
-
function requiredRef(value) {
|
|
2429
|
-
if (!safeRef2(value)) throw new TypeError("Host Relay binding reference is invalid.");
|
|
2430
|
-
return value;
|
|
2431
|
-
}
|
|
2432
|
-
function optionalRefs2(value) {
|
|
2433
|
-
if (value === void 0) return [];
|
|
2434
|
-
if (!refList(value)) throw new TypeError("Host Relay binding aliases are invalid.");
|
|
2435
|
-
return [...value];
|
|
2436
|
-
}
|
|
2437
|
-
function timestamp2(value) {
|
|
2438
|
-
if (!validTimestamp(value)) throw new TypeError("Host Relay binding timestamp is invalid.");
|
|
2439
|
-
return value;
|
|
2440
|
-
}
|
|
2441
|
-
function producer(value) {
|
|
2442
|
-
return value === "codex" || value === "claude";
|
|
2443
|
-
}
|
|
2444
|
-
function safeRef2(value) {
|
|
2445
|
-
return typeof value === "string" && SAFE_REF3.test(value);
|
|
2446
|
-
}
|
|
2447
|
-
function refList(value) {
|
|
2448
|
-
return Array.isArray(value) && value.length <= 16 && value.every(safeRef2) && new Set(value).size === value.length;
|
|
2449
|
-
}
|
|
2450
|
-
function validTimestamp(value) {
|
|
2451
|
-
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
2452
|
-
}
|
|
2453
|
-
function record2(value) {
|
|
2454
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2455
|
-
}
|
|
2456
|
-
function exactKeys3(value, keys) {
|
|
2457
|
-
return Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
2458
|
-
}
|
|
2459
|
-
function exactOptionalKeys(value, required, optional) {
|
|
2460
|
-
const keys = Object.keys(value);
|
|
2461
|
-
return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => required.includes(key) || optional.includes(key));
|
|
2462
|
-
}
|
|
2463
|
-
function errorCode4(error) {
|
|
2464
|
-
return record2(error) && typeof error.code === "string" ? error.code : null;
|
|
2465
|
-
}
|
|
2466
|
-
function ownerPrivate2(stats) {
|
|
2467
|
-
if (process.platform === "win32") return true;
|
|
2468
|
-
const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
|
|
2469
|
-
return stats.uid === uid && (stats.mode & 63) === 0;
|
|
2470
|
-
}
|
|
2471
|
-
|
|
2472
2783
|
// host/relay/src/relay-quarantine-store.ts
|
|
2473
|
-
import { createHash as
|
|
2784
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
2474
2785
|
import { readFile } from "node:fs/promises";
|
|
2475
|
-
import { join as
|
|
2786
|
+
import { join as join2 } from "node:path";
|
|
2476
2787
|
var RELAY_QUARANTINE_SCHEMA = "ceal.host_relay_quarantine_state.v1";
|
|
2477
2788
|
var RELAY_QUARANTINE_MAX_RECORDS = 512;
|
|
2478
2789
|
var RELAY_QUARANTINE_MAX_BYTES = 16 * 1024 * 1024;
|
|
@@ -2492,8 +2803,8 @@ var RelayQuarantineStore = class {
|
|
|
2492
2803
|
if (!Number.isSafeInteger(this.maxRecords) || this.maxRecords < 1 || this.maxRecords > RELAY_QUARANTINE_MAX_RECORDS) throw new TypeError(`Relay quarantine record cap is invalid: ${refusalFact("max_records", this.maxRecords, `between 1 and ${RELAY_QUARANTINE_MAX_RECORDS}`)}.`);
|
|
2493
2804
|
if (!Number.isSafeInteger(this.maxBytes) || this.maxBytes < 1 || this.maxBytes > RELAY_QUARANTINE_MAX_BYTES) throw new TypeError(`Relay quarantine byte cap is invalid: ${refusalFact("max_bytes", this.maxBytes, `between 1 and ${RELAY_QUARANTINE_MAX_BYTES}`)}.`);
|
|
2494
2805
|
}
|
|
2495
|
-
async retainFrame(frame, reason,
|
|
2496
|
-
await this.retain(encodeHostProducerFrame(frame), frame.producer_id, reason, capturedAt
|
|
2806
|
+
async retainFrame(frame, reason, capturedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2807
|
+
await this.retain(encodeHostProducerFrame(frame), frame.producer_id, reason, capturedAt);
|
|
2497
2808
|
}
|
|
2498
2809
|
async retainMalformed(rawFrame, reason, capturedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2499
2810
|
await this.retain(rawFrame, inferProducer(rawFrame), reason, capturedAt);
|
|
@@ -2505,53 +2816,37 @@ var RelayQuarantineStore = class {
|
|
|
2505
2816
|
const records = state.producers[producerId].slice(-Math.min(limit, this.maxRecords)).map((record6) => structuredClone(record6));
|
|
2506
2817
|
return { producer_id: producerId, records, total_bytes: records.reduce((total, record6) => total + record6.charged_bytes, 0) };
|
|
2507
2818
|
}
|
|
2508
|
-
/**
|
|
2509
|
-
* The newest quarantined record whose exact-turn attribution matches the
|
|
2510
|
-
* query, or null when evicted, unattributed, or for another turn. The
|
|
2511
|
-
* file-backed state survives drain, restart, and overwrite.
|
|
2512
|
-
*/
|
|
2513
|
-
async findNewestAttributedMatch(query) {
|
|
2514
|
-
await this.tail;
|
|
2515
|
-
const state = await this.readState();
|
|
2516
|
-
const bucket = query.producer_id === "codex" || query.producer_id === "claude" ? state.producers[query.producer_id] : [];
|
|
2517
|
-
for (let index = bucket.length - 1; index >= 0; index -= 1) {
|
|
2518
|
-
const record6 = bucket[index];
|
|
2519
|
-
if (record6 !== void 0 && attributedRecordMatchesQuery(record6, query)) return structuredClone(record6);
|
|
2520
|
-
}
|
|
2521
|
-
return null;
|
|
2522
|
-
}
|
|
2523
2819
|
async snapshot() {
|
|
2524
2820
|
await this.tail;
|
|
2525
2821
|
return this.readState();
|
|
2526
2822
|
}
|
|
2527
|
-
async retain(rawFrame, producerId, reason, capturedAt
|
|
2823
|
+
async retain(rawFrame, producerId, reason, capturedAt) {
|
|
2528
2824
|
const run = this.tail.then(async () => {
|
|
2529
2825
|
if (!PRODUCERS2.includes(producerId) || !Number.isFinite(Date.parse(capturedAt))) throw new TypeError("Relay quarantine record identity is invalid.");
|
|
2530
2826
|
const raw = Buffer.from(rawFrame).subarray(0, MAX_CAPTURE_BYTES);
|
|
2531
2827
|
const state = await this.readState();
|
|
2532
2828
|
const record6 = {
|
|
2533
|
-
quarantine_ref: `quarantine:${
|
|
2829
|
+
quarantine_ref: `quarantine:${createHash5("sha256").update(`${producerId}\0${reason}\0${capturedAt}\0`).update(raw).digest("hex")}`,
|
|
2534
2830
|
producer_id: producerId,
|
|
2535
2831
|
reason,
|
|
2536
2832
|
captured_at: capturedAt,
|
|
2537
|
-
frame_sha256:
|
|
2833
|
+
frame_sha256: createHash5("sha256").update(raw).digest("hex"),
|
|
2538
2834
|
charged_bytes: raw.byteLength,
|
|
2539
|
-
raw_frame_base64: raw.toString("base64")
|
|
2540
|
-
...attribution === void 0 ? {} : { attribution: structuredClone(attribution) }
|
|
2835
|
+
raw_frame_base64: raw.toString("base64")
|
|
2541
2836
|
};
|
|
2542
2837
|
const records = state.producers[producerId];
|
|
2543
2838
|
records.push(record6);
|
|
2544
2839
|
while (records.length > this.maxRecords || totalBytes(records) > this.maxBytes) records.shift();
|
|
2545
|
-
await durableAtomicReplace(
|
|
2840
|
+
await durableAtomicReplace(join2(this.root, "quarantine.json"), JSON.stringify(state));
|
|
2546
2841
|
});
|
|
2547
2842
|
this.tail = run.then(() => void 0, () => void 0);
|
|
2548
2843
|
await run;
|
|
2549
2844
|
}
|
|
2550
2845
|
async readState() {
|
|
2551
2846
|
try {
|
|
2552
|
-
return
|
|
2847
|
+
return decodeState(JSON.parse(await readFile(join2(this.root, "quarantine.json"), "utf8")));
|
|
2553
2848
|
} catch (error) {
|
|
2554
|
-
if (
|
|
2849
|
+
if (errorCode4(error) === "ENOENT") return emptyState();
|
|
2555
2850
|
throw error;
|
|
2556
2851
|
}
|
|
2557
2852
|
}
|
|
@@ -2562,9 +2857,9 @@ function emptyState() {
|
|
|
2562
2857
|
function totalBytes(records) {
|
|
2563
2858
|
return records.reduce((total, record6) => total + record6.charged_bytes, 0);
|
|
2564
2859
|
}
|
|
2565
|
-
function
|
|
2566
|
-
if (!
|
|
2567
|
-
if (!
|
|
2860
|
+
function decodeState(value) {
|
|
2861
|
+
if (!record2(value) || value.schema_version !== RELAY_QUARANTINE_SCHEMA) throw new TypeError(`Host Relay quarantine state is invalid: ${refusalFact("schema_version", record2(value) ? value.schema_version : void 0, RELAY_QUARANTINE_SCHEMA)}.`);
|
|
2862
|
+
if (!record2(value.producers)) throw new TypeError(`Host Relay quarantine state is invalid: ${refusalFact("producer_ids", record2(value.producers) ? Object.keys(value.producers) : [], `the ${PRODUCERS2.length} quarantine producer buckets`)}.`);
|
|
2568
2863
|
const state = emptyState();
|
|
2569
2864
|
for (const producerId of PRODUCERS2) {
|
|
2570
2865
|
const records = value.producers[producerId];
|
|
@@ -2574,8 +2869,7 @@ function decodeState2(value) {
|
|
|
2574
2869
|
return state;
|
|
2575
2870
|
}
|
|
2576
2871
|
function validRecord(value) {
|
|
2577
|
-
|
|
2578
|
-
return value.attribution === void 0 || validAttribution(value.attribution);
|
|
2872
|
+
return record2(value) && validRecordIdentity(value);
|
|
2579
2873
|
}
|
|
2580
2874
|
function validRecordIdentity(value) {
|
|
2581
2875
|
return typeof value.quarantine_ref === "string" && quarantineProducer(value.producer_id) && validReason(value.reason) && Number.isFinite(Date.parse(String(value.captured_at))) && typeof value.frame_sha256 === "string" && typeof value.raw_frame_base64 === "string" && validChargedBytes(value.charged_bytes);
|
|
@@ -2586,52 +2880,22 @@ function validReason(value) {
|
|
|
2586
2880
|
function validChargedBytes(value) {
|
|
2587
2881
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
2588
2882
|
}
|
|
2589
|
-
var ATTRIBUTION_REQUIRED_KEYS = ["producer_id", "producer_installation_ref", "local_session_ref", "local_turn_ref", "local_turn_aliases", "sequence", "frame_sha256"];
|
|
2590
|
-
var ATTRIBUTION_OPTIONAL_KEYS = ["diagnostic"];
|
|
2591
|
-
function validAttribution(value) {
|
|
2592
|
-
if (!record3(value) || !hasExactKeys2(value, ATTRIBUTION_REQUIRED_KEYS, ATTRIBUTION_OPTIONAL_KEYS)) return false;
|
|
2593
|
-
return validAttributionIdentity(value) && validAttributionAliases(value.local_turn_aliases) && validAttributionEnvelope(value) && (value.diagnostic === void 0 || validAttributionDiagnostic(value.diagnostic));
|
|
2594
|
-
}
|
|
2595
|
-
function validAttributionIdentity(value) {
|
|
2596
|
-
return (value.producer_id === "codex" || value.producer_id === "claude") && isHostSafeRef(value.producer_installation_ref) && isHostSafeRef(value.local_session_ref) && isHostSafeRef(value.local_turn_ref);
|
|
2597
|
-
}
|
|
2598
|
-
function validAttributionAliases(value) {
|
|
2599
|
-
return Array.isArray(value) && value.length <= 16 && value.every(isHostSafeRef);
|
|
2600
|
-
}
|
|
2601
|
-
function validAttributionEnvelope(value) {
|
|
2602
|
-
return typeof value.sequence === "number" && Number.isSafeInteger(value.sequence) && value.sequence > 0 && typeof value.frame_sha256 === "string";
|
|
2603
|
-
}
|
|
2604
|
-
function validAttributionDiagnostic(value) {
|
|
2605
|
-
if (!record3(value) || !hasExactKeys2(value, [], RELAY_DELIVERY_DIAGNOSTIC_KEYS)) return false;
|
|
2606
|
-
return isUndefinedOr(value.relayCode, isRelayGatewayErrorCode) && isUndefinedOr(value.gatewayStatus, isSafeInteger) && isUndefinedOr(value.gatewayCode, isBoundedRefusalWord) && isUndefinedOr(value.gatewayReason, isBoundedRefusalWord) && isUndefinedOr(value.exhausted, isBoolean);
|
|
2607
|
-
}
|
|
2608
|
-
function isSafeInteger(value) {
|
|
2609
|
-
return Number.isSafeInteger(value);
|
|
2610
|
-
}
|
|
2611
|
-
function isBoolean(value) {
|
|
2612
|
-
return typeof value === "boolean";
|
|
2613
|
-
}
|
|
2614
|
-
function attributedRecordMatchesQuery(recordValue, query) {
|
|
2615
|
-
const attribution = recordValue.attribution;
|
|
2616
|
-
if (attribution === void 0) return false;
|
|
2617
|
-
return attribution.producer_id === query.producer_id && attribution.producer_installation_ref === query.producer_installation_ref && attribution.local_session_ref === query.local_session_ref && (attribution.local_turn_ref === query.local_turn_ref || attribution.local_turn_aliases.includes(query.local_turn_ref));
|
|
2618
|
-
}
|
|
2619
2883
|
function quarantineProducer(value) {
|
|
2620
2884
|
return value === "codex" || value === "claude" || value === "unknown";
|
|
2621
2885
|
}
|
|
2622
2886
|
function inferProducer(rawFrame) {
|
|
2623
2887
|
try {
|
|
2624
2888
|
const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(Buffer.from(rawFrame).subarray(4)));
|
|
2625
|
-
return
|
|
2889
|
+
return record2(value) && (value.producer_id === "codex" || value.producer_id === "claude") ? value.producer_id : "unknown";
|
|
2626
2890
|
} catch {
|
|
2627
2891
|
return "unknown";
|
|
2628
2892
|
}
|
|
2629
2893
|
}
|
|
2630
|
-
function
|
|
2894
|
+
function record2(value) {
|
|
2631
2895
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2632
2896
|
}
|
|
2633
|
-
function
|
|
2634
|
-
return
|
|
2897
|
+
function errorCode4(error) {
|
|
2898
|
+
return record2(error) && typeof error.code === "string" ? error.code : null;
|
|
2635
2899
|
}
|
|
2636
2900
|
|
|
2637
2901
|
// host/relay/src/relay-admission-store.ts
|
|
@@ -2641,12 +2905,11 @@ var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
|
|
|
2641
2905
|
var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
|
|
2642
2906
|
var RelayAdmissionStore = class {
|
|
2643
2907
|
tail = Promise.resolve();
|
|
2644
|
-
drainWaiters = /* @__PURE__ */ new Set();
|
|
2645
2908
|
root;
|
|
2646
2909
|
quarantine;
|
|
2647
2910
|
limits;
|
|
2648
2911
|
faultHooks;
|
|
2649
|
-
constructor(root, limits = DEFAULT_LIMITS, faultHooks = {}, quarantine = new RelayQuarantineStore(
|
|
2912
|
+
constructor(root, limits = DEFAULT_LIMITS, faultHooks = {}, quarantine = new RelayQuarantineStore(join3(root, "quarantine"))) {
|
|
2650
2913
|
this.root = root;
|
|
2651
2914
|
this.quarantine = quarantine;
|
|
2652
2915
|
this.limits = limits;
|
|
@@ -2661,36 +2924,10 @@ var RelayAdmissionStore = class {
|
|
|
2661
2924
|
await this.tail;
|
|
2662
2925
|
return this.read();
|
|
2663
2926
|
}
|
|
2664
|
-
async hasPendingRequestBinding(query) {
|
|
2665
|
-
const state = await this.read();
|
|
2666
|
-
return state.entries.some((entry2) => pendingFrameMatchesBindingQuery(entry2, query));
|
|
2667
|
-
}
|
|
2668
2927
|
async drainRound(deliver, options) {
|
|
2669
2928
|
const run = this.tail.then(() => this.drainLocked(deliver, options));
|
|
2670
2929
|
this.tail = run.then(() => void 0, () => void 0);
|
|
2671
|
-
|
|
2672
|
-
if (result2.delivered.length > 0 || result2.refused.length > 0 || result2.retained.length > 0) this.resolveDrainWaiters();
|
|
2673
|
-
return result2;
|
|
2674
|
-
}
|
|
2675
|
-
waitForDrainChange(signal) {
|
|
2676
|
-
if (signal.aborted) return Promise.reject(new Error("Host Relay admission wait cancelled."));
|
|
2677
|
-
return new Promise((resolve, reject) => {
|
|
2678
|
-
let settled = false;
|
|
2679
|
-
const finish = (error) => {
|
|
2680
|
-
if (settled) return;
|
|
2681
|
-
settled = true;
|
|
2682
|
-
this.drainWaiters.delete(waiter);
|
|
2683
|
-
signal.removeEventListener("abort", waiter.abort);
|
|
2684
|
-
if (error !== void 0) reject(error);
|
|
2685
|
-
else resolve();
|
|
2686
|
-
};
|
|
2687
|
-
const waiter = { resolve: () => finish(), abort: () => finish(new Error("Host Relay admission wait cancelled.")) };
|
|
2688
|
-
this.drainWaiters.add(waiter);
|
|
2689
|
-
signal.addEventListener("abort", waiter.abort, { once: true });
|
|
2690
|
-
});
|
|
2691
|
-
}
|
|
2692
|
-
resolveDrainWaiters() {
|
|
2693
|
-
for (const waiter of this.drainWaiters) waiter.resolve();
|
|
2930
|
+
return run;
|
|
2694
2931
|
}
|
|
2695
2932
|
async admitLocked(frame) {
|
|
2696
2933
|
const state = await this.read();
|
|
@@ -2702,11 +2939,11 @@ var RelayAdmissionStore = class {
|
|
|
2702
2939
|
const hash = hostProducerFrameHash(frame);
|
|
2703
2940
|
const replay = replayCode(existing, hash, frame.sequence, installation2);
|
|
2704
2941
|
if (replay !== null) {
|
|
2705
|
-
if (replay === "frame_identity_conflict") await this.quarantine.retainFrame(frame, replay
|
|
2942
|
+
if (replay === "frame_identity_conflict") await this.quarantine.retainFrame(frame, replay);
|
|
2706
2943
|
return result(replay, installation2);
|
|
2707
2944
|
}
|
|
2708
2945
|
if (frame.sequence !== installation2.admitted_through_sequence + 1) {
|
|
2709
|
-
await this.quarantine.retainFrame(frame, "sequence_gap"
|
|
2946
|
+
await this.quarantine.retainFrame(frame, "sequence_gap");
|
|
2710
2947
|
return result("sequence_gap", installation2);
|
|
2711
2948
|
}
|
|
2712
2949
|
const chargedBytes = 4 + Buffer.byteLength(JSON.stringify(frame), "utf8");
|
|
@@ -2753,7 +2990,7 @@ var RelayAdmissionStore = class {
|
|
|
2753
2990
|
if (!installation2) throw new TypeError("Host Relay delivery installation is unavailable.");
|
|
2754
2991
|
if (outcome.status === "refused" || exhausted) {
|
|
2755
2992
|
const diagnostic = exhaustedDiagnostic(outcome, exhausted);
|
|
2756
|
-
await this.quarantine.retainFrame(head.frame, "gateway_refused"
|
|
2993
|
+
await this.quarantine.retainFrame(head.frame, "gateway_refused");
|
|
2757
2994
|
installation2.last_refusal = { sequence: head.sequence, submission_ref: input.submission_ref, ...refusalCause(outcome, head), ...diagnostic === void 0 ? {} : { diagnostic }, refused_at_ms: options.now_ms };
|
|
2758
2995
|
refused.push(input.submission_ref);
|
|
2759
2996
|
} else {
|
|
@@ -2784,31 +3021,19 @@ var RelayAdmissionStore = class {
|
|
|
2784
3021
|
}
|
|
2785
3022
|
async read() {
|
|
2786
3023
|
try {
|
|
2787
|
-
return
|
|
3024
|
+
return decodeState2(JSON.parse(await readFile2(join3(this.root, "admission.json"), "utf8")));
|
|
2788
3025
|
} catch (error) {
|
|
2789
|
-
if (
|
|
3026
|
+
if (errorCode5(error) === "ENOENT") return { schema_version: "ceal.host_relay_admission_state.v1", installations: {}, entries: [] };
|
|
2790
3027
|
throw error;
|
|
2791
3028
|
}
|
|
2792
3029
|
}
|
|
2793
3030
|
async write(state) {
|
|
2794
|
-
await durableAtomicReplace(
|
|
3031
|
+
await durableAtomicReplace(join3(this.root, "admission.json"), JSON.stringify(state));
|
|
2795
3032
|
}
|
|
2796
3033
|
};
|
|
2797
3034
|
function installationKey(frame) {
|
|
2798
3035
|
return `${frame.producer_id}\0${frame.producer_installation_ref}`;
|
|
2799
3036
|
}
|
|
2800
|
-
function pendingFrameMatchesBindingQuery(entry2, query) {
|
|
2801
|
-
const selector = extractHostBindingSelector(entry2.frame);
|
|
2802
|
-
if (selector === null) return false;
|
|
2803
|
-
return relayRequestBindingMatchesQuery(query, selector);
|
|
2804
|
-
}
|
|
2805
|
-
function attributionForFrame(frame, hash, sequence, diagnostic) {
|
|
2806
|
-
const selector = extractHostBindingSelector(frame);
|
|
2807
|
-
if (selector === null) return void 0;
|
|
2808
|
-
if (frame.producer_id !== selector.producer_id || frame.producer_installation_ref !== selector.producer_installation_ref || frame.sequence !== sequence) return void 0;
|
|
2809
|
-
if (hostProducerFrameHash(frame) !== hash) return void 0;
|
|
2810
|
-
return { ...selector, sequence, frame_sha256: hash, ...diagnostic === void 0 ? {} : { diagnostic } };
|
|
2811
|
-
}
|
|
2812
3037
|
function exhaustedDiagnostic(outcome, exhausted) {
|
|
2813
3038
|
if (outcome.status !== "retryable" && outcome.status !== "refused") return void 0;
|
|
2814
3039
|
if (outcome.diagnostic !== void 0) return exhausted ? { ...outcome.diagnostic, exhausted: true } : outcome.diagnostic;
|
|
@@ -2826,6 +3051,7 @@ function retryExhausted(outcome, entry2) {
|
|
|
2826
3051
|
return outcome.status === "retryable" && entry2.retry_attempts >= MAX_RETRYABLE_ATTEMPTS;
|
|
2827
3052
|
}
|
|
2828
3053
|
function refusalCause(outcome, entry2) {
|
|
3054
|
+
if (outcome.status === "refused" && outcome.code === "no_binding") return { code: "gateway_refused", message: "Gateway refused the Host producer frame." };
|
|
2829
3055
|
if (outcome.status === "refused") return { code: outcome.code, message: outcome.message };
|
|
2830
3056
|
const reason = outcome.status === "retryable" ? outcome.reason ?? "no reason reported" : "no reason reported";
|
|
2831
3057
|
return { code: "retry_exhausted", message: `Gateway delivery stayed retryable through ${entry2.retry_attempts} attempts: ${reason}` };
|
|
@@ -2839,10 +3065,10 @@ function scheduleRetry(entry2, nowMs, reason) {
|
|
|
2839
3065
|
function result(code, state) {
|
|
2840
3066
|
return { code, admitted_through_sequence: state.admitted_through_sequence, delivered_through_sequence: state.delivered_through_sequence };
|
|
2841
3067
|
}
|
|
2842
|
-
function
|
|
3068
|
+
function errorCode5(error) {
|
|
2843
3069
|
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
|
|
2844
3070
|
}
|
|
2845
|
-
function
|
|
3071
|
+
function decodeState2(value) {
|
|
2846
3072
|
if (!stateRecord(value)) throw new TypeError("Host Relay admission state is invalid.");
|
|
2847
3073
|
const installations = {};
|
|
2848
3074
|
for (const [key, item] of Object.entries(value.installations)) {
|
|
@@ -2869,18 +3095,18 @@ function refusalRecord(value) {
|
|
|
2869
3095
|
}
|
|
2870
3096
|
function validDiagnostic(value) {
|
|
2871
3097
|
if (!object(value) || !hasExactKeys2(value, [], RELAY_DELIVERY_DIAGNOSTIC_KEYS)) return false;
|
|
2872
|
-
return isUndefinedOr(value.relayCode, isRelayGatewayErrorCode) && isUndefinedOr(value.gatewayStatus,
|
|
3098
|
+
return isUndefinedOr(value.relayCode, isRelayGatewayErrorCode) && isUndefinedOr(value.gatewayStatus, isSafeInteger) && isUndefinedOr(value.gatewayCode, isBoundedRefusalWord) && isUndefinedOr(value.gatewayReason, isBoundedRefusalWord) && isUndefinedOr(value.exhausted, isBoolean);
|
|
2873
3099
|
}
|
|
2874
|
-
function
|
|
3100
|
+
function isSafeInteger(value) {
|
|
2875
3101
|
return Number.isSafeInteger(value);
|
|
2876
3102
|
}
|
|
2877
|
-
function
|
|
3103
|
+
function isBoolean(value) {
|
|
2878
3104
|
return typeof value === "boolean";
|
|
2879
3105
|
}
|
|
2880
3106
|
function entry(value) {
|
|
2881
|
-
return object(value) &&
|
|
3107
|
+
return object(value) && producer(value.producer_id) && typeof value.producer_installation_ref === "string" && Number.isSafeInteger(value.sequence) && typeof value.hash === "string" && Number.isSafeInteger(value.charged_bytes) && object(value.frame) && Number.isSafeInteger(value.retry_attempts) && Number.isSafeInteger(value.retry_at_ms);
|
|
2882
3108
|
}
|
|
2883
|
-
function
|
|
3109
|
+
function producer(value) {
|
|
2884
3110
|
return value === "codex" || value === "claude";
|
|
2885
3111
|
}
|
|
2886
3112
|
function object(value) {
|
|
@@ -2910,10 +3136,10 @@ function poisonEnvelopeMatches(entryValue, installation2) {
|
|
|
2910
3136
|
return poisonEnvelopeSchema(frame) && poisonEnvelopeProducer(frame, entryValue) && poisonEnvelopeInstallation(frame, entryValue) && poisonEnvelopeEpoch(frame, installation2) && poisonEnvelopeSequence(frame, entryValue) && poisonEnvelopeKind(frame) && poisonEnvelopeTimestamp(frame);
|
|
2911
3137
|
}
|
|
2912
3138
|
function poisonEnvelopeSchema(frame) {
|
|
2913
|
-
return frame.schema_version ===
|
|
3139
|
+
return frame.schema_version === HOST_PRODUCER_FRAME_SCHEMA;
|
|
2914
3140
|
}
|
|
2915
3141
|
function poisonEnvelopeProducer(frame, entryValue) {
|
|
2916
|
-
return
|
|
3142
|
+
return producer(frame.producer_id) && frame.producer_id === entryValue.producer_id;
|
|
2917
3143
|
}
|
|
2918
3144
|
function poisonEnvelopeInstallation(frame, entryValue) {
|
|
2919
3145
|
return opaqueHostSourceRef(frame.producer_installation_ref) && frame.producer_installation_ref === entryValue.producer_installation_ref;
|
|
@@ -2941,674 +3167,275 @@ function poisonSequenceInRange(entryValue, installation2) {
|
|
|
2941
3167
|
return entryValue.sequence <= installation2.admitted_through_sequence;
|
|
2942
3168
|
}
|
|
2943
3169
|
|
|
2944
|
-
// host/relay/src/relay-
|
|
2945
|
-
import {
|
|
2946
|
-
import {
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
var
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
var
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
this.
|
|
2965
|
-
this.gateway = options.gateway;
|
|
2966
|
-
this.timeoutMs = options.timeout_ms ?? 5e3;
|
|
2967
|
-
this.onRequestBound = options.on_request_bound ?? (() => void 0);
|
|
2968
|
-
}
|
|
2969
|
-
deliver(input) {
|
|
2970
|
-
const run = this.tail.then(() => this.deliverLocked(input));
|
|
2971
|
-
this.tail = run.then(() => void 0, () => void 0);
|
|
2972
|
-
return run;
|
|
3170
|
+
// host/relay/src/relay-pipe-handshake.ts
|
|
3171
|
+
import { createHmac, randomBytes } from "node:crypto";
|
|
3172
|
+
import { constants as constants2 } from "node:fs";
|
|
3173
|
+
import { link, unlink as unlink2 } from "node:fs/promises";
|
|
3174
|
+
import { dirname as dirname4, join as join4 } from "node:path";
|
|
3175
|
+
var RELAY_PIPE_HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
3176
|
+
var RELAY_PIPE_KEY_BYTES = 32;
|
|
3177
|
+
var RELAY_PIPE_KEY_FILENAME = "relay-pipe.key";
|
|
3178
|
+
var RELAY_PIPE_NONCE_BYTES = 32;
|
|
3179
|
+
var RELAY_PIPE_NONCE_PATTERN = /^[A-Za-z0-9_-]{43}$/u;
|
|
3180
|
+
var RELAY_PIPE_HELLO_SCHEMA = "ceal.host_relay_pipe_hello.v1";
|
|
3181
|
+
var RELAY_PIPE_CHALLENGE_SCHEMA = "ceal.host_relay_pipe_challenge.v1";
|
|
3182
|
+
var RELAY_PIPE_RESPONSE_SCHEMA = "ceal.host_relay_pipe_response.v1";
|
|
3183
|
+
var RELAY_PIPE_FRAME_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
3184
|
+
var RELAY_PIPE_FRAME_MAX_BYTES = 1024;
|
|
3185
|
+
var RelayPipeHandshakeError = class extends Error {
|
|
3186
|
+
name = "RelayPipeHandshakeError";
|
|
3187
|
+
code;
|
|
3188
|
+
constructor(code, message) {
|
|
3189
|
+
super(message);
|
|
3190
|
+
this.code = code;
|
|
2973
3191
|
}
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
3192
|
+
};
|
|
3193
|
+
function relayPipeKeyPath(relayStateRoot) {
|
|
3194
|
+
return join4(relayStateRoot, RELAY_PIPE_KEY_FILENAME);
|
|
3195
|
+
}
|
|
3196
|
+
async function loadRelayPipeKey(relayStateRoot, platform = process.platform, custody = {}) {
|
|
3197
|
+
const path = relayPipeKeyPath(relayStateRoot);
|
|
3198
|
+
if (platform === "win32") return await loadWindowsRelayPipeKey(path, custody);
|
|
3199
|
+
const existing = await readRelayPipeKey(path, platform, custody);
|
|
3200
|
+
if (existing !== null) return existing;
|
|
3201
|
+
return await createPosixRelayPipeKey(path, platform, custody);
|
|
3202
|
+
}
|
|
3203
|
+
async function loadWindowsRelayPipeKey(path, custody) {
|
|
3204
|
+
const existing = await readRelayPipeKey(path, "win32", custody);
|
|
3205
|
+
if (existing !== null) return existing;
|
|
3206
|
+
try {
|
|
3207
|
+
await createRandomPrivateFileWithSecurefs(path, RELAY_PIPE_KEY_BYTES, custody);
|
|
3208
|
+
} catch (error) {
|
|
3209
|
+
if (errorCode6(error) === "EEXIST") return await readWinnerPipeKey(path, "win32", custody);
|
|
3210
|
+
throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key is unavailable at ${path}; delete it and restart the Host Relay.`);
|
|
2981
3211
|
}
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
3212
|
+
return await readWinnerPipeKey(path, "win32", custody);
|
|
3213
|
+
}
|
|
3214
|
+
async function createPosixRelayPipeKey(path, platform, custody) {
|
|
3215
|
+
const key = randomBytes(RELAY_PIPE_KEY_BYTES);
|
|
3216
|
+
const staging = `${path}.${randomBytes(8).toString("hex")}.tmp`;
|
|
3217
|
+
let handle;
|
|
3218
|
+
try {
|
|
3219
|
+
handle = await openNoFollow(staging, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL, 384);
|
|
3220
|
+
} catch (error) {
|
|
3221
|
+
if (errorCode6(error) !== "EEXIST") throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key is unavailable at ${path}.`);
|
|
3222
|
+
return await readWinnerPipeKey(path, platform, custody);
|
|
2988
3223
|
}
|
|
2989
|
-
|
|
2990
|
-
const
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
...existing?.request_ref === void 0 ? {} : { request_ref: existing.request_ref },
|
|
2997
|
-
...existing?.prompt_ref === void 0 ? {} : { prompt_ref: existing.prompt_ref },
|
|
2998
|
-
...existing?.origin_host_input_ref === void 0 ? {} : { origin_host_input_ref: existing.origin_host_input_ref },
|
|
2999
|
-
...existing?.input_refused === void 0 ? {} : { input_refused: existing.input_refused }
|
|
3000
|
-
};
|
|
3001
|
-
await this.writeState({ ...state, turns: { ...state.turns, [refs.host_turn_ref]: nextTurn } });
|
|
3002
|
-
return { status: "created" };
|
|
3224
|
+
try {
|
|
3225
|
+
const stats = await handle.stat();
|
|
3226
|
+
if (!stats.isFile() || !isSelfOwned(stats.uid) || !isPrivateFileMode(stats.mode)) throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key at ${path} is not a private regular file owned by the current user.`);
|
|
3227
|
+
await handle.writeFile(key);
|
|
3228
|
+
await handle.sync();
|
|
3229
|
+
} finally {
|
|
3230
|
+
await handle.close();
|
|
3003
3231
|
}
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
if (
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
return deliveryRefusal(outcome.refusal);
|
|
3012
|
-
}
|
|
3013
|
-
const acknowledgement2 = outcome.acknowledgement;
|
|
3014
|
-
const nextTurn = bindInput(prepared, acknowledgement2.request_ref, input.submission_ref);
|
|
3015
|
-
await this.writeState({ ...state, turns: { ...state.turns, [prepared.refs.host_turn_ref]: nextTurn } });
|
|
3016
|
-
if (!bindsOriginInput(prepared)) return { status: "created" };
|
|
3017
|
-
const bindingRequestRef = nextTurn.request_ref ?? acknowledgement2.request_ref;
|
|
3018
|
-
const bindingPromptRef = nextTurn.prompt_ref ?? prepared.promptRef;
|
|
3019
|
-
const bindingInputRef = nextTurn.origin_host_input_ref ?? prepared.hostInputRef;
|
|
3020
|
-
const binding = { frame: structuredClone(input.frame), request_ref: bindingRequestRef, prompt_ref: bindingPromptRef, host_binding: {
|
|
3021
|
-
host_source_ref: prepared.submission.identity.host_source_ref,
|
|
3022
|
-
host_session_ref: prepared.submission.identity.host_session_ref,
|
|
3023
|
-
host_turn_ref: prepared.submission.identity.host_turn_ref,
|
|
3024
|
-
host_input_ref: bindingInputRef,
|
|
3025
|
-
prompt_ref: bindingPromptRef
|
|
3026
|
-
} };
|
|
3027
|
-
const context = createRelayRequestWorkContext({ request_ref: bindingRequestRef, host_binding: binding.host_binding });
|
|
3028
|
-
try {
|
|
3029
|
-
await this.onRequestBound(binding, context);
|
|
3030
|
-
} catch (error) {
|
|
3031
|
-
if (error instanceof HostRelayBindingError) return { status: "refused", code: error.code, message: error.message };
|
|
3032
|
-
return { status: "retryable", reason: `binding_store: ${errorMessage(error)}` };
|
|
3033
|
-
}
|
|
3034
|
-
return { status: "created" };
|
|
3035
|
-
}
|
|
3036
|
-
async deliverTerminal(state, input) {
|
|
3037
|
-
const refs = frameRefs(input.frame, this.hostSourceRef);
|
|
3038
|
-
const turn = state.turns[refs.host_turn_ref];
|
|
3039
|
-
if (turn === void 0) return { status: "refused", code: "origin_turn_unknown", message: "The terminal frame names a turn whose start was never admitted." };
|
|
3040
|
-
if (turn.request_ref === void 0 || turn.prompt_ref === void 0 || turn.origin_host_input_ref === void 0) {
|
|
3041
|
-
if (turn.input_refused !== void 0) return { status: "refused", code: "origin_input_refused", message: `The terminal frame names a turn whose input was refused (${turn.input_refused.code}: ${turn.input_refused.message}).` };
|
|
3042
|
-
return { status: "retryable", reason: "origin_turn_unbound" };
|
|
3043
|
-
}
|
|
3044
|
-
const outcome = await this.submit({
|
|
3045
|
-
identity: { host_source_ref: this.hostSourceRef, host_session_ref: refs.host_session_ref, host_turn_ref: refs.host_turn_ref },
|
|
3046
|
-
host_input_ref: turn.origin_host_input_ref,
|
|
3047
|
-
input_kind: "host_automatic",
|
|
3048
|
-
prompt_ref: turn.prompt_ref,
|
|
3049
|
-
observations: payloadObservations(input.frame),
|
|
3050
|
-
request_ref: turn.request_ref,
|
|
3051
|
-
submission_ref: input.submission_ref
|
|
3052
|
-
});
|
|
3053
|
-
return "refusal" in outcome ? deliveryRefusal(outcome.refusal) : { status: "created" };
|
|
3054
|
-
}
|
|
3055
|
-
async submit(input) {
|
|
3056
|
-
const controller2 = new AbortController();
|
|
3057
|
-
const timeout = setTimeout(() => controller2.abort(), this.timeoutMs);
|
|
3058
|
-
try {
|
|
3059
|
-
const acknowledgement2 = await this.gateway.submitHostTurn(input, controller2.signal);
|
|
3060
|
-
return SAFE_REF4.test(acknowledgement2.request_ref) && isHostGatewayAcknowledgementDisposition(acknowledgement2.disposition) ? { acknowledgement: acknowledgement2 } : { refusal: { code: "invalid_response", message: "Gateway acknowledgement identity is invalid.", definitive: false } };
|
|
3061
|
-
} catch (error) {
|
|
3062
|
-
return { refusal: submissionRefusal(error) };
|
|
3063
|
-
} finally {
|
|
3064
|
-
clearTimeout(timeout);
|
|
3065
|
-
}
|
|
3066
|
-
}
|
|
3067
|
-
async readState() {
|
|
3068
|
-
try {
|
|
3069
|
-
const state = decodeState4(JSON.parse(await readFile3(this.statePath, "utf8")));
|
|
3070
|
-
if (state.host_source_ref !== this.hostSourceRef) throw new TypeError(`Host Relay Gateway source binding changed: ${refusalFacts([{ name: "stored_host_source_ref", value: state.host_source_ref }, { name: "configured_host_source_ref", value: this.hostSourceRef }])}.`);
|
|
3071
|
-
return state;
|
|
3072
|
-
} catch (error) {
|
|
3073
|
-
if (errorCode7(error) === "ENOENT") return { schema_version: STATE_SCHEMA2, host_source_ref: this.hostSourceRef, sessions: {}, turns: {} };
|
|
3074
|
-
throw error;
|
|
3075
|
-
}
|
|
3076
|
-
}
|
|
3077
|
-
async writeState(state) {
|
|
3078
|
-
await durableAtomicReplace(this.statePath, JSON.stringify(state));
|
|
3232
|
+
try {
|
|
3233
|
+
await link(staging, path);
|
|
3234
|
+
} catch (error) {
|
|
3235
|
+
if (errorCode6(error) !== "EEXIST") throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key is unavailable at ${path}.`);
|
|
3236
|
+
return await readWinnerPipeKey(path, platform, custody);
|
|
3237
|
+
} finally {
|
|
3238
|
+
await unlink2(staging).catch(() => void 0);
|
|
3079
3239
|
}
|
|
3080
|
-
|
|
3081
|
-
var RETRYABLE_HTTP_STATUSES = /* @__PURE__ */ new Set([401, 403, 408, 429]);
|
|
3082
|
-
function submissionRefusal(error) {
|
|
3083
|
-
const status = isRecord(error) && typeof error.status === "number" ? error.status : void 0;
|
|
3084
|
-
const code = errorCode7(error) ?? "gateway_unavailable";
|
|
3085
|
-
const definitive = code === "payload_rejected" || status !== void 0 && status >= 400 && status < 500 && !RETRYABLE_HTTP_STATUSES.has(status);
|
|
3086
|
-
return { code, message: errorMessage(error), definitive, diagnostic: typedDiagnostic(error, status) };
|
|
3087
|
-
}
|
|
3088
|
-
function typedDiagnostic(error, status) {
|
|
3089
|
-
if (!isRecord(error)) return status === void 0 ? {} : { gatewayStatus: status };
|
|
3090
|
-
const gatewayCode = isBoundedRefusalWord(error.error_code) ? error.error_code : void 0;
|
|
3091
|
-
const gatewayReason = isBoundedRefusalWord(error.refusal_reason) ? error.refusal_reason : void 0;
|
|
3092
|
-
return {
|
|
3093
|
-
...typeof error.code === "string" ? { relayCode: error.code } : {},
|
|
3094
|
-
...status === void 0 ? {} : { gatewayStatus: status },
|
|
3095
|
-
...gatewayCode === void 0 ? {} : { gatewayCode },
|
|
3096
|
-
...gatewayReason === void 0 ? {} : { gatewayReason }
|
|
3097
|
-
};
|
|
3240
|
+
return Buffer.from(key);
|
|
3098
3241
|
}
|
|
3099
|
-
function
|
|
3100
|
-
const
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
function errorMessage(error) {
|
|
3104
|
-
return error instanceof Error ? error.message : String(error);
|
|
3105
|
-
}
|
|
3106
|
-
function frameRefs(frame, hostSourceRef) {
|
|
3107
|
-
const localSessionRef = payloadRef(frame, "local_session_ref");
|
|
3108
|
-
const localTurnRef = payloadRef(frame, "local_turn_ref");
|
|
3109
|
-
const hostSessionRef = hostProducerSessionRef(hostSourceRef, frame, localSessionRef);
|
|
3110
|
-
return { local_session_ref: localSessionRef, local_turn_ref: localTurnRef, host_session_ref: hostSessionRef, host_turn_ref: hostProducerTurnRef(hostSourceRef, frame, localSessionRef, localTurnRef) };
|
|
3111
|
-
}
|
|
3112
|
-
function prepareInput(state, input, hostSourceRef) {
|
|
3113
|
-
const refs = frameRefs(input.frame, hostSourceRef);
|
|
3114
|
-
const turn = state.turns[refs.host_turn_ref];
|
|
3115
|
-
const promptRef = turn?.prompt_ref ?? hostProducerPromptRef(payloadPromptDigest(input.frame));
|
|
3116
|
-
const hostInputRef = hostProducerInputRef(hostSourceRef, input.frame, refs.local_session_ref, refs.local_turn_ref, payloadRef(input.frame, "local_input_ref"));
|
|
3117
|
-
const observations = uniqueObservations([...state.sessions[refs.host_session_ref]?.observations ?? [], ...turn?.observations ?? [], ...payloadObservations(input.frame)]);
|
|
3118
|
-
const submission = compactSubmission({
|
|
3119
|
-
identity: { host_source_ref: hostSourceRef, host_session_ref: refs.host_session_ref, host_turn_ref: refs.host_turn_ref },
|
|
3120
|
-
host_input_ref: hostInputRef,
|
|
3121
|
-
input_kind: payloadInputKind(input.frame),
|
|
3122
|
-
prompt_ref: promptRef,
|
|
3123
|
-
observations,
|
|
3124
|
-
request_ref: turn?.request_ref,
|
|
3125
|
-
submission_ref: input.submission_ref
|
|
3126
|
-
});
|
|
3127
|
-
return { refs, turn, promptRef, hostInputRef, submission };
|
|
3242
|
+
async function readWinnerPipeKey(path, platform, custody) {
|
|
3243
|
+
const winner = await readRelayPipeKey(path, platform, custody);
|
|
3244
|
+
if (winner === null) throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key is unavailable at ${path}.`);
|
|
3245
|
+
return winner;
|
|
3128
3246
|
}
|
|
3129
|
-
function
|
|
3130
|
-
return
|
|
3131
|
-
|
|
3132
|
-
observations: prepared.turn?.observations ?? [],
|
|
3133
|
-
submission_ref: prepared.turn?.submission_ref ?? submissionRef,
|
|
3134
|
-
request_ref: prepared.turn?.request_ref ?? requestRef,
|
|
3135
|
-
prompt_ref: prepared.turn?.prompt_ref ?? prepared.promptRef,
|
|
3136
|
-
origin_host_input_ref: prepared.turn?.origin_host_input_ref ?? prepared.hostInputRef
|
|
3137
|
-
};
|
|
3247
|
+
async function readRelayPipeKey(path, platform, custody) {
|
|
3248
|
+
if (platform === "win32") return await readWindowsRelayPipeKey(path, custody);
|
|
3249
|
+
return await readPosixRelayPipeKey(path);
|
|
3138
3250
|
}
|
|
3139
|
-
function
|
|
3140
|
-
|
|
3141
|
-
return { status: "refused", code: "origin_input_refused", message: `The input frame names a turn whose origin input was refused (${turn.input_refused.code}: ${turn.input_refused.message}).` };
|
|
3142
|
-
}
|
|
3143
|
-
function bindsOriginInput(prepared) {
|
|
3144
|
-
return prepared.turn?.request_ref === void 0 || prepared.turn.origin_host_input_ref === prepared.hostInputRef;
|
|
3145
|
-
}
|
|
3146
|
-
function payloadRef(frame, key) {
|
|
3147
|
-
const value = frame.payload[key];
|
|
3148
|
-
if (typeof value !== "string" || !SAFE_REF4.test(value)) throw new TypeError("Host Relay frame reference is invalid.");
|
|
3149
|
-
return value;
|
|
3150
|
-
}
|
|
3151
|
-
function payloadPromptDigest(frame) {
|
|
3152
|
-
const prompt = payloadObservations(frame).find((observation) => observation.observation_kind === "prompt");
|
|
3153
|
-
if (prompt === void 0 || !isCealHostPromptObservationValue(prompt.value)) throw new TypeError("Host Relay prompt observation is invalid.");
|
|
3154
|
-
return prompt.value.body_sha256;
|
|
3155
|
-
}
|
|
3156
|
-
function payloadObservations(frame) {
|
|
3157
|
-
return Array.isArray(frame.payload.observations) ? frame.payload.observations.filter(isObservation) : [];
|
|
3158
|
-
}
|
|
3159
|
-
function payloadInputKind(frame) {
|
|
3160
|
-
const value = frame.payload.input_kind;
|
|
3161
|
-
if (!isCealHostInputKind(value)) throw new TypeError("Host Relay input kind is invalid.");
|
|
3162
|
-
return value;
|
|
3163
|
-
}
|
|
3164
|
-
function uniqueObservations(values) {
|
|
3165
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3166
|
-
return values.filter((value) => !seen.has(value.observation_ref) && Boolean(seen.add(value.observation_ref)));
|
|
3167
|
-
}
|
|
3168
|
-
function compactSubmission(value) {
|
|
3169
|
-
return { identity: value.identity, host_input_ref: value.host_input_ref, input_kind: value.input_kind, prompt_ref: value.prompt_ref, observations: value.observations, submission_ref: value.submission_ref, ...value.request_ref === void 0 ? {} : { request_ref: value.request_ref } };
|
|
3170
|
-
}
|
|
3171
|
-
function isObservation(value) {
|
|
3251
|
+
async function readWindowsRelayPipeKey(path, custody) {
|
|
3252
|
+
let key;
|
|
3172
3253
|
try {
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3254
|
+
key = await readPrivateFileWithSecurefs(path, RELAY_PIPE_KEY_BYTES, custody);
|
|
3255
|
+
} catch (error) {
|
|
3256
|
+
if (errorCode6(error) === "ENOENT" || errorCode6(error) === "ELOOP") return null;
|
|
3257
|
+
throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key is unavailable at ${path}; delete it and restart the Host Relay.`);
|
|
3177
3258
|
}
|
|
3259
|
+
if (key.byteLength !== RELAY_PIPE_KEY_BYTES) throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key at ${path} has ${key.byteLength} bytes; want ${RELAY_PIPE_KEY_BYTES}. Delete the file and restart the Host Relay.`);
|
|
3260
|
+
return Buffer.from(key);
|
|
3178
3261
|
}
|
|
3179
|
-
function
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
return isRecord(error) && typeof error.code === "string" ? error.code : null;
|
|
3187
|
-
}
|
|
3188
|
-
function decodeState4(value) {
|
|
3189
|
-
if (!isRecord(value) || value.schema_version !== STATE_SCHEMA2) throw new TypeError(`Host Relay Gateway state is invalid: ${refusalFact("schema_version", isRecord(value) ? value.schema_version : void 0, STATE_SCHEMA2)}.`);
|
|
3190
|
-
if (!opaqueHostSourceRef(value.host_source_ref)) throw new TypeError(`Host Relay Gateway state is invalid: ${refusalFact("host_source_ref", value.host_source_ref, "an opaque Host source reference")}.`);
|
|
3191
|
-
if (!isRecord(value.sessions) || !isRecord(value.turns)) throw new TypeError(`Host Relay Gateway state is invalid: ${refusalFacts([{ name: "sessions_is_object", value: isRecord(value.sessions), expected: "true" }, { name: "turns_is_object", value: isRecord(value.turns), expected: "true" }])}.`);
|
|
3192
|
-
return { schema_version: STATE_SCHEMA2, host_source_ref: value.host_source_ref, sessions: decodeSessions(value.sessions), turns: decodeTurns(value.turns) };
|
|
3193
|
-
}
|
|
3194
|
-
function decodeSessions(value) {
|
|
3195
|
-
const result2 = {};
|
|
3196
|
-
for (const [key, item] of Object.entries(value)) {
|
|
3197
|
-
if (!SAFE_REF4.test(key) || !isRecord(item) || !Array.isArray(item.observations) || !item.observations.every(isObservation) || typeof item.submission_ref !== "string") throw new TypeError("Host Relay staged Session is invalid.");
|
|
3198
|
-
result2[key] = { observations: item.observations, submission_ref: item.submission_ref };
|
|
3262
|
+
async function readPosixRelayPipeKey(path) {
|
|
3263
|
+
let handle;
|
|
3264
|
+
try {
|
|
3265
|
+
handle = await openNoFollow(path);
|
|
3266
|
+
} catch (error) {
|
|
3267
|
+
if (errorCode6(error) === "ENOENT" || errorCode6(error) === "ELOOP") return null;
|
|
3268
|
+
throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key is unavailable at ${path}.`);
|
|
3199
3269
|
}
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3270
|
+
try {
|
|
3271
|
+
const stats = await handle.stat();
|
|
3272
|
+
if (!stats.isFile()) throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key at ${path} is not a regular file; delete it and restart the Host Relay.`);
|
|
3273
|
+
if (!isSelfOwned(stats.uid) || !isPrivateFileMode(stats.mode)) throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key at ${path} is not a private file owned by the current user; delete it and restart the Host Relay.`);
|
|
3274
|
+
if (stats.size !== RELAY_PIPE_KEY_BYTES) throw new RelayPipeHandshakeError("pipe_key_unavailable", `Host Relay pipe key at ${path} has ${stats.size} bytes; want ${RELAY_PIPE_KEY_BYTES}. Delete the file and restart the Host Relay.`);
|
|
3275
|
+
return await handle.readFile();
|
|
3276
|
+
} finally {
|
|
3277
|
+
await handle.close();
|
|
3207
3278
|
}
|
|
3208
|
-
return result2;
|
|
3209
3279
|
}
|
|
3210
|
-
function
|
|
3211
|
-
|
|
3212
|
-
return {
|
|
3213
|
-
host_session_ref: item.host_session_ref,
|
|
3214
|
-
observations: item.observations,
|
|
3215
|
-
submission_ref: item.submission_ref,
|
|
3216
|
-
...decodeTurnBinding(item),
|
|
3217
|
-
...isInputRefusal(item.input_refused) ? { input_refused: { code: item.input_refused.code, message: item.input_refused.message } } : {}
|
|
3218
|
-
};
|
|
3280
|
+
function createPipeClientNonce() {
|
|
3281
|
+
return randomBytes(RELAY_PIPE_NONCE_BYTES).toString("base64url");
|
|
3219
3282
|
}
|
|
3220
|
-
function
|
|
3221
|
-
return
|
|
3283
|
+
function pipeProof(key, role, firstNonce, secondNonce) {
|
|
3284
|
+
return createHmac("sha256", key).update(`${role}\0${firstNonce}\0${secondNonce}`, "utf8").digest("hex");
|
|
3222
3285
|
}
|
|
3223
|
-
function
|
|
3224
|
-
return
|
|
3286
|
+
function createPipeServerProof(key, clientNonce, serverNonce) {
|
|
3287
|
+
return pipeProof(key, "server", clientNonce, serverNonce);
|
|
3225
3288
|
}
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
import { O_NOFOLLOW as O_NOFOLLOW2, O_RDONLY as O_RDONLY3 } from "node:constants";
|
|
3229
|
-
import { lstat as lstat2, open as open4 } from "node:fs/promises";
|
|
3230
|
-
import { dirname as dirname2 } from "node:path";
|
|
3231
|
-
|
|
3232
|
-
// host/gateway-http.ts
|
|
3233
|
-
var SAFE_HOST_REF = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
3234
|
-
function hostIngressBody(input, invalidEventTime) {
|
|
3235
|
-
if (!isCealHostInputKind(input.input_kind)) throw new TypeError("Host ingress input kind is invalid.");
|
|
3236
|
-
const prompt = input.observations.find((observation) => observation.observation_kind === "prompt");
|
|
3237
|
-
const observedAt = prompt?.observed_at ?? input.observations[0]?.observed_at;
|
|
3238
|
-
if (observedAt === void 0 || !Number.isFinite(Date.parse(observedAt))) throw invalidEventTime();
|
|
3239
|
-
return {
|
|
3240
|
-
submission_ref: input.submission_ref,
|
|
3241
|
-
host_source_ref: input.identity.host_source_ref,
|
|
3242
|
-
host_session_ref: input.identity.host_session_ref,
|
|
3243
|
-
host_turn_ref: input.identity.host_turn_ref,
|
|
3244
|
-
host_input_ref: input.host_input_ref,
|
|
3245
|
-
input_kind: input.input_kind,
|
|
3246
|
-
prompt_ref: input.prompt_ref,
|
|
3247
|
-
observed_at: observedAt,
|
|
3248
|
-
observations: input.observations,
|
|
3249
|
-
...input.request_ref === void 0 ? {} : { request_ref: input.request_ref }
|
|
3250
|
-
};
|
|
3251
|
-
}
|
|
3252
|
-
async function responseJson(response2, invalidResponse2) {
|
|
3253
|
-
try {
|
|
3254
|
-
return await response2.json();
|
|
3255
|
-
} catch {
|
|
3256
|
-
throw invalidResponse2();
|
|
3257
|
-
}
|
|
3289
|
+
function createPipeClientProof(key, serverNonce, clientNonce) {
|
|
3290
|
+
return pipeProof(key, "client", serverNonce, clientNonce);
|
|
3258
3291
|
}
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
const
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
method: "POST",
|
|
3266
|
-
...options.redirect === void 0 ? {} : { redirect: options.redirect },
|
|
3267
|
-
headers: { authorization: `Bearer ${options.credential}`, "content-type": "application/json" },
|
|
3268
|
-
body,
|
|
3269
|
-
signal: options.signal
|
|
3270
|
-
});
|
|
3271
|
-
} catch {
|
|
3272
|
-
throw options.unavailable();
|
|
3273
|
-
}
|
|
3274
|
-
const responseBody = await responseJson(response2, options.invalidResponse);
|
|
3275
|
-
if (!response2.ok) throw options.refused(response2.status, responseBody);
|
|
3276
|
-
return options.acknowledgement(responseBody);
|
|
3292
|
+
function encodePipeFrame(value) {
|
|
3293
|
+
const body = Buffer.from(JSON.stringify(value), "utf8");
|
|
3294
|
+
const output = Buffer.allocUnsafe(body.length + RELAY_PIPE_FRAME_BYTES);
|
|
3295
|
+
output.writeUInt32BE(body.length, 0);
|
|
3296
|
+
body.copy(output, RELAY_PIPE_FRAME_BYTES);
|
|
3297
|
+
return output;
|
|
3277
3298
|
}
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
"host_access_token",
|
|
3285
|
-
"host_installation_ref",
|
|
3286
|
-
"host_source_ref",
|
|
3287
|
-
"registration_attempt_ref",
|
|
3288
|
-
"schema_version",
|
|
3289
|
-
"status"
|
|
3290
|
-
]);
|
|
3291
|
-
var HOST_REGISTRATION_STATE_MAX_BYTES = 65536;
|
|
3292
|
-
var HostRelayGatewayError = class extends Error {
|
|
3293
|
-
name = "HostRelayGatewayError";
|
|
3294
|
-
code;
|
|
3295
|
-
/** HTTP status of a Gateway refusal, when one answered. */
|
|
3296
|
-
status;
|
|
3297
|
-
/** The Gateway's own refusal word, when it named one; a bounded token, never its message. */
|
|
3298
|
-
error_code;
|
|
3299
|
-
/** The Gateway mint refusal's bounded continuity reason, when it named one; a fixed token, never its message. */
|
|
3300
|
-
refusal_reason;
|
|
3301
|
-
constructor(code, message, status, errorCode9, refusalReason) {
|
|
3302
|
-
super(message);
|
|
3303
|
-
this.code = code;
|
|
3304
|
-
if (status !== void 0) this.status = status;
|
|
3305
|
-
if (errorCode9 !== void 0) this.error_code = errorCode9;
|
|
3306
|
-
if (refusalReason !== void 0) this.refusal_reason = refusalReason;
|
|
3307
|
-
}
|
|
3308
|
-
};
|
|
3309
|
-
var GATEWAY_REFUSAL_CODE_PATTERN = /^[a-z][a-z0-9_]{0,63}$/u;
|
|
3310
|
-
async function gatewayRefusal(response2) {
|
|
3299
|
+
function decodePipeFrame(buffer) {
|
|
3300
|
+
if (buffer.length < RELAY_PIPE_FRAME_BYTES) return null;
|
|
3301
|
+
const length = buffer.readUInt32BE(0);
|
|
3302
|
+
if (length === 0 || length > RELAY_PIPE_FRAME_MAX_BYTES) throw new RelayPipeHandshakeError("pipe_handshake_invalid", `Host Relay pipe handshake frame declares ${length} bytes; want between 1 and ${RELAY_PIPE_FRAME_MAX_BYTES}.`);
|
|
3303
|
+
if (buffer.length < length + RELAY_PIPE_FRAME_BYTES) return null;
|
|
3304
|
+
let value;
|
|
3311
3305
|
try {
|
|
3312
|
-
|
|
3313
|
-
if (!isCealJsonRecord(body)) return {};
|
|
3314
|
-
const code = typeof body.error_code === "string" && GATEWAY_REFUSAL_CODE_PATTERN.test(body.error_code) ? body.error_code : void 0;
|
|
3315
|
-
const reason = code === "request_context_refused" && typeof body.reason === "string" && GATEWAY_REFUSAL_CODE_PATTERN.test(body.reason) && isVocabularyWord(CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS, body.reason) ? body.reason : void 0;
|
|
3316
|
-
return {
|
|
3317
|
-
...code === void 0 ? {} : { code },
|
|
3318
|
-
...reason === void 0 ? {} : { reason }
|
|
3319
|
-
};
|
|
3306
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(RELAY_PIPE_FRAME_BYTES, length + RELAY_PIPE_FRAME_BYTES)));
|
|
3320
3307
|
} catch {
|
|
3321
|
-
|
|
3308
|
+
throw new RelayPipeHandshakeError("pipe_handshake_invalid", "Host Relay pipe handshake frame is not JSON.");
|
|
3322
3309
|
}
|
|
3310
|
+
return { value, rest: buffer.subarray(length + RELAY_PIPE_FRAME_BYTES) };
|
|
3323
3311
|
}
|
|
3324
|
-
function
|
|
3325
|
-
|
|
3326
|
-
return body.error_code;
|
|
3312
|
+
function validNonce(value) {
|
|
3313
|
+
return typeof value === "string" && RELAY_PIPE_NONCE_PATTERN.test(value);
|
|
3327
3314
|
}
|
|
3328
|
-
function
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3315
|
+
function writePipeFrame(socket, frame) {
|
|
3316
|
+
if (socket.write(frame)) return Promise.resolve();
|
|
3317
|
+
return new Promise((resolve, reject) => {
|
|
3318
|
+
socket.once("drain", resolve);
|
|
3319
|
+
socket.once("error", reject);
|
|
3320
|
+
socket.once("close", () => reject(new RelayPipeHandshakeError("pipe_handshake_invalid", "Host Relay pipe handshake connection closed.")));
|
|
3321
|
+
});
|
|
3333
3322
|
}
|
|
3334
|
-
function
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
},
|
|
3359
|
-
async mintInvocationContext(input, signal) {
|
|
3360
|
-
const registration = await readHostRegistrationAuthority(options.registration_state_file, options.host_source_ref);
|
|
3361
|
-
let response2;
|
|
3323
|
+
function readPipeFrames() {
|
|
3324
|
+
let buffer = Buffer.alloc(0);
|
|
3325
|
+
let pending = null;
|
|
3326
|
+
const feed = (chunk) => {
|
|
3327
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
3328
|
+
if (pending === null) return;
|
|
3329
|
+
let frame;
|
|
3330
|
+
try {
|
|
3331
|
+
frame = decodePipeFrame(buffer);
|
|
3332
|
+
} catch (error) {
|
|
3333
|
+
const waiting2 = pending;
|
|
3334
|
+
pending = null;
|
|
3335
|
+
waiting2.reject(error instanceof Error ? error : new RelayPipeHandshakeError("pipe_handshake_invalid", "Host Relay pipe handshake frame is invalid."));
|
|
3336
|
+
return;
|
|
3337
|
+
}
|
|
3338
|
+
if (frame === null) return;
|
|
3339
|
+
buffer = frame.rest;
|
|
3340
|
+
const waiting = pending;
|
|
3341
|
+
pending = null;
|
|
3342
|
+
waiting.resolve({ value: frame.value, rest: Buffer.alloc(0) });
|
|
3343
|
+
};
|
|
3344
|
+
const waiter = {
|
|
3345
|
+
wait: () => new Promise((resolve, reject) => {
|
|
3346
|
+
let frame;
|
|
3362
3347
|
try {
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
headers: { authorization: `Bearer ${registration.host_access_token}`, "content-type": "application/json" },
|
|
3368
|
-
body: JSON.stringify({ schema_version: "ceal.gateway_host_invocation_context_request.v1", ...input })
|
|
3369
|
-
});
|
|
3370
|
-
} catch {
|
|
3371
|
-
throw new HostRelayGatewayError("gateway_unavailable", "Gateway Host invocation context is unavailable.");
|
|
3372
|
-
}
|
|
3373
|
-
if (!response2.ok) {
|
|
3374
|
-
const refusal = await gatewayRefusal(response2);
|
|
3375
|
-
throw new HostRelayGatewayError(response2.status === 401 ? "credential" : "gateway_refused", `Gateway Host invocation context was refused (${response2.status}).`, response2.status, refusal.code, refusal.reason);
|
|
3348
|
+
frame = decodePipeFrame(buffer);
|
|
3349
|
+
} catch (error) {
|
|
3350
|
+
reject(error instanceof Error ? error : new RelayPipeHandshakeError("pipe_handshake_invalid", "Host Relay pipe handshake frame is invalid."));
|
|
3351
|
+
return;
|
|
3376
3352
|
}
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
value
|
|
3380
|
-
|
|
3381
|
-
throw new HostRelayGatewayError("invalid_response", "Gateway Host invocation context response is not JSON.");
|
|
3353
|
+
if (frame !== null) {
|
|
3354
|
+
buffer = frame.rest;
|
|
3355
|
+
resolve({ value: frame.value, rest: Buffer.alloc(0) });
|
|
3356
|
+
return;
|
|
3382
3357
|
}
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3358
|
+
pending = { resolve, reject };
|
|
3359
|
+
}),
|
|
3360
|
+
cancel: (error) => {
|
|
3361
|
+
if (pending !== null) {
|
|
3362
|
+
const waiting = pending;
|
|
3363
|
+
pending = null;
|
|
3364
|
+
waiting.reject(error);
|
|
3387
3365
|
}
|
|
3388
3366
|
}
|
|
3389
|
-
}
|
|
3390
|
-
}
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
}
|
|
3405
|
-
if (hasOriginDecoration(value, parsed)) {
|
|
3406
|
-
throw new HostRelayGatewayError("configuration", `Host Relay Gateway origin is invalid: ${refusalFacts([{ name: "origin", value: parsed.origin }, { name: "has_userinfo", value: parsed.username !== "" || parsed.password !== "", expected: "false" }, { name: "pathname", value: parsed.pathname, expected: "/" }, { name: "search", value: parsed.search, expected: "an empty query" }, { name: "hash", value: parsed.hash, expected: "an empty fragment" }])}.`);
|
|
3407
|
-
}
|
|
3408
|
-
return `${parsed.origin}${path}`;
|
|
3409
|
-
}
|
|
3410
|
-
function isExplicitLoopbackHttp(parsed, hostname) {
|
|
3411
|
-
return parsed.protocol === "http:" && parsed.port !== "" && (hostname === "127.0.0.1" || hostname === "[::1]");
|
|
3412
|
-
}
|
|
3413
|
-
function isSecureHttps(parsed) {
|
|
3414
|
-
return parsed.protocol === "https:";
|
|
3415
|
-
}
|
|
3416
|
-
function validPort(parsed) {
|
|
3417
|
-
if (parsed.port === "") return true;
|
|
3418
|
-
const port = Number(parsed.port);
|
|
3419
|
-
return Number.isInteger(port) && port >= 1 && port <= 65535;
|
|
3367
|
+
};
|
|
3368
|
+
return { waiter, feed, remainder: () => buffer };
|
|
3369
|
+
}
|
|
3370
|
+
function attachHandshakeReader(socket, waiter, feed) {
|
|
3371
|
+
const onData = (chunk) => feed(Buffer.from(chunk));
|
|
3372
|
+
const onError = (error) => waiter.cancel(error);
|
|
3373
|
+
const onClose = () => waiter.cancel(new RelayPipeHandshakeError("pipe_handshake_invalid", "Host Relay pipe handshake connection closed."));
|
|
3374
|
+
socket.on("data", onData);
|
|
3375
|
+
socket.once("error", onError);
|
|
3376
|
+
socket.once("close", onClose);
|
|
3377
|
+
return () => {
|
|
3378
|
+
socket.removeListener("data", onData);
|
|
3379
|
+
socket.removeListener("error", onError);
|
|
3380
|
+
socket.removeListener("close", onClose);
|
|
3381
|
+
};
|
|
3420
3382
|
}
|
|
3421
|
-
function
|
|
3422
|
-
|
|
3423
|
-
const
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3383
|
+
function handshakeTimeout(timeoutMs) {
|
|
3384
|
+
let timer;
|
|
3385
|
+
const done = new Promise((_resolve, reject) => {
|
|
3386
|
+
timer = setTimeout(() => reject(new RelayPipeHandshakeError("pipe_handshake_timeout", `Host Relay pipe handshake stalled past ${timeoutMs} ms; dropping the connection.`)), timeoutMs);
|
|
3387
|
+
timer.unref?.();
|
|
3388
|
+
});
|
|
3389
|
+
return { done, clear: () => clearTimeout(timer) };
|
|
3427
3390
|
}
|
|
3428
|
-
async function
|
|
3429
|
-
|
|
3391
|
+
async function conductPipeServerHandshake(socket, key, timeoutMs = RELAY_PIPE_HANDSHAKE_TIMEOUT_MS) {
|
|
3392
|
+
const bound = handshakeTimeout(timeoutMs);
|
|
3393
|
+
const { waiter, feed, remainder } = readPipeFrames();
|
|
3394
|
+
const detach = attachHandshakeReader(socket, waiter, feed);
|
|
3430
3395
|
try {
|
|
3431
|
-
await
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
if (!stats.isFile() || !ownerPrivate3(stats)) throw new HostRelayGatewayError("credential", "Host Relay credential file is not owner-private.");
|
|
3435
|
-
const raw = await handle.readFile("utf8");
|
|
3436
|
-
if (Buffer.byteLength(raw, "utf8") > HOST_REGISTRATION_STATE_MAX_BYTES) throw new HostRelayGatewayError("credential", `Host registration state is invalid: ${refusalFact("registration_state_bytes", Buffer.byteLength(raw, "utf8"), `at most ${HOST_REGISTRATION_STATE_MAX_BYTES}`)}.`);
|
|
3437
|
-
let value;
|
|
3438
|
-
try {
|
|
3439
|
-
value = JSON.parse(raw);
|
|
3440
|
-
} catch {
|
|
3441
|
-
throw new HostRelayGatewayError("credential", "Host registration state is invalid.");
|
|
3396
|
+
const hello = await Promise.race([waiter.wait(), bound.done]);
|
|
3397
|
+
if (!isRecord2(hello.value) || hello.value.schema_version !== RELAY_PIPE_HELLO_SCHEMA || !validNonce(hello.value.client_nonce)) {
|
|
3398
|
+
throw new RelayPipeHandshakeError("pipe_handshake_invalid", "Host Relay pipe client hello is invalid; dropping the connection without an answer.");
|
|
3442
3399
|
}
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3400
|
+
const clientNonce = hello.value.client_nonce;
|
|
3401
|
+
const serverNonce = createPipeClientNonce();
|
|
3402
|
+
const serverProof = createPipeServerProof(key, clientNonce, serverNonce);
|
|
3403
|
+
await Promise.race([writePipeFrame(socket, encodePipeFrame({ schema_version: RELAY_PIPE_CHALLENGE_SCHEMA, server_nonce: serverNonce, server_proof: serverProof })), bound.done]);
|
|
3404
|
+
const response2 = await Promise.race([waiter.wait(), bound.done]);
|
|
3405
|
+
if (!isRecord2(response2.value) || response2.value.schema_version !== RELAY_PIPE_RESPONSE_SCHEMA || typeof response2.value.client_proof !== "string") {
|
|
3406
|
+
throw new RelayPipeHandshakeError("pipe_handshake_invalid", "Host Relay pipe client proof is invalid; dropping the connection without an answer.");
|
|
3407
|
+
}
|
|
3408
|
+
const expected = createPipeClientProof(key, serverNonce, clientNonce);
|
|
3409
|
+
if (!timingSafeEqualHex(expected, response2.value.client_proof)) {
|
|
3410
|
+
throw new RelayPipeHandshakeError("pipe_handshake_invalid", "Host Relay pipe client proof failed; dropping the connection without an answer.");
|
|
3411
|
+
}
|
|
3412
|
+
return remainder();
|
|
3447
3413
|
} finally {
|
|
3448
|
-
|
|
3414
|
+
bound.clear();
|
|
3415
|
+
detach();
|
|
3449
3416
|
}
|
|
3450
3417
|
}
|
|
3451
|
-
function
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
if (!validRegistrationIdentity(value, expectedHostSourceRef)) throw new HostRelayGatewayError("credential", `Host registration state is invalid or does not match this Relay: ${refusalFacts([{ name: "schema_version", value: value.schema_version, expected: "ceal.host_registration_state.v1" }, { name: "status", value: value.status, expected: "active" }, { name: "host_source_ref", value: value.host_source_ref, expected: expectedHostSourceRef }, { name: "registration_attempt_ref", value: value.registration_attempt_ref, expected: "host-registration-attempt:<uuid>" }, { name: "host_installation_ref", value: value.host_installation_ref, expected: "host-installation:<uuid>" }])}.`);
|
|
3457
|
-
if (!validRegistrationCredential(value)) throw new HostRelayGatewayError("credential", `Host registration state is invalid or does not match this Relay: ${refusalFacts([{ name: "credential_generation", value: value.credential_generation, expected: "a positive safe integer" }, { name: "host_access_token_matches_registered_shape", value: validHostAccessToken(value.host_access_token), expected: "true" }])}.`);
|
|
3458
|
-
if (!validGatewayOrigin(value.gateway_request_origin)) throw new HostRelayGatewayError("credential", `Host registration state is invalid or does not match this Relay: ${refusalFact("gateway_request_origin", value.gateway_request_origin, "an HTTPS origin or explicit-port loopback HTTP origin")}.`);
|
|
3459
|
-
return { gateway_request_origin: value.gateway_request_origin, host_access_token: value.host_access_token };
|
|
3418
|
+
function timingSafeEqualHex(left, right) {
|
|
3419
|
+
if (left.length !== right.length) return false;
|
|
3420
|
+
let diff = 0;
|
|
3421
|
+
for (let index = 0; index < left.length; index += 1) diff |= left.charCodeAt(index) ^ right.charCodeAt(index);
|
|
3422
|
+
return diff === 0;
|
|
3460
3423
|
}
|
|
3461
|
-
function
|
|
3462
|
-
return value
|
|
3463
|
-
}
|
|
3464
|
-
function validRegistrationCredential(value) {
|
|
3465
|
-
return Number.isSafeInteger(value.credential_generation) && Number(value.credential_generation) > 0 && validHostAccessToken(value.host_access_token);
|
|
3466
|
-
}
|
|
3467
|
-
function validHostAccessToken(value) {
|
|
3468
|
-
return typeof value === "string" && CEAL_HOST_ACCESS_TOKEN_PATTERN.test(value);
|
|
3469
|
-
}
|
|
3470
|
-
function validGatewayOrigin(value) {
|
|
3471
|
-
try {
|
|
3472
|
-
if (typeof value !== "string") return false;
|
|
3473
|
-
hostRelayGatewayEndpoint(value);
|
|
3474
|
-
return true;
|
|
3475
|
-
} catch {
|
|
3476
|
-
return false;
|
|
3477
|
-
}
|
|
3478
|
-
}
|
|
3479
|
-
async function assertOwnerPrivateDirectory(path) {
|
|
3480
|
-
const stats = await lstat2(path);
|
|
3481
|
-
if (!stats.isDirectory() || stats.isSymbolicLink() || !ownerPrivate3(stats)) throw new HostRelayGatewayError("credential", "Host Relay credential directory is not owner-private.");
|
|
3482
|
-
}
|
|
3483
|
-
function ownerPrivate3(stats) {
|
|
3484
|
-
if (process.platform === "win32") return true;
|
|
3485
|
-
const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
|
|
3486
|
-
return stats.uid === uid && (stats.mode & 63) === 0;
|
|
3424
|
+
function isRecord2(value) {
|
|
3425
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3487
3426
|
}
|
|
3488
|
-
function
|
|
3489
|
-
|
|
3490
|
-
if (!isCealJsonRecord(value) || value.ok !== true || value.status !== "accepted" || typeof value.request_ref !== "string" || !SAFE_HOST_REF.test(value.request_ref) || !isHostGatewayAcknowledgementDisposition(value.disposition)) {
|
|
3491
|
-
throw new HostRelayGatewayError("invalid_response", `Gateway Host ingress acknowledgement is invalid: ${refusalFacts([{ name: "ok", value: fields.ok, expected: "true" }, { name: "status", value: fields.status, expected: "accepted" }, { name: "request_ref", value: fields.request_ref, expected: "a safe Host reference" }, { name: "disposition", value: fields.disposition, expected: "a Gateway acknowledgement disposition" }])}.`);
|
|
3492
|
-
}
|
|
3493
|
-
return { request_ref: value.request_ref, disposition: value.disposition };
|
|
3427
|
+
function errorCode6(error) {
|
|
3428
|
+
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
|
|
3494
3429
|
}
|
|
3495
3430
|
|
|
3496
3431
|
// host/relay/src/relay-ipc.ts
|
|
3497
|
-
import { chmod, lstat as lstat3, rename as rename2, unlink as
|
|
3432
|
+
import { chmod, lstat as lstat3, rename as rename2, unlink as unlink3 } from "node:fs/promises";
|
|
3498
3433
|
import { createConnection, createServer } from "node:net";
|
|
3499
|
-
import { dirname as
|
|
3500
|
-
|
|
3501
|
-
// host/relay/src/current-prompt-custody.ts
|
|
3502
|
-
import { createHash as createHash5 } from "node:crypto";
|
|
3503
|
-
var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
|
|
3504
|
-
var MAX_CURRENT_PROMPTS = 1024;
|
|
3505
|
-
var HostCurrentPromptCustody = class {
|
|
3506
|
-
#records = /* @__PURE__ */ new Map();
|
|
3507
|
-
#consumed = /* @__PURE__ */ new Map();
|
|
3508
|
-
#now;
|
|
3509
|
-
constructor(now = Date.now) {
|
|
3510
|
-
this.#now = now;
|
|
3511
|
-
}
|
|
3512
|
-
register(identity, prompt, expiresAt) {
|
|
3513
|
-
const bytes2 = Buffer.byteLength(prompt, "utf8");
|
|
3514
|
-
const expiry = Date.parse(expiresAt);
|
|
3515
|
-
if (bytes2 === 0 || bytes2 > HOST_CURRENT_PROMPT_MAX_BYTES || !Number.isFinite(expiry) || expiry <= this.#now()) throw new TypeError("current_prompt_unavailable");
|
|
3516
|
-
if (hostProducerPromptRef(createHash5("sha256").update(prompt, "utf8").digest("hex")) !== identity.prompt_ref) throw new TypeError("current_prompt_unavailable");
|
|
3517
|
-
this.#purge();
|
|
3518
|
-
const key = promptKey(identity);
|
|
3519
|
-
if (this.#consumed.has(key)) return;
|
|
3520
|
-
if (!this.#records.has(key) && this.#records.size + this.#consumed.size >= MAX_CURRENT_PROMPTS) throw new TypeError("current_prompt_unavailable");
|
|
3521
|
-
this.#records.set(key, { prompt, expires_at_ms: expiry });
|
|
3522
|
-
}
|
|
3523
|
-
/** Transfer this turn's prompt into at most one authenticated invocation. */
|
|
3524
|
-
take(identity) {
|
|
3525
|
-
this.#purge();
|
|
3526
|
-
const key = promptKey(identity);
|
|
3527
|
-
const prompt = this.#records.get(key)?.prompt;
|
|
3528
|
-
if (prompt !== void 0) {
|
|
3529
|
-
const expiresAt = this.#records.get(key)?.expires_at_ms;
|
|
3530
|
-
this.#records.delete(key);
|
|
3531
|
-
if (expiresAt !== void 0) this.#consumed.set(key, expiresAt);
|
|
3532
|
-
}
|
|
3533
|
-
return prompt;
|
|
3534
|
-
}
|
|
3535
|
-
#purge() {
|
|
3536
|
-
const now = this.#now();
|
|
3537
|
-
for (const [key, value] of this.#records) if (value.expires_at_ms <= now) this.#records.delete(key);
|
|
3538
|
-
for (const [key, expiresAt] of this.#consumed) if (expiresAt <= now) this.#consumed.delete(key);
|
|
3539
|
-
}
|
|
3540
|
-
};
|
|
3541
|
-
function promptKey(value) {
|
|
3542
|
-
return [value.producer_id, value.producer_installation_ref, value.local_session_ref, value.local_turn_ref, value.prompt_ref].join("\0");
|
|
3543
|
-
}
|
|
3544
|
-
|
|
3545
|
-
// host/relay/src/relay-ipc.ts
|
|
3546
|
-
var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
|
|
3547
|
-
"schema_version",
|
|
3548
|
-
"producer_id",
|
|
3549
|
-
"producer_installation_ref",
|
|
3550
|
-
"local_session_ref",
|
|
3551
|
-
"local_turn_ref",
|
|
3552
|
-
"local_turn_aliases",
|
|
3553
|
-
"prompt_ref",
|
|
3554
|
-
"request_ref",
|
|
3555
|
-
"bound_at",
|
|
3556
|
-
"host_source_ref",
|
|
3557
|
-
"host_session_ref",
|
|
3558
|
-
"host_turn_ref",
|
|
3559
|
-
"host_input_ref"
|
|
3560
|
-
]);
|
|
3434
|
+
import { dirname as dirname5, join as join5 } from "node:path";
|
|
3561
3435
|
var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
|
|
3562
|
-
var
|
|
3563
|
-
var
|
|
3564
|
-
var HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA = "ceal.host_relay_invocation_context_query.v1";
|
|
3565
|
-
var HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA = "ceal.host_relay_invocation_context_result.v1";
|
|
3566
|
-
var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
|
|
3567
|
-
var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
|
|
3436
|
+
var HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA = "ceal.host_relay_invocation_context_query.v2";
|
|
3437
|
+
var HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA = "ceal.host_relay_invocation_context_result.v2";
|
|
3568
3438
|
var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
3569
|
-
var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
|
|
3570
|
-
"context_stale",
|
|
3571
|
-
"context_expired",
|
|
3572
|
-
"host_source_refused",
|
|
3573
|
-
"host_credential_refused",
|
|
3574
|
-
"gateway_unavailable",
|
|
3575
|
-
"gateway_refused",
|
|
3576
|
-
"gateway_response_invalid",
|
|
3577
|
-
"gateway_rate_limited",
|
|
3578
|
-
"producer_frame_invalid",
|
|
3579
|
-
"deadline",
|
|
3580
|
-
"cancelled",
|
|
3581
|
-
"mint_failed",
|
|
3582
|
-
"relay_failed",
|
|
3583
|
-
"request_authority_unavailable",
|
|
3584
|
-
"request_missing",
|
|
3585
|
-
"authority_mismatch",
|
|
3586
|
-
"host_source_mismatch",
|
|
3587
|
-
"host_session_mismatch",
|
|
3588
|
-
"host_turn_mismatch",
|
|
3589
|
-
"host_input_mismatch",
|
|
3590
|
-
"host_prompt_mismatch"
|
|
3591
|
-
]);
|
|
3592
|
-
var HOST_RELAY_DIRECT_WIRE_MINT_REASONS = vocabulary([
|
|
3593
|
-
"context_stale",
|
|
3594
|
-
"context_expired",
|
|
3595
|
-
"host_source_refused",
|
|
3596
|
-
"host_credential_refused",
|
|
3597
|
-
"gateway_unavailable",
|
|
3598
|
-
"gateway_rate_limited",
|
|
3599
|
-
"gateway_refused",
|
|
3600
|
-
"gateway_response_invalid",
|
|
3601
|
-
"producer_frame_invalid"
|
|
3602
|
-
]);
|
|
3603
|
-
var HostRelayInvocationMintRefusal = class extends Error {
|
|
3604
|
-
reason;
|
|
3605
|
-
constructor(reason) {
|
|
3606
|
-
super(reason);
|
|
3607
|
-
this.name = "HostRelayInvocationMintRefusal";
|
|
3608
|
-
this.reason = reason;
|
|
3609
|
-
}
|
|
3610
|
-
};
|
|
3611
|
-
var HOST_RELAY_INVOCATION_MINT_OUTCOMES = vocabulary(["completed", "caller_cancelled", "deadline", "failed", "no_binding"]);
|
|
3612
3439
|
var HOST_RELAY_EXCHANGE_FAILURES = vocabulary([
|
|
3613
3440
|
"permission_denied",
|
|
3614
3441
|
"socket_missing",
|
|
@@ -3631,26 +3458,14 @@ var HostRelayIpcError = class extends Error {
|
|
|
3631
3458
|
};
|
|
3632
3459
|
async function startHostRelayIpcServer(options) {
|
|
3633
3460
|
return withRelayEndpointLock(options.endpoint, async () => {
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
await removeStaleRelayEndpoint(options.endpoint, options.testHooks);
|
|
3639
|
-
}
|
|
3461
|
+
const lastSeenRequestBySession = /* @__PURE__ */ new Map();
|
|
3462
|
+
if (process.platform === "win32") return startWindowsPipeServer(options, lastSeenRequestBySession);
|
|
3463
|
+
await assertTrustedEndpointParent(options.endpoint);
|
|
3464
|
+
await removeStaleRelayEndpoint(options.endpoint, options.testHooks);
|
|
3640
3465
|
const active = /* @__PURE__ */ new Map();
|
|
3641
3466
|
const sockets = /* @__PURE__ */ new Set();
|
|
3642
3467
|
const quarantine = options.quarantine ?? options.admission.quarantine;
|
|
3643
|
-
const
|
|
3644
|
-
const server = createServer((socket) => serveSocket(socket, options, currentPrompts, quarantine, active, sockets));
|
|
3645
|
-
if (process.platform === "win32") {
|
|
3646
|
-
try {
|
|
3647
|
-
await listen(server, options.endpoint);
|
|
3648
|
-
} catch (error) {
|
|
3649
|
-
await abandonRelayServer(server, sockets);
|
|
3650
|
-
throw error;
|
|
3651
|
-
}
|
|
3652
|
-
return { endpoint: options.endpoint, close: () => closeServer(server, sockets, options.endpoint) };
|
|
3653
|
-
}
|
|
3468
|
+
const server = createServer((socket) => serveSocket(socket, options, quarantine, active, sockets, Buffer.alloc(0), lastSeenRequestBySession));
|
|
3654
3469
|
const staging = stagingRelayEndpoint(options.endpoint);
|
|
3655
3470
|
await listenStagingRelayEndpoint(server, sockets, staging);
|
|
3656
3471
|
let owned;
|
|
@@ -3674,39 +3489,105 @@ function encodeHostRelayIpcResponse(response2) {
|
|
|
3674
3489
|
body.copy(result2, HOST_RELAY_IPC_LENGTH_BYTES);
|
|
3675
3490
|
return result2;
|
|
3676
3491
|
}
|
|
3677
|
-
function
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3492
|
+
async function startWindowsPipeServer(options, lastSeenRequestBySession) {
|
|
3493
|
+
if (options.pipeKey === void 0) throw new HostRelayIpcError("relay_unavailable", "Host Relay pipe key is unavailable; refusing to serve Windows pipe clients it cannot admit.");
|
|
3494
|
+
const active = /* @__PURE__ */ new Map();
|
|
3495
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
3496
|
+
const quarantine = options.quarantine ?? options.admission.quarantine;
|
|
3497
|
+
const pipeKey = options.pipeKey;
|
|
3498
|
+
const server = createServer((socket) => {
|
|
3499
|
+
void admitWindowsPipeSocket(socket, options, quarantine, active, sockets, pipeKey, lastSeenRequestBySession);
|
|
3500
|
+
});
|
|
3501
|
+
try {
|
|
3502
|
+
await listen(server, options.endpoint);
|
|
3503
|
+
} catch (error) {
|
|
3504
|
+
await abandonRelayServer(server, sockets);
|
|
3505
|
+
throw await resolveWindowsPipeListenOutcome(options.endpoint, error);
|
|
3506
|
+
}
|
|
3507
|
+
return { endpoint: options.endpoint, close: () => closeServer(server, sockets, options.endpoint) };
|
|
3508
|
+
}
|
|
3509
|
+
async function admitWindowsPipeSocket(socket, options, quarantine, active, sockets, pipeKey, lastSeenRequestBySession) {
|
|
3510
|
+
let leftover;
|
|
3511
|
+
try {
|
|
3512
|
+
leftover = await conductPipeServerHandshake(socket, pipeKey, RELAY_PIPE_HANDSHAKE_TIMEOUT_MS);
|
|
3513
|
+
} catch {
|
|
3514
|
+
socket.destroy();
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3517
|
+
if (socket.destroyed) return;
|
|
3518
|
+
serveSocket(socket, options, quarantine, active, sockets, leftover, lastSeenRequestBySession);
|
|
3519
|
+
}
|
|
3520
|
+
var HostRelayAlreadyRunningError = class extends Error {
|
|
3521
|
+
constructor(endpoint) {
|
|
3522
|
+
super(`another Host Relay already serves ${endpoint}; this instance exits without opening a second pipe.`);
|
|
3523
|
+
this.name = "HostRelayAlreadyRunningError";
|
|
3524
|
+
}
|
|
3525
|
+
};
|
|
3526
|
+
async function resolveWindowsPipeListenOutcome(endpoint, error, probe = windowsPipeHolderIsLive) {
|
|
3527
|
+
if (pipeNameHeldError(error) && await probe(endpoint)) return new HostRelayAlreadyRunningError(endpoint);
|
|
3528
|
+
return mapWindowsPipeListenError(endpoint, error);
|
|
3529
|
+
}
|
|
3530
|
+
function pipeNameHeldError(error) {
|
|
3531
|
+
const code = errorCode7(error);
|
|
3532
|
+
return code === "EADDRINUSE" || code === "EACCES";
|
|
3533
|
+
}
|
|
3534
|
+
function windowsPipeHolderIsLive(endpoint) {
|
|
3535
|
+
return new Promise((resolve) => {
|
|
3536
|
+
const timer = setTimeout(() => {
|
|
3537
|
+
probe.destroy();
|
|
3538
|
+
resolve(false);
|
|
3539
|
+
}, RELAY_PIPE_HANDSHAKE_TIMEOUT_MS);
|
|
3540
|
+
const probe = createConnection(endpoint);
|
|
3541
|
+
probe.once("connect", () => {
|
|
3542
|
+
clearTimeout(timer);
|
|
3543
|
+
probe.destroy();
|
|
3544
|
+
resolve(true);
|
|
3545
|
+
});
|
|
3546
|
+
probe.once("error", () => {
|
|
3547
|
+
clearTimeout(timer);
|
|
3548
|
+
probe.destroy();
|
|
3549
|
+
resolve(false);
|
|
3685
3550
|
});
|
|
3686
3551
|
});
|
|
3552
|
+
}
|
|
3553
|
+
function mapWindowsPipeListenError(endpoint, error) {
|
|
3554
|
+
const code = errorCode7(error);
|
|
3555
|
+
if (pipeNameHeldError(error)) {
|
|
3556
|
+
return new HostRelayIpcError("relay_unavailable", `relay pipe name held by another process: the Host Relay pipe name ${endpoint} is already served (error ${code}); stop the process holding it, then restart the Host Relay.`);
|
|
3557
|
+
}
|
|
3558
|
+
return error instanceof Error ? error : new HostRelayIpcError("relay_unavailable", "Host Relay IPC endpoint is unavailable.");
|
|
3559
|
+
}
|
|
3560
|
+
function serveSocket(socket, options, quarantine, active, sockets, initial = Buffer.alloc(0), lastSeenRequestBySession = /* @__PURE__ */ new Map()) {
|
|
3561
|
+
const state = { buffer: initial, closed: false, tail: Promise.resolve(), abort: new AbortController(), last_seen_request_by_session: lastSeenRequestBySession };
|
|
3562
|
+
sockets.add(socket);
|
|
3563
|
+
socket.on("data", (chunk) => receiveRelaySocketBytes(socket, state, options, quarantine, active, Buffer.from(chunk)));
|
|
3687
3564
|
socket.once("error", () => closeConnection(socket, state, active, sockets));
|
|
3688
3565
|
socket.once("close", () => closeConnection(socket, state, active, sockets));
|
|
3689
3566
|
socket.once("end", () => {
|
|
3690
|
-
if (state.buffer.length > 0
|
|
3691
|
-
writeCurrentPromptResponse(socket, "current_prompt_unavailable");
|
|
3692
|
-
socket.end();
|
|
3693
|
-
} else if (state.buffer.length > 0) void quarantine.retainMalformed(state.buffer, "frame_truncated").finally(() => {
|
|
3567
|
+
if (state.buffer.length > 0) void quarantine.retainMalformed(state.buffer, "frame_truncated").finally(() => {
|
|
3694
3568
|
writeResponse(socket, errorResponse("frame_truncated"));
|
|
3695
3569
|
socket.end();
|
|
3696
3570
|
});
|
|
3697
3571
|
else socket.end();
|
|
3698
3572
|
});
|
|
3573
|
+
if (initial.length > 0) receiveRelaySocketBytes(socket, state, options, quarantine, active, Buffer.alloc(0));
|
|
3699
3574
|
}
|
|
3700
|
-
|
|
3701
|
-
|
|
3575
|
+
function receiveRelaySocketBytes(socket, state, options, quarantine, active, chunk) {
|
|
3576
|
+
state.buffer = Buffer.concat([state.buffer, chunk]);
|
|
3577
|
+
state.tail = state.tail.then(() => drainFrames(socket, state, options, quarantine, active)).catch(() => {
|
|
3578
|
+
writeResponse(socket, errorResponse("relay_unavailable"));
|
|
3579
|
+
socket.end();
|
|
3580
|
+
});
|
|
3581
|
+
}
|
|
3582
|
+
async function drainFrames(socket, state, options, quarantine, active) {
|
|
3583
|
+
while (!state.closed && state.buffer.length >= HOST_RELAY_IPC_LENGTH_BYTES && await processBufferedRequest(socket, state, options, quarantine, active)) {
|
|
3702
3584
|
}
|
|
3703
3585
|
}
|
|
3704
|
-
async function processBufferedRequest(socket, state, options,
|
|
3586
|
+
async function processBufferedRequest(socket, state, options, quarantine, active) {
|
|
3705
3587
|
const length = state.buffer.readUInt32BE(0);
|
|
3706
|
-
const
|
|
3707
|
-
const maximum = promptWire ? HOST_CURRENT_PROMPT_MAX_BYTES + 4096 : HOST_PRODUCER_MAX_JSON_BYTES;
|
|
3588
|
+
const maximum = HOST_PRODUCER_MAX_JSON_BYTES;
|
|
3708
3589
|
if (length === 0 || length > maximum) {
|
|
3709
|
-
await refuseInvalidBufferedRequest(socket, state.buffer, length,
|
|
3590
|
+
await refuseInvalidBufferedRequest(socket, state.buffer, length, quarantine);
|
|
3710
3591
|
state.buffer = Buffer.alloc(0);
|
|
3711
3592
|
socket.end();
|
|
3712
3593
|
return false;
|
|
@@ -3718,41 +3599,20 @@ async function processBufferedRequest(socket, state, options, currentPrompts, qu
|
|
|
3718
3599
|
try {
|
|
3719
3600
|
request2 = decodeIpcRequest(encoded);
|
|
3720
3601
|
} catch (error) {
|
|
3721
|
-
await refuseMalformedRequest(socket, encoded,
|
|
3722
|
-
return true;
|
|
3723
|
-
}
|
|
3724
|
-
if (request2.schema_version === HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA) {
|
|
3725
|
-
try {
|
|
3726
|
-
currentPrompts.register(request2, request2.current_prompt, request2.context_expires_at);
|
|
3727
|
-
writeCurrentPromptResponse(socket, "registered");
|
|
3728
|
-
} catch {
|
|
3729
|
-
writeCurrentPromptResponse(socket, "current_prompt_unavailable");
|
|
3730
|
-
}
|
|
3602
|
+
await refuseMalformedRequest(socket, encoded, error, quarantine);
|
|
3731
3603
|
return true;
|
|
3732
3604
|
}
|
|
3733
|
-
const answered = await processHostQuery(socket,
|
|
3734
|
-
if (answered === "closed") {
|
|
3735
|
-
socket.end();
|
|
3736
|
-
return false;
|
|
3737
|
-
}
|
|
3605
|
+
const answered = await processHostQuery(socket, state, options, request2);
|
|
3738
3606
|
if (answered === "answered") return true;
|
|
3739
3607
|
await processProducerRequest(socket, state, request2, encoded, options.admission, quarantine, active);
|
|
3740
3608
|
return true;
|
|
3741
3609
|
}
|
|
3742
|
-
async function refuseInvalidBufferedRequest(socket, rawBuffer, length,
|
|
3743
|
-
if (promptWire) {
|
|
3744
|
-
writeCurrentPromptResponse(socket, "current_prompt_unavailable");
|
|
3745
|
-
return;
|
|
3746
|
-
}
|
|
3610
|
+
async function refuseInvalidBufferedRequest(socket, rawBuffer, length, quarantine) {
|
|
3747
3611
|
const code = length === 0 ? "invalid_frame_length" : "frame_too_large";
|
|
3748
3612
|
await quarantine.retainMalformed(rawBuffer.subarray(0, Math.min(rawBuffer.length, length + HOST_RELAY_IPC_LENGTH_BYTES)), code);
|
|
3749
3613
|
writeResponse(socket, errorResponse(code));
|
|
3750
3614
|
}
|
|
3751
|
-
async function refuseMalformedRequest(socket, encoded,
|
|
3752
|
-
if (promptWire) {
|
|
3753
|
-
writeCurrentPromptResponse(socket, "current_prompt_unavailable");
|
|
3754
|
-
return;
|
|
3755
|
-
}
|
|
3615
|
+
async function refuseMalformedRequest(socket, encoded, error, quarantine) {
|
|
3756
3616
|
const code = error instanceof HostProducerFrameError ? error.code : "invalid_frame_shape";
|
|
3757
3617
|
await quarantine.retainMalformed(encoded, code);
|
|
3758
3618
|
writeResponse(socket, errorResponse(code));
|
|
@@ -3768,29 +3628,19 @@ async function processProducerRequest(socket, state, request2, encoded, admissio
|
|
|
3768
3628
|
}
|
|
3769
3629
|
writeResponse(socket, admissionResponse(await admission.admit(request2)));
|
|
3770
3630
|
}
|
|
3771
|
-
async function processHostQuery(socket,
|
|
3772
|
-
if (request2.schema_version
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
return "answered";
|
|
3781
|
-
}
|
|
3782
|
-
if (request2.schema_version === HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA) {
|
|
3783
|
-
let response2;
|
|
3784
|
-
const resolve = (invocationSignal) => resolveInvocationContext(request2, options.admission, options.bindings, currentPrompts, options.mintInvocationContext, invocationSignal);
|
|
3785
|
-
try {
|
|
3786
|
-
response2 = options.invocationMintTimeoutMs === void 0 && options.scheduleInvocationMintDeadline === void 0 && options.observeInvocationMint === void 0 ? await resolve(signal) : await observeInvocationMint(resolve, signal, options.invocationMintTimeoutMs, options.scheduleInvocationMintDeadline, options.observeInvocationMint);
|
|
3787
|
-
} catch {
|
|
3788
|
-
response2 = { schema_version: HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA, status: "refused", reason: "relay_failed" };
|
|
3789
|
-
}
|
|
3790
|
-
writeInvocationContextResponse(socket, response2);
|
|
3791
|
-
return "answered";
|
|
3631
|
+
async function processHostQuery(socket, state, options, request2) {
|
|
3632
|
+
if (request2.schema_version !== HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA) return "not_a_query";
|
|
3633
|
+
const sessionKey = `${request2.host_binding.host_source_ref}\0${request2.host_binding.host_session_ref}`;
|
|
3634
|
+
const lastSeen = state.last_seen_request_by_session.get(sessionKey);
|
|
3635
|
+
let response2;
|
|
3636
|
+
try {
|
|
3637
|
+
response2 = options.mintInvocationContext === void 0 ? { schema_version: HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA, status: "refused", reason: "gateway_unavailable" } : await options.mintInvocationContext({ ...request2, ...lastSeen === void 0 ? {} : { last_seen_request_ref: lastSeen } }, state.abort.signal);
|
|
3638
|
+
} catch {
|
|
3639
|
+
response2 = { schema_version: HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA, status: "refused", reason: "gateway_unavailable" };
|
|
3792
3640
|
}
|
|
3793
|
-
|
|
3641
|
+
if (response2.status === "ready") state.last_seen_request_by_session.set(sessionKey, response2.request_ref);
|
|
3642
|
+
writeInvocationContextResponse(socket, response2);
|
|
3643
|
+
return "answered";
|
|
3794
3644
|
}
|
|
3795
3645
|
function claimProducer(socket, state, frame, active) {
|
|
3796
3646
|
const key = producerKey(frame);
|
|
@@ -3814,154 +3664,9 @@ function closeConnection(socket, state, active, sockets) {
|
|
|
3814
3664
|
function writeResponse(socket, response2) {
|
|
3815
3665
|
if (!socket.destroyed) socket.write(encodeHostRelayIpcResponse(response2));
|
|
3816
3666
|
}
|
|
3817
|
-
function writeBindingResponse(socket, result2) {
|
|
3818
|
-
const response2 = { schema_version: HOST_RELAY_BINDING_RESULT_SCHEMA, ...result2 };
|
|
3819
|
-
if (!socket.destroyed) socket.write(encodeLengthPrefixed(response2));
|
|
3820
|
-
}
|
|
3821
3667
|
function writeInvocationContextResponse(socket, response2) {
|
|
3822
3668
|
if (!socket.destroyed) socket.write(encodeLengthPrefixed(response2));
|
|
3823
3669
|
}
|
|
3824
|
-
function writeCurrentPromptResponse(socket, status) {
|
|
3825
|
-
if (!socket.destroyed) socket.write(encodeLengthPrefixed({ schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status }));
|
|
3826
|
-
}
|
|
3827
|
-
async function resolveInvocationContext(request2, admission, bindings, currentPrompts, mint, signal) {
|
|
3828
|
-
if (mint === void 0) throw new HostRelayIpcError("relay_unavailable");
|
|
3829
|
-
const query = bindingQueryFromRequest(request2);
|
|
3830
|
-
const expectedSequences = pendingAdmissionSequences(await admission.snapshot(), query);
|
|
3831
|
-
const binding = await waitForRequestBinding(query, admission, bindings, signal);
|
|
3832
|
-
if (binding.status !== "ready") return await unboundInvocationContextRefusal(admission, await admission.snapshot(), query, expectedSequences) ?? { schema_version: HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA, status: "no_binding" };
|
|
3833
|
-
const currentPrompt = currentPrompts.take({ producer_id: request2.producer_id, producer_installation_ref: request2.producer_installation_ref, local_session_ref: request2.local_session_ref, local_turn_ref: request2.local_turn_ref, prompt_ref: binding.binding.prompt_ref });
|
|
3834
|
-
let result2;
|
|
3835
|
-
try {
|
|
3836
|
-
result2 = await mint({ client_nonce: request2.client_nonce, context_expires_at: binding.binding.context_expires_at ?? binding.binding.bound_at, request_work_context: relayRequestWorkContextFromBinding(binding.binding), ...currentPrompt === void 0 ? {} : { current_prompt: currentPrompt }, ...request2.visible_output_max_bytes === void 0 ? {} : { visible_output_max_bytes: request2.visible_output_max_bytes } }, signal);
|
|
3837
|
-
} catch (error) {
|
|
3838
|
-
return { schema_version: HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA, status: "refused", reason: error instanceof HostRelayInvocationMintRefusal ? error.reason : "mint_failed" };
|
|
3839
|
-
}
|
|
3840
|
-
return { schema_version: HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA, status: "ready", result: result2 };
|
|
3841
|
-
}
|
|
3842
|
-
async function unboundInvocationContextRefusal(admission, snapshot, query, expectedSequences) {
|
|
3843
|
-
if (expectedSequences.length > 0) {
|
|
3844
|
-
const refusal = snapshot.installations[`${query.producer_id}\0${query.producer_installation_ref}`]?.last_refusal;
|
|
3845
|
-
if (refusal !== void 0 && expectedSequences.includes(refusal.sequence)) {
|
|
3846
|
-
const reason2 = mintReasonForDeliveryRefusal(refusal.code, refusal.diagnostic);
|
|
3847
|
-
if (reason2 !== void 0) return { schema_version: HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA, status: "refused", reason: reason2 };
|
|
3848
|
-
}
|
|
3849
|
-
}
|
|
3850
|
-
const selector = Object.freeze({
|
|
3851
|
-
producer_id: query.producer_id,
|
|
3852
|
-
producer_installation_ref: query.producer_installation_ref,
|
|
3853
|
-
local_session_ref: query.local_session_ref,
|
|
3854
|
-
local_turn_ref: query.local_turn_ref,
|
|
3855
|
-
local_turn_aliases: Object.freeze([])
|
|
3856
|
-
});
|
|
3857
|
-
const match = await admission.quarantine.findNewestAttributedMatch(selector);
|
|
3858
|
-
if (match === null) return null;
|
|
3859
|
-
const reason = mintReasonForQuarantinedMatch(match);
|
|
3860
|
-
if (reason === void 0) return null;
|
|
3861
|
-
return { schema_version: HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA, status: "refused", reason };
|
|
3862
|
-
}
|
|
3863
|
-
function mintReasonForDeliveryRefusal(code, diagnostic) {
|
|
3864
|
-
if (code !== "gateway_refused") return void 0;
|
|
3865
|
-
if (diagnostic === void 0) return "gateway_refused";
|
|
3866
|
-
return mintReasonForWireCause(projectCealHostAuthenticationCause(diagnostic));
|
|
3867
|
-
}
|
|
3868
|
-
function mintReasonForQuarantinedMatch(match) {
|
|
3869
|
-
if (match === null) return void 0;
|
|
3870
|
-
if (match.reason !== "gateway_refused") return "producer_frame_invalid";
|
|
3871
|
-
const diagnostic = match.attribution?.diagnostic;
|
|
3872
|
-
if (diagnostic === void 0) return void 0;
|
|
3873
|
-
return mintReasonForWireCause(projectCealHostAuthenticationCause(diagnostic));
|
|
3874
|
-
}
|
|
3875
|
-
function mintReasonForWireCause(cause) {
|
|
3876
|
-
if (isDirectWireCause(cause)) return cause;
|
|
3877
|
-
if (cause === "relay_failed" || cause === "relay_unavailable" || cause === "options_invalid") return "relay_failed";
|
|
3878
|
-
return "mint_failed";
|
|
3879
|
-
}
|
|
3880
|
-
function isDirectWireCause(cause) {
|
|
3881
|
-
return isVocabularyWord(HOST_RELAY_DIRECT_WIRE_MINT_REASONS, cause);
|
|
3882
|
-
}
|
|
3883
|
-
function pendingAdmissionSequences(snapshot, query) {
|
|
3884
|
-
const sequences = [];
|
|
3885
|
-
for (const entry2 of snapshot.entries) {
|
|
3886
|
-
const selector = extractHostBindingSelector(entry2.frame);
|
|
3887
|
-
if (selector === null || !relayRequestBindingMatchesQuery(query, selector)) continue;
|
|
3888
|
-
sequences.push(entry2.sequence);
|
|
3889
|
-
}
|
|
3890
|
-
return sequences;
|
|
3891
|
-
}
|
|
3892
|
-
async function waitForRequestBinding(query, admission, bindings, signal) {
|
|
3893
|
-
let binding = await bindings.query(query);
|
|
3894
|
-
if (binding.status === "ready") return binding;
|
|
3895
|
-
while (await admission.hasPendingRequestBinding(query)) {
|
|
3896
|
-
const cycle = new AbortController();
|
|
3897
|
-
const cancelCycle = () => cycle.abort(signal.reason);
|
|
3898
|
-
if (signal.aborted) cancelCycle();
|
|
3899
|
-
else signal.addEventListener("abort", cancelCycle, { once: true });
|
|
3900
|
-
try {
|
|
3901
|
-
const changed = Promise.race([
|
|
3902
|
-
bindings.waitForBinding(query, cycle.signal).then((result3) => ({ kind: "bound", result: result3 })),
|
|
3903
|
-
admission.waitForDrainChange(cycle.signal).then(() => ({ kind: "drained" }))
|
|
3904
|
-
]).then((result3) => result3, (error) => ({ kind: "cancelled", error }));
|
|
3905
|
-
binding = await bindings.query(query);
|
|
3906
|
-
if (binding.status === "ready") return binding;
|
|
3907
|
-
if (!await admission.hasPendingRequestBinding(query)) return await bindings.query(query);
|
|
3908
|
-
const result2 = await changed;
|
|
3909
|
-
if (result2.kind === "cancelled") throw result2.error;
|
|
3910
|
-
} finally {
|
|
3911
|
-
signal.removeEventListener("abort", cancelCycle);
|
|
3912
|
-
cycle.abort("wait_cycle_complete");
|
|
3913
|
-
}
|
|
3914
|
-
binding = await bindings.query(query);
|
|
3915
|
-
if (binding.status === "ready") return binding;
|
|
3916
|
-
}
|
|
3917
|
-
return await bindings.query(query);
|
|
3918
|
-
}
|
|
3919
|
-
async function observeInvocationMint(resolve, callerSignal, timeoutMs, scheduleDeadline, observe) {
|
|
3920
|
-
const started = process.hrtime.bigint();
|
|
3921
|
-
const controller2 = new AbortController();
|
|
3922
|
-
let cancellation = null;
|
|
3923
|
-
let outcome = "failed";
|
|
3924
|
-
let reason;
|
|
3925
|
-
const cancel = (cause) => {
|
|
3926
|
-
if (cancellation === null) {
|
|
3927
|
-
cancellation = cause;
|
|
3928
|
-
controller2.abort(cause);
|
|
3929
|
-
}
|
|
3930
|
-
};
|
|
3931
|
-
const callerCancelled = () => cancel("caller_cancelled");
|
|
3932
|
-
const cancelled = new Promise((_resolve, reject) => controller2.signal.addEventListener("abort", () => reject(new Error("invocation mint cancelled")), { once: true }));
|
|
3933
|
-
const clearDeadline = timeoutMs === void 0 ? () => void 0 : (scheduleDeadline ?? scheduleInvocationMintDeadline)(() => cancel("deadline"), timeoutMs);
|
|
3934
|
-
if (callerSignal.aborted) callerCancelled();
|
|
3935
|
-
else callerSignal.addEventListener("abort", callerCancelled, { once: true });
|
|
3936
|
-
try {
|
|
3937
|
-
const response2 = await Promise.race([resolve(controller2.signal), cancelled]);
|
|
3938
|
-
({ outcome, reason } = invocationMintObservationForResponse(response2));
|
|
3939
|
-
return response2;
|
|
3940
|
-
} catch {
|
|
3941
|
-
({ outcome, reason } = invocationMintObservationForCancellation(cancellation));
|
|
3942
|
-
return { schema_version: HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA, status: "refused", reason: reason ?? "relay_failed" };
|
|
3943
|
-
} finally {
|
|
3944
|
-
clearDeadline();
|
|
3945
|
-
callerSignal.removeEventListener("abort", callerCancelled);
|
|
3946
|
-
observe?.({ outcome, elapsed_ms: Number((process.hrtime.bigint() - started) / 1000000n), ...reason === void 0 ? {} : { reason } });
|
|
3947
|
-
}
|
|
3948
|
-
}
|
|
3949
|
-
function invocationMintObservationForResponse(response2) {
|
|
3950
|
-
if (response2.status === "no_binding") return { outcome: "no_binding" };
|
|
3951
|
-
if (response2.status === "ready") return { outcome: "completed" };
|
|
3952
|
-
if (response2.reason === "deadline") return { outcome: "deadline", reason: "deadline" };
|
|
3953
|
-
if (response2.reason === "cancelled") return { outcome: "caller_cancelled", reason: "cancelled" };
|
|
3954
|
-
return { outcome: "failed", reason: response2.reason };
|
|
3955
|
-
}
|
|
3956
|
-
function invocationMintObservationForCancellation(cancellation) {
|
|
3957
|
-
if (cancellation === "deadline") return { outcome: "deadline", reason: "deadline" };
|
|
3958
|
-
if (cancellation === "caller_cancelled") return { outcome: "caller_cancelled", reason: "cancelled" };
|
|
3959
|
-
return { outcome: "failed", reason: "relay_failed" };
|
|
3960
|
-
}
|
|
3961
|
-
function scheduleInvocationMintDeadline(expire, milliseconds) {
|
|
3962
|
-
const timeout = setTimeout(expire, milliseconds);
|
|
3963
|
-
return () => clearTimeout(timeout);
|
|
3964
|
-
}
|
|
3965
3670
|
function admissionResponse(result2) {
|
|
3966
3671
|
return { schema_version: HOST_RELAY_IPC_RESPONSE_SCHEMA, ...result2 };
|
|
3967
3672
|
}
|
|
@@ -3971,45 +3676,45 @@ function errorResponse(code) {
|
|
|
3971
3676
|
function producerKey(frame) {
|
|
3972
3677
|
return `${frame.producer_id}\0${frame.producer_installation_ref}`;
|
|
3973
3678
|
}
|
|
3974
|
-
function
|
|
3679
|
+
function record3(value) {
|
|
3975
3680
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3976
3681
|
}
|
|
3977
3682
|
function decodeIpcRequest(input) {
|
|
3978
|
-
const value = decodeLengthPrefixedJson(input,
|
|
3683
|
+
const value = decodeLengthPrefixedJson(input, HOST_PRODUCER_MAX_JSON_BYTES);
|
|
3979
3684
|
const local = decodeLocalRequest(value);
|
|
3980
3685
|
if (local !== void 0) return local;
|
|
3981
3686
|
return decodeHostProducerFrame(input);
|
|
3982
3687
|
}
|
|
3983
3688
|
function decodeLocalRequest(value) {
|
|
3984
|
-
if (!
|
|
3985
|
-
if (value.schema_version === HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA) {
|
|
3986
|
-
if (!validCurrentPromptRegisterRequest(value)) throw new HostProducerFrameError("invalid_frame_shape");
|
|
3987
|
-
return value;
|
|
3988
|
-
}
|
|
3989
|
-
if (value.schema_version === HOST_RELAY_BINDING_QUERY_SCHEMA) {
|
|
3990
|
-
if (!validBindingQueryRequest(value)) throw new HostProducerFrameError("invalid_frame_shape");
|
|
3991
|
-
return value;
|
|
3992
|
-
}
|
|
3689
|
+
if (!record3(value)) return void 0;
|
|
3993
3690
|
if (value.schema_version === HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA) {
|
|
3994
3691
|
if (!validInvocationContextQueryRequest(value)) throw new HostProducerFrameError("invalid_frame_shape");
|
|
3995
3692
|
return value;
|
|
3996
3693
|
}
|
|
3997
3694
|
return void 0;
|
|
3998
3695
|
}
|
|
3999
|
-
function validCurrentPromptRegisterRequest(value) {
|
|
4000
|
-
return validQueryIdentity(value, HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA) && exactKeys4(value, ["schema_version", "producer_id", "producer_installation_ref", "local_session_ref", "local_turn_ref", "prompt_ref", "current_prompt", "context_expires_at"]) && safeRef3(value.prompt_ref) && typeof value.current_prompt === "string" && Buffer.byteLength(value.current_prompt, "utf8") > 0 && Buffer.byteLength(value.current_prompt, "utf8") <= HOST_CURRENT_PROMPT_MAX_BYTES && validTimestamp2(value.context_expires_at);
|
|
4001
|
-
}
|
|
4002
|
-
function validBindingQueryRequest(value) {
|
|
4003
|
-
return validQueryIdentity(value, HOST_RELAY_BINDING_QUERY_SCHEMA) && exactKeys4(value, ["schema_version", "producer_id", "producer_installation_ref", "local_session_ref", "local_turn_ref"]);
|
|
4004
|
-
}
|
|
4005
|
-
function bindingQueryFromRequest(value) {
|
|
4006
|
-
return { producer_id: value.producer_id, producer_installation_ref: value.producer_installation_ref, local_session_ref: value.local_session_ref, local_turn_ref: value.local_turn_ref };
|
|
4007
|
-
}
|
|
4008
3696
|
function validInvocationContextQueryRequest(value) {
|
|
4009
|
-
|
|
3697
|
+
if (!validQueryEnvelope(value)) return false;
|
|
3698
|
+
try {
|
|
3699
|
+
decodeCealHostInvocationContextRequest({
|
|
3700
|
+
schema_version: CEAL_HOST_INVOCATION_CONTEXT_REQUEST_SCHEMA,
|
|
3701
|
+
client_nonce: value.client_nonce,
|
|
3702
|
+
context_expires_at: value.context_expires_at,
|
|
3703
|
+
host_binding: value.host_binding,
|
|
3704
|
+
human_message_window: value.human_message_window,
|
|
3705
|
+
...value.visible_output_max_bytes === void 0 ? {} : { visible_output_max_bytes: value.visible_output_max_bytes }
|
|
3706
|
+
});
|
|
3707
|
+
return true;
|
|
3708
|
+
} catch {
|
|
3709
|
+
return false;
|
|
3710
|
+
}
|
|
3711
|
+
}
|
|
3712
|
+
function validQueryEnvelope(value) {
|
|
3713
|
+
if (!validQueryIdentity(value, HOST_RELAY_INVOCATION_CONTEXT_QUERY_SCHEMA) || !exactKeys3(value, ["schema_version", "producer_id", "producer_installation_ref", "client_nonce", "context_expires_at", "host_binding", "human_message_window"], ["visible_output_max_bytes"])) return false;
|
|
3714
|
+
return typeof value.client_nonce === "string" && CEAL_HOST_CLIENT_NONCE_PATTERN.test(value.client_nonce) && typeof value.context_expires_at === "string" && (value.visible_output_max_bytes === void 0 || validVisibleOutputBudget(value.visible_output_max_bytes));
|
|
4010
3715
|
}
|
|
4011
3716
|
function validQueryIdentity(value, schema) {
|
|
4012
|
-
return
|
|
3717
|
+
return record3(value) && value.schema_version === schema && (value.producer_id === "codex" || value.producer_id === "claude") && safeRef2(value.producer_installation_ref);
|
|
4013
3718
|
}
|
|
4014
3719
|
function validVisibleOutputBudget(value) {
|
|
4015
3720
|
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 && value <= CEAL_CANONICAL_HTTP_MAX_RESPONSE_BYTES;
|
|
@@ -4033,27 +3738,13 @@ function encodeLengthPrefixed(value) {
|
|
|
4033
3738
|
body.copy(output, HOST_RELAY_IPC_LENGTH_BYTES);
|
|
4034
3739
|
return output;
|
|
4035
3740
|
}
|
|
4036
|
-
function
|
|
4037
|
-
return value.subarray(HOST_RELAY_IPC_LENGTH_BYTES, Math.min(value.length, HOST_RELAY_IPC_LENGTH_BYTES + 128)).includes(Buffer.from(`"schema_version":"${HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA}"`, "utf8"));
|
|
4038
|
-
}
|
|
4039
|
-
function exactKeys4(value, required, optional = []) {
|
|
3741
|
+
function exactKeys3(value, required, optional = []) {
|
|
4040
3742
|
const keys = Object.keys(value);
|
|
4041
3743
|
return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => required.includes(key) || optional.includes(key));
|
|
4042
3744
|
}
|
|
4043
|
-
function
|
|
3745
|
+
function safeRef2(value) {
|
|
4044
3746
|
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
|
|
4045
3747
|
}
|
|
4046
|
-
function validTimestamp2(value) {
|
|
4047
|
-
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
4048
|
-
}
|
|
4049
|
-
async function assertEndpointVacant(endpoint) {
|
|
4050
|
-
try {
|
|
4051
|
-
await lstat3(endpoint);
|
|
4052
|
-
throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC endpoint is already present.");
|
|
4053
|
-
} catch (error) {
|
|
4054
|
-
if (errorCode8(error) !== "ENOENT") throw error;
|
|
4055
|
-
}
|
|
4056
|
-
}
|
|
4057
3748
|
function listen(server, endpoint) {
|
|
4058
3749
|
return new Promise((resolve, reject) => {
|
|
4059
3750
|
server.once("error", reject);
|
|
@@ -4066,7 +3757,7 @@ function listen(server, endpoint) {
|
|
|
4066
3757
|
async function closeServer(server, sockets, endpoint) {
|
|
4067
3758
|
for (const socket of sockets) socket.destroy();
|
|
4068
3759
|
await new Promise((resolve) => server.close(() => resolve()));
|
|
4069
|
-
if (process.platform !== "win32") await
|
|
3760
|
+
if (process.platform !== "win32") await unlink3(endpoint).catch(() => void 0);
|
|
4070
3761
|
}
|
|
4071
3762
|
var relayEndpointLocks = /* @__PURE__ */ new Map();
|
|
4072
3763
|
function withRelayEndpointLock(endpoint, run) {
|
|
@@ -4086,7 +3777,7 @@ function currentUid(fallback) {
|
|
|
4086
3777
|
return typeof process.geteuid === "function" ? process.geteuid() : fallback;
|
|
4087
3778
|
}
|
|
4088
3779
|
async function assertTrustedEndpointParent(endpoint) {
|
|
4089
|
-
const parent =
|
|
3780
|
+
const parent = dirname5(endpoint);
|
|
4090
3781
|
let stats;
|
|
4091
3782
|
try {
|
|
4092
3783
|
stats = await lstat3(parent);
|
|
@@ -4111,7 +3802,7 @@ function probeRelayEndpoint(endpoint) {
|
|
|
4111
3802
|
timer.unref?.();
|
|
4112
3803
|
socket.once("connect", () => finish("live"));
|
|
4113
3804
|
socket.once("error", (error) => {
|
|
4114
|
-
const code =
|
|
3805
|
+
const code = errorCode7(error);
|
|
4115
3806
|
if (code === "ECONNREFUSED") finish("stale");
|
|
4116
3807
|
else if (code === "ENOENT") finish("missing");
|
|
4117
3808
|
else finish("ambiguous");
|
|
@@ -4146,7 +3837,7 @@ async function readEndpointStats(endpoint) {
|
|
|
4146
3837
|
try {
|
|
4147
3838
|
return await lstat3(endpoint);
|
|
4148
3839
|
} catch (error) {
|
|
4149
|
-
if (
|
|
3840
|
+
if (errorCode7(error) === "ENOENT") return null;
|
|
4150
3841
|
throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC endpoint identity is ambiguous; leaving it untouched.");
|
|
4151
3842
|
}
|
|
4152
3843
|
}
|
|
@@ -4155,17 +3846,17 @@ function requireSameEndpointIdentity(current, owned, message) {
|
|
|
4155
3846
|
}
|
|
4156
3847
|
async function unlinkRelayEndpoint(endpoint) {
|
|
4157
3848
|
try {
|
|
4158
|
-
await
|
|
3849
|
+
await unlink3(endpoint);
|
|
4159
3850
|
} catch (error) {
|
|
4160
|
-
if (
|
|
3851
|
+
if (errorCode7(error) !== "ENOENT") throw error;
|
|
4161
3852
|
}
|
|
4162
3853
|
}
|
|
4163
3854
|
var relayStagingSequence = 0;
|
|
4164
3855
|
function stagingRelayEndpoint(canonical) {
|
|
4165
|
-
const parent =
|
|
3856
|
+
const parent = dirname5(canonical);
|
|
4166
3857
|
const pid = process.pid.toString(36);
|
|
4167
3858
|
const sequence = (relayStagingSequence++ % (36 * 36)).toString(36);
|
|
4168
|
-
return
|
|
3859
|
+
return join5(parent, `.r${pid}${sequence}`);
|
|
4169
3860
|
}
|
|
4170
3861
|
async function readStagingRelayEndpointIdentity(staging) {
|
|
4171
3862
|
let created;
|
|
@@ -4222,8 +3913,121 @@ async function abandonRelayServer(server, sockets) {
|
|
|
4222
3913
|
server.close(() => resolve());
|
|
4223
3914
|
});
|
|
4224
3915
|
}
|
|
4225
|
-
function
|
|
4226
|
-
return
|
|
3916
|
+
function errorCode7(error) {
|
|
3917
|
+
return record3(error) && typeof error.code === "string" ? error.code : null;
|
|
3918
|
+
}
|
|
3919
|
+
|
|
3920
|
+
// host/relay/src/host-registration-state.ts
|
|
3921
|
+
import { constants as constants3 } from "node:fs";
|
|
3922
|
+
import { dirname as dirname6 } from "node:path";
|
|
3923
|
+
var HOST_REGISTRATION_STATE_SCHEMA = "ceal.host_registration_state.v1";
|
|
3924
|
+
var HOST_REGISTRATION_STATE_KEYS = Object.freeze([
|
|
3925
|
+
"credential_generation",
|
|
3926
|
+
"gateway_request_origin",
|
|
3927
|
+
"host_access_token",
|
|
3928
|
+
"host_installation_ref",
|
|
3929
|
+
"host_source_ref",
|
|
3930
|
+
"registration_attempt_ref",
|
|
3931
|
+
"schema_version",
|
|
3932
|
+
"status"
|
|
3933
|
+
]);
|
|
3934
|
+
var HOST_REGISTRATION_STATE_MAX_BYTES = 65536;
|
|
3935
|
+
var HostRegistrationStateError = class extends Error {
|
|
3936
|
+
name = "HostRegistrationStateError";
|
|
3937
|
+
code = "credential";
|
|
3938
|
+
};
|
|
3939
|
+
async function readHostRegistrationAuthority(path, expectedHostSourceRef, platform = process.platform, custody = {}) {
|
|
3940
|
+
let raw;
|
|
3941
|
+
try {
|
|
3942
|
+
await assertPrivateRegistrationDirectory(dirname6(path), platform, custody);
|
|
3943
|
+
raw = platform === "win32" ? await readWindowsRegistration(path, custody) : await readPosixRegistration(path, platform, custody);
|
|
3944
|
+
} catch (error) {
|
|
3945
|
+
throw registrationReadError(error);
|
|
3946
|
+
}
|
|
3947
|
+
if (Buffer.byteLength(raw, "utf8") > HOST_REGISTRATION_STATE_MAX_BYTES) throw new HostRegistrationStateError(`Host registration state is invalid: ${refusalFact("registration_state_bytes", Buffer.byteLength(raw, "utf8"), `at most ${HOST_REGISTRATION_STATE_MAX_BYTES}`)}.`);
|
|
3948
|
+
let value;
|
|
3949
|
+
try {
|
|
3950
|
+
value = JSON.parse(raw);
|
|
3951
|
+
} catch {
|
|
3952
|
+
throw new HostRegistrationStateError("Host registration state is invalid.");
|
|
3953
|
+
}
|
|
3954
|
+
return decodeHostRegistrationAuthority(value, expectedHostSourceRef);
|
|
3955
|
+
}
|
|
3956
|
+
async function readWindowsRegistration(path, custody) {
|
|
3957
|
+
const bytes2 = await readPrivateFileWithSecurefs(path, HOST_REGISTRATION_STATE_MAX_BYTES, custody);
|
|
3958
|
+
try {
|
|
3959
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
3960
|
+
} catch {
|
|
3961
|
+
throw new HostRegistrationStateError("Host registration state is invalid.");
|
|
3962
|
+
}
|
|
3963
|
+
}
|
|
3964
|
+
async function readPosixRegistration(path, platform, custody) {
|
|
3965
|
+
const handle = await openNoFollow(path, constants3.O_RDONLY, void 0, platform, custody);
|
|
3966
|
+
try {
|
|
3967
|
+
const stats = await handle.stat();
|
|
3968
|
+
if (!stats.isFile() || !isSelfOwned(stats.uid, platform, handle) || !isPrivateFileMode(stats.mode, platform, handle)) throw new HostRegistrationStateError("Host registration file is not private to its owner.");
|
|
3969
|
+
return await handle.readFile("utf8");
|
|
3970
|
+
} finally {
|
|
3971
|
+
await handle.close();
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
function registrationReadError(error) {
|
|
3975
|
+
if (error instanceof HostRegistrationStateError) return error;
|
|
3976
|
+
if (error instanceof SecureFileCustodyError && error.reason === "not_private") return new HostRegistrationStateError("Host registration file is not private to its owner.");
|
|
3977
|
+
return new HostRegistrationStateError("Host registration file is unavailable.");
|
|
3978
|
+
}
|
|
3979
|
+
function decodeHostRegistrationAuthority(value, expectedHostSourceRef) {
|
|
3980
|
+
const keys = HOST_REGISTRATION_STATE_KEYS;
|
|
3981
|
+
const fieldCount = record4(value) ? Object.keys(value).length : 0;
|
|
3982
|
+
const missing = record4(value) ? keys.filter((key) => !Object.hasOwn(value, key)).length : keys.length;
|
|
3983
|
+
if (!record4(value) || fieldCount !== keys.length || missing !== 0) throw new HostRegistrationStateError(`Host registration state is invalid or does not match this Relay: ${refusalFacts([{ name: "registration_field_count", value: fieldCount, expected: `exactly ${keys.length}` }, { name: "missing_registration_field_count", value: missing, expected: "0" }])}.`);
|
|
3984
|
+
if (!validRegistrationIdentity(value, expectedHostSourceRef)) throw new HostRegistrationStateError(`Host registration state is invalid or does not match this Relay: ${refusalFacts([{ name: "schema_version", value: value.schema_version, expected: HOST_REGISTRATION_STATE_SCHEMA }, { name: "status", value: value.status, expected: "active" }, { name: "host_source_ref", value: value.host_source_ref, expected: expectedHostSourceRef }, { name: "registration_attempt_ref", value: value.registration_attempt_ref, expected: "host-registration-attempt:<uuid>" }, { name: "host_installation_ref", value: value.host_installation_ref, expected: "host-installation:<uuid>" }])}.`);
|
|
3985
|
+
if (!validRegistrationCredential(value)) throw new HostRegistrationStateError(`Host registration state is invalid or does not match this Relay: ${refusalFacts([{ name: "credential_generation", value: value.credential_generation, expected: "a positive safe integer" }, { name: "host_access_token_matches_registered_shape", value: validHostAccessToken(value.host_access_token), expected: "true" }])}.`);
|
|
3986
|
+
if (!validGatewayOrigin(value.gateway_request_origin)) throw new HostRegistrationStateError(`Host registration state is invalid or does not match this Relay: ${refusalFact("gateway_request_origin", value.gateway_request_origin, "an HTTPS origin or explicit-port loopback HTTP origin")}.`);
|
|
3987
|
+
return { gateway_request_origin: value.gateway_request_origin, host_access_token: value.host_access_token };
|
|
3988
|
+
}
|
|
3989
|
+
function validRegistrationIdentity(value, expectedHostSourceRef) {
|
|
3990
|
+
return value.schema_version === HOST_REGISTRATION_STATE_SCHEMA && value.status === "active" && value.host_source_ref === expectedHostSourceRef && typeof value.registration_attempt_ref === "string" && CEAL_HOST_REGISTRATION_ATTEMPT_REF_PATTERN.test(value.registration_attempt_ref) && typeof value.host_installation_ref === "string" && CEAL_HOST_INSTALLATION_REF_PATTERN.test(value.host_installation_ref) && typeof value.host_source_ref === "string" && hostSourceUuid(value.host_source_ref) !== null;
|
|
3991
|
+
}
|
|
3992
|
+
function validRegistrationCredential(value) {
|
|
3993
|
+
return Number.isSafeInteger(value.credential_generation) && Number(value.credential_generation) > 0 && validHostAccessToken(value.host_access_token);
|
|
3994
|
+
}
|
|
3995
|
+
function validHostAccessToken(value) {
|
|
3996
|
+
return typeof value === "string" && CEAL_HOST_ACCESS_TOKEN_PATTERN.test(value);
|
|
3997
|
+
}
|
|
3998
|
+
function validGatewayOrigin(value) {
|
|
3999
|
+
try {
|
|
4000
|
+
if (typeof value !== "string" || /\s|\0/u.test(value)) return false;
|
|
4001
|
+
const parsed = new URL(value);
|
|
4002
|
+
const hostname = parsed.hostname.toLowerCase().replace(/\.+$/u, "");
|
|
4003
|
+
return hostname !== "" && validGatewayPort(parsed.port) && (loopbackHttpOrigin(parsed, hostname) || parsed.protocol === "https:") && cleanOriginPath(parsed) && bareOriginSuffix(value);
|
|
4004
|
+
} catch {
|
|
4005
|
+
return false;
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
function loopbackHttpOrigin(parsed, hostname) {
|
|
4009
|
+
return parsed.protocol === "http:" && parsed.port !== "" && (hostname === "127.0.0.1" || hostname === "[::1]");
|
|
4010
|
+
}
|
|
4011
|
+
function validGatewayPort(port) {
|
|
4012
|
+
if (port === "") return true;
|
|
4013
|
+
const numeric = Number(port);
|
|
4014
|
+
return Number.isInteger(numeric) && numeric >= 1 && numeric <= 65535;
|
|
4015
|
+
}
|
|
4016
|
+
function cleanOriginPath(parsed) {
|
|
4017
|
+
return parsed.username === "" && parsed.password === "" && parsed.pathname === "/" && parsed.search === "" && parsed.hash === "";
|
|
4018
|
+
}
|
|
4019
|
+
function bareOriginSuffix(value) {
|
|
4020
|
+
const schemeEnd = value.indexOf("://");
|
|
4021
|
+
const authority = schemeEnd < 0 ? "" : value.slice(schemeEnd + 3);
|
|
4022
|
+
const suffixOffset = authority.search(/[\\/?#]/u);
|
|
4023
|
+
const rawSuffix = suffixOffset < 0 ? "" : authority.slice(suffixOffset);
|
|
4024
|
+
return rawSuffix === "" || rawSuffix === "/";
|
|
4025
|
+
}
|
|
4026
|
+
async function assertPrivateRegistrationDirectory(path, platform, custody) {
|
|
4027
|
+
if (!await isPrivateDirectory(path, platform, custody)) throw new HostRegistrationStateError("Host registration directory is not private to its owner.");
|
|
4028
|
+
}
|
|
4029
|
+
function record4(value) {
|
|
4030
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4227
4031
|
}
|
|
4228
4032
|
|
|
4229
4033
|
// host/relay/src/host-relay-runtime.ts
|
|
@@ -4239,16 +4043,24 @@ var HostRelayConfigError = class extends Error {
|
|
|
4239
4043
|
this.code = code;
|
|
4240
4044
|
}
|
|
4241
4045
|
};
|
|
4242
|
-
async function readHostRelayConfig(path) {
|
|
4046
|
+
async function readHostRelayConfig(path, platform = process.platform, custody = {}) {
|
|
4243
4047
|
if (!isAbsoluteNormalizedNonRootPath(path, process.platform, true)) throw new HostRelayConfigError("invalid_config_path", `invalid_config_path: Host Relay config path is invalid: ${refusalFact("config_path", path, "absolute, normalized, and non-root")}.`);
|
|
4244
4048
|
let handle;
|
|
4245
4049
|
try {
|
|
4246
|
-
await
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4050
|
+
await assertPrivateRelayConfigDirectory(dirname7(path), platform, custody);
|
|
4051
|
+
let text;
|
|
4052
|
+
if (platform === "win32") {
|
|
4053
|
+
const bytes2 = await readPrivateFileWithSecurefs(path, HOST_RELAY_CONFIG_MAX_BYTES, custody);
|
|
4054
|
+
if (bytes2.byteLength < HOST_RELAY_CONFIG_MIN_BYTES) throw new HostRelayConfigError("invalid_config", `invalid_config: Host Relay config size is invalid: ${refusalFact("config_bytes", bytes2.byteLength, `between ${HOST_RELAY_CONFIG_MIN_BYTES} and ${HOST_RELAY_CONFIG_MAX_BYTES}`)}.`);
|
|
4055
|
+
try {
|
|
4056
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
|
|
4057
|
+
} catch {
|
|
4058
|
+
throw new HostRelayConfigError("invalid_json", "Host Relay config is not valid UTF-8 JSON.");
|
|
4059
|
+
}
|
|
4060
|
+
} else {
|
|
4061
|
+
handle = await openPrivateRelayConfigFile(path, platform, custody);
|
|
4062
|
+
text = await handle.readFile("utf8");
|
|
4063
|
+
}
|
|
4252
4064
|
assertNoDuplicateJsonKeys(text);
|
|
4253
4065
|
let value;
|
|
4254
4066
|
try {
|
|
@@ -4256,114 +4068,236 @@ async function readHostRelayConfig(path) {
|
|
|
4256
4068
|
} catch {
|
|
4257
4069
|
throw new HostRelayConfigError("invalid_json", "Host Relay config is not JSON.");
|
|
4258
4070
|
}
|
|
4259
|
-
const
|
|
4071
|
+
const decoded = decodeHostRelayConfig(value);
|
|
4072
|
+
const config = resolveRelayIpcEndpoint(decoded);
|
|
4260
4073
|
validateHostRelaySourcePaths(config);
|
|
4261
4074
|
return config;
|
|
4262
4075
|
} catch (error) {
|
|
4263
|
-
|
|
4264
|
-
throw new HostRelayConfigError("config_unavailable", "Host Relay config is unavailable.");
|
|
4076
|
+
throw relayConfigReadError(error);
|
|
4265
4077
|
} finally {
|
|
4266
4078
|
await handle?.close();
|
|
4267
4079
|
}
|
|
4268
4080
|
}
|
|
4081
|
+
async function openPrivateRelayConfigFile(path, platform, custody) {
|
|
4082
|
+
const handle = await openNoFollow(path, constants4.O_RDONLY, void 0, platform, custody);
|
|
4083
|
+
try {
|
|
4084
|
+
const stats = await handle.stat();
|
|
4085
|
+
if (!stats.isFile() || !isSelfOwned(stats.uid, platform, handle) || !isPrivateFileMode(stats.mode, platform, handle)) throw new HostRelayConfigError("config_not_private", "Host Relay config file is not private to its owner.");
|
|
4086
|
+
if (stats.size < HOST_RELAY_CONFIG_MIN_BYTES || stats.size > HOST_RELAY_CONFIG_MAX_BYTES) throw new HostRelayConfigError("invalid_config", `invalid_config: Host Relay config size is invalid: ${refusalFact("config_bytes", stats.size, `between ${HOST_RELAY_CONFIG_MIN_BYTES} and ${HOST_RELAY_CONFIG_MAX_BYTES}`)}.`);
|
|
4087
|
+
return handle;
|
|
4088
|
+
} catch (error) {
|
|
4089
|
+
await handle.close();
|
|
4090
|
+
throw error;
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
function relayConfigReadError(error) {
|
|
4094
|
+
if (error instanceof HostRelayConfigError) return error;
|
|
4095
|
+
if (error instanceof SecureFileCustodyError && error.reason === "not_private") return new HostRelayConfigError("config_not_private", "Host Relay config file is not private to its owner.");
|
|
4096
|
+
if (error instanceof SecureFileCustodyError && error.reason === "too_large") return new HostRelayConfigError("invalid_config", `invalid_config: Host Relay config exceeds ${HOST_RELAY_CONFIG_MAX_BYTES} bytes.`);
|
|
4097
|
+
return new HostRelayConfigError("config_unavailable", "Host Relay config is unavailable.");
|
|
4098
|
+
}
|
|
4099
|
+
async function assertPrivateRelayConfigDirectory(path, platform, custody) {
|
|
4100
|
+
try {
|
|
4101
|
+
if (!await isPrivateDirectory(path, platform, custody)) throw new HostRelayConfigError("config_not_private", "Host Relay config directory is not private to its owner.");
|
|
4102
|
+
} catch (error) {
|
|
4103
|
+
if (error instanceof HostRelayConfigError) throw error;
|
|
4104
|
+
throw new HostRelayConfigError("config_unavailable", "Host Relay config directory is unavailable.");
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
function resolveRelayIpcEndpoint(decoded) {
|
|
4108
|
+
if (process.platform !== "win32") return decoded;
|
|
4109
|
+
const endpoint = assertHostRelaySourcePaths(dirname7(decoded.registration_state_file), decoded);
|
|
4110
|
+
return endpoint === decoded.ipc_endpoint ? decoded : { ...decoded, ipc_endpoint: endpoint };
|
|
4111
|
+
}
|
|
4269
4112
|
function validateHostRelaySourcePaths(config) {
|
|
4270
4113
|
try {
|
|
4271
|
-
assertHostRelaySourcePaths(
|
|
4114
|
+
assertHostRelaySourcePaths(dirname7(config.registration_state_file), config);
|
|
4272
4115
|
} catch (error) {
|
|
4273
4116
|
throw new HostRelayConfigError("invalid_config", error instanceof Error ? error.message : "Host Relay source tuple is invalid.");
|
|
4274
4117
|
}
|
|
4275
4118
|
}
|
|
4276
|
-
function decodeHostRelayConfig(value) {
|
|
4277
|
-
const
|
|
4278
|
-
const
|
|
4279
|
-
|
|
4280
|
-
|
|
4119
|
+
function decodeHostRelayConfig(value, platform = process.platform) {
|
|
4120
|
+
const ignoreEndpoint = platform === "win32";
|
|
4121
|
+
const required = ignoreEndpoint ? ["schema_version", "registration_state_file", "host_source_ref", "state_root"] : ["schema_version", "registration_state_file", "host_source_ref", "state_root", "ipc_endpoint"];
|
|
4122
|
+
const optional = ignoreEndpoint ? ["ipc_endpoint", "drain_interval_ms"] : ["drain_interval_ms"];
|
|
4123
|
+
if (!record5(value) || !exactOptionalKeys2(value, required, optional) || !validConfigIdentity(value, platform) || !validConfigRuntime(value, platform)) {
|
|
4124
|
+
throw new HostRelayConfigError("invalid_config", `Host Relay config shape is invalid: ${refusalFacts(relayShapeFacts(value, required, optional, platform))}.`);
|
|
4281
4125
|
}
|
|
4282
4126
|
return {
|
|
4283
4127
|
schema_version: HOST_RELAY_CONFIG_SCHEMA,
|
|
4284
4128
|
registration_state_file: value.registration_state_file,
|
|
4285
4129
|
host_source_ref: value.host_source_ref,
|
|
4286
4130
|
state_root: value.state_root,
|
|
4287
|
-
ipc_endpoint: value.ipc_endpoint,
|
|
4288
|
-
drain_interval_ms: value.drain_interval_ms ?? 250
|
|
4289
|
-
gateway_timeout_ms: value.gateway_timeout_ms ?? 5e3
|
|
4131
|
+
ipc_endpoint: typeof value.ipc_endpoint === "string" ? value.ipc_endpoint : "",
|
|
4132
|
+
drain_interval_ms: value.drain_interval_ms ?? 250
|
|
4290
4133
|
};
|
|
4291
4134
|
}
|
|
4292
|
-
function validConfigIdentity(value) {
|
|
4293
|
-
return value.schema_version === HOST_RELAY_CONFIG_SCHEMA && isAbsoluteNormalizedNonRootPath(value.registration_state_file,
|
|
4135
|
+
function validConfigIdentity(value, platform) {
|
|
4136
|
+
return value.schema_version === HOST_RELAY_CONFIG_SCHEMA && isAbsoluteNormalizedNonRootPath(value.registration_state_file, platform, true) && opaqueHostSourceRef(value.host_source_ref);
|
|
4137
|
+
}
|
|
4138
|
+
function relayShapeFacts(value, required, optional, platform) {
|
|
4139
|
+
if (!record5(value)) return [{ name: "config", value: "wrong_type", expected: "a record" }];
|
|
4140
|
+
const facts = [
|
|
4141
|
+
...Object.keys(value).filter((key) => !required.includes(key) && !optional.includes(key)).map((key) => ({ name: key, value: "unknown", expected: `one of ${[...required, ...optional].join(", ")}` })),
|
|
4142
|
+
...required.filter((key) => !Object.hasOwn(value, key)).map((key) => ({ name: key, value: "missing", expected: "present" }))
|
|
4143
|
+
];
|
|
4144
|
+
if (Object.hasOwn(value, "schema_version") && value.schema_version !== HOST_RELAY_CONFIG_SCHEMA) facts.push({ name: "schema_version", value: "wrong_type", expected: HOST_RELAY_CONFIG_SCHEMA });
|
|
4145
|
+
for (const fact of relayValueFacts(value, platform)) facts.push(fact);
|
|
4146
|
+
return facts;
|
|
4147
|
+
}
|
|
4148
|
+
function relayValueFacts(value, platform) {
|
|
4149
|
+
const facts = [];
|
|
4150
|
+
for (const key of platform === "win32" ? ["registration_state_file", "state_root"] : ["registration_state_file", "state_root", "ipc_endpoint"]) {
|
|
4151
|
+
if (Object.hasOwn(value, key) && !isAbsoluteNormalizedNonRootPath(value[key], platform, true)) facts.push({ name: key, value: "wrong_type", expected: "an absolute normalized non-root path" });
|
|
4152
|
+
}
|
|
4153
|
+
if (Object.hasOwn(value, "host_source_ref") && !opaqueHostSourceRef(value.host_source_ref)) facts.push({ name: "host_source_ref", value: "wrong_type", expected: "a host source reference" });
|
|
4154
|
+
if (Object.hasOwn(value, "drain_interval_ms") && !boundedInteger(value.drain_interval_ms, 50, 3e4, true)) facts.push({ name: "drain_interval_ms", value: "wrong_type", expected: "between 50 and 30000" });
|
|
4155
|
+
return facts;
|
|
4294
4156
|
}
|
|
4295
|
-
function validConfigRuntime(value) {
|
|
4296
|
-
return isAbsoluteNormalizedNonRootPath(value.state_root,
|
|
4157
|
+
function validConfigRuntime(value, platform) {
|
|
4158
|
+
return isAbsoluteNormalizedNonRootPath(value.state_root, platform, true) && (platform === "win32" || isAbsoluteNormalizedNonRootPath(value.ipc_endpoint, platform, true)) && boundedInteger(value.drain_interval_ms, 50, 3e4, true);
|
|
4297
4159
|
}
|
|
4298
4160
|
var HOST_RELAY_INVOCATION_MINT_LOG_TAG = "[host-relay-invocation-mint]";
|
|
4299
|
-
|
|
4161
|
+
function formatHostRelayInvocationMintLine(result2) {
|
|
4162
|
+
return `${HOST_RELAY_INVOCATION_MINT_LOG_TAG} outcome=${result2.status === "ready" ? "completed" : "failed"}${result2.status === "refused" ? ` reason=${result2.reason}` : ""}`;
|
|
4163
|
+
}
|
|
4164
|
+
function reportInvocationMintLine(line) {
|
|
4165
|
+
try {
|
|
4166
|
+
process.stderr.write(line);
|
|
4167
|
+
} catch {
|
|
4168
|
+
}
|
|
4169
|
+
writeHostServiceLogLine(line);
|
|
4170
|
+
}
|
|
4300
4171
|
async function serveHostRelay(config, signal, dependencies = {}) {
|
|
4301
|
-
const admission = new RelayAdmissionStore(
|
|
4302
|
-
const bindings = new RelayRequestBindingStore(join7(config.state_root, "bindings"));
|
|
4303
|
-
const gateway = (dependencies.createGateway ?? createHostRelayGatewayHttpPort)({ registration_state_file: config.registration_state_file, host_source_ref: config.host_source_ref });
|
|
4304
|
-
const bridge = new RelayGatewayBridge({ state_root: join7(config.state_root, "gateway"), host_source_ref: config.host_source_ref, gateway, timeout_ms: config.gateway_timeout_ms, on_request_bound: async (binding) => {
|
|
4305
|
-
await bindings.bind(binding);
|
|
4306
|
-
} });
|
|
4172
|
+
const admission = new RelayAdmissionStore(join6(config.state_root, "admission"));
|
|
4307
4173
|
let server;
|
|
4308
4174
|
try {
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4175
|
+
const pipeKey = process.platform === "win32" ? await loadRelayPipeKey(config.state_root) : void 0;
|
|
4176
|
+
server = await startHostRelayRuntimeIpc(config, admission, dependencies, pipeKey);
|
|
4177
|
+
await waitForRelayShutdown(config, signal, dependencies);
|
|
4178
|
+
} catch (error) {
|
|
4179
|
+
if (error instanceof HostRelayAlreadyRunningError) return;
|
|
4180
|
+
throw error;
|
|
4314
4181
|
} finally {
|
|
4315
4182
|
await server?.close();
|
|
4316
4183
|
}
|
|
4317
4184
|
}
|
|
4318
|
-
async function
|
|
4185
|
+
async function waitForRelayShutdown(config, signal, dependencies) {
|
|
4186
|
+
while (!signal.aborted) await (dependencies.wait ?? ((milliseconds, waitSignal) => waitForAbort(waitSignal, milliseconds)))(config.drain_interval_ms, signal);
|
|
4187
|
+
}
|
|
4188
|
+
async function startHostRelayRuntimeIpc(config, admission, dependencies, pipeKey) {
|
|
4319
4189
|
return (dependencies.startIpc ?? startHostRelayIpcServer)({
|
|
4320
4190
|
endpoint: config.ipc_endpoint,
|
|
4321
4191
|
admission,
|
|
4322
|
-
|
|
4323
|
-
mintInvocationContext: (input,
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4192
|
+
...pipeKey === void 0 ? {} : { pipeKey },
|
|
4193
|
+
mintInvocationContext: async (input, signal) => {
|
|
4194
|
+
let result2;
|
|
4195
|
+
try {
|
|
4196
|
+
result2 = await mintInvocationContextAtGateway(config, input, signal, dependencies.fetch ?? fetch);
|
|
4197
|
+
} catch (error) {
|
|
4198
|
+
const refused = { schema_version: "ceal.host_relay_invocation_context_result.v2", status: "refused", reason: "gateway_unavailable" };
|
|
4199
|
+
(dependencies.logInvocationMint ?? reportInvocationMintLine)(`${formatHostRelayInvocationMintLine(refused)}
|
|
4200
|
+
`);
|
|
4201
|
+
throw error;
|
|
4202
|
+
}
|
|
4203
|
+
(dependencies.logInvocationMint ?? reportInvocationMintLine)(`${formatHostRelayInvocationMintLine(result2)}
|
|
4204
|
+
`);
|
|
4205
|
+
return result2;
|
|
4206
|
+
}
|
|
4327
4207
|
});
|
|
4328
4208
|
}
|
|
4329
|
-
async function
|
|
4209
|
+
async function mintInvocationContextAtGateway(config, input, signal, fetchGateway) {
|
|
4330
4210
|
try {
|
|
4331
|
-
|
|
4211
|
+
const registration = await readHostRegistrationAuthority(config.registration_state_file, config.host_source_ref);
|
|
4212
|
+
return gatewayMintPost(registration, input, signal, fetchGateway);
|
|
4332
4213
|
} catch (error) {
|
|
4333
|
-
|
|
4334
|
-
throw new HostRelayInvocationMintRefusal(invocationMintRefusalReason(error, cancellation));
|
|
4214
|
+
return gatewayMintFailure(signal, error);
|
|
4335
4215
|
}
|
|
4336
4216
|
}
|
|
4337
|
-
function
|
|
4338
|
-
|
|
4339
|
-
if (
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
if (error.error_code === "request_context_refused" && isVocabularyWord(CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS, error.refusal_reason)) return error.refusal_reason;
|
|
4349
|
-
return GATEWAY_MINT_REFUSAL_REASONS.get(error.error_code ?? "") ?? "gateway_refused";
|
|
4350
|
-
}
|
|
4351
|
-
var GATEWAY_MINT_REFUSAL_REASONS = /* @__PURE__ */ new Map([
|
|
4352
|
-
["request_context_refused", "context_stale"],
|
|
4353
|
-
["context_expired", "context_expired"],
|
|
4354
|
-
["host_source_refused", "host_source_refused"],
|
|
4355
|
-
["host_invocation_context_unavailable", "gateway_unavailable"]
|
|
4356
|
-
]);
|
|
4357
|
-
function scheduleInvocationMintDeadline2(expire, milliseconds) {
|
|
4358
|
-
const timeout = setTimeout(expire, milliseconds);
|
|
4359
|
-
return () => clearTimeout(timeout);
|
|
4217
|
+
async function gatewayMintPost(registration, input, signal, fetchGateway) {
|
|
4218
|
+
const serialized = JSON.stringify(gatewayMintBody(input));
|
|
4219
|
+
if (Buffer.byteLength(serialized, "utf8") > CEAL_HOST_INVOCATION_CONTEXT_REQUEST_MAX_BYTES) return mintRefusal("producer_frame_invalid");
|
|
4220
|
+
const response2 = await fetchGateway(`${registration.gateway_request_origin}${CEAL_HOST_INVOCATION_CONTEXT_PATH}`, {
|
|
4221
|
+
method: "POST",
|
|
4222
|
+
headers: { authorization: `Bearer ${registration.host_access_token}`, "content-type": "application/json" },
|
|
4223
|
+
body: serialized,
|
|
4224
|
+
signal
|
|
4225
|
+
});
|
|
4226
|
+
const raw = await readBoundedResponseText(response2, 8192);
|
|
4227
|
+
return decodeGatewayMintResponse(response2, raw);
|
|
4360
4228
|
}
|
|
4361
|
-
function
|
|
4362
|
-
|
|
4363
|
-
|
|
4229
|
+
function gatewayMintBody(input) {
|
|
4230
|
+
return {
|
|
4231
|
+
schema_version: CEAL_HOST_INVOCATION_CONTEXT_REQUEST_SCHEMA,
|
|
4232
|
+
client_nonce: input.client_nonce,
|
|
4233
|
+
context_expires_at: input.context_expires_at,
|
|
4234
|
+
host_binding: input.host_binding,
|
|
4235
|
+
human_message_window: input.human_message_window,
|
|
4236
|
+
...input.last_seen_request_ref === void 0 ? {} : { last_seen_request_ref: input.last_seen_request_ref },
|
|
4237
|
+
...input.visible_output_max_bytes === void 0 ? {} : { visible_output_max_bytes: input.visible_output_max_bytes }
|
|
4238
|
+
};
|
|
4239
|
+
}
|
|
4240
|
+
function decodeGatewayMintResponse(response2, raw) {
|
|
4241
|
+
if (raw === null) return mintRefusal("gateway_response_invalid");
|
|
4242
|
+
let value;
|
|
4243
|
+
try {
|
|
4244
|
+
value = JSON.parse(raw);
|
|
4245
|
+
} catch {
|
|
4246
|
+
value = null;
|
|
4247
|
+
}
|
|
4248
|
+
if (!response2.ok) return mintRefusal(gatewayMintRefusal(response2.status, value));
|
|
4249
|
+
if (!isMintResult(value)) return mintRefusal("gateway_response_invalid");
|
|
4250
|
+
const requestRef = response2.headers.get("ceal-request-ref");
|
|
4251
|
+
if (requestRef === null || !/^ceal-request:[a-f0-9-]{36}$/u.test(requestRef)) return mintRefusal("gateway_response_invalid");
|
|
4252
|
+
const result2 = decodeCealHostInvocationAuthenticationResult(value, /* @__PURE__ */ new Date());
|
|
4253
|
+
return { schema_version: "ceal.host_relay_invocation_context_result.v2", status: "ready", request_ref: requestRef, result: result2 };
|
|
4254
|
+
}
|
|
4255
|
+
function gatewayMintFailure(signal, _error) {
|
|
4256
|
+
return mintRefusal(signal.aborted ? "transient_retryable" : "gateway_unavailable");
|
|
4257
|
+
}
|
|
4258
|
+
function mintRefusal(reason) {
|
|
4259
|
+
return { schema_version: "ceal.host_relay_invocation_context_result.v2", status: "refused", reason };
|
|
4260
|
+
}
|
|
4261
|
+
async function readBoundedResponseText(response2, maximumBytes) {
|
|
4262
|
+
if (response2.body === null) return null;
|
|
4263
|
+
const reader = response2.body.getReader();
|
|
4264
|
+
const chunks = [];
|
|
4265
|
+
let total = 0;
|
|
4266
|
+
try {
|
|
4267
|
+
while (true) {
|
|
4268
|
+
const { done, value } = await reader.read();
|
|
4269
|
+
if (done) break;
|
|
4270
|
+
total += value.byteLength;
|
|
4271
|
+
if (total > maximumBytes) {
|
|
4272
|
+
await reader.cancel();
|
|
4273
|
+
return null;
|
|
4274
|
+
}
|
|
4275
|
+
chunks.push(value);
|
|
4276
|
+
}
|
|
4277
|
+
const output = new Uint8Array(total);
|
|
4278
|
+
let offset = 0;
|
|
4279
|
+
for (const chunk of chunks) {
|
|
4280
|
+
output.set(chunk, offset);
|
|
4281
|
+
offset += chunk.byteLength;
|
|
4282
|
+
}
|
|
4283
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(output);
|
|
4284
|
+
} catch {
|
|
4285
|
+
return null;
|
|
4286
|
+
}
|
|
4364
4287
|
}
|
|
4365
|
-
function
|
|
4366
|
-
return
|
|
4288
|
+
function isMintResult(value) {
|
|
4289
|
+
return record5(value) && Object.keys(value).length === 3 && value.schema_version === CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_SCHEMA && typeof value.context_token === "string" && typeof value.expires_at === "string";
|
|
4290
|
+
}
|
|
4291
|
+
function gatewayMintRefusal(status, value) {
|
|
4292
|
+
if (status === 401) return "host_credential_refused";
|
|
4293
|
+
if (status === 403 && record5(value)) {
|
|
4294
|
+
if (value.error_code === "host_source_refused") return "host_source_refused";
|
|
4295
|
+
if (value.error_code === "context_expired") return "context_expired";
|
|
4296
|
+
if (value.error_code === "request_context_refused") return "context_stale";
|
|
4297
|
+
}
|
|
4298
|
+
if (status === 429) return "gateway_rate_limited";
|
|
4299
|
+
if (status >= 500) return "gateway_unavailable";
|
|
4300
|
+
return "gateway_refused";
|
|
4367
4301
|
}
|
|
4368
4302
|
async function runHostRelayCli(args, signal, dependencies = {}) {
|
|
4369
4303
|
try {
|
|
@@ -4382,20 +4316,6 @@ function parseServeArgs(args) {
|
|
|
4382
4316
|
}
|
|
4383
4317
|
return args[2];
|
|
4384
4318
|
}
|
|
4385
|
-
async function assertOwnerPrivateDirectory2(path) {
|
|
4386
|
-
try {
|
|
4387
|
-
const stats = await lstat4(path);
|
|
4388
|
-
if (!stats.isDirectory() || stats.isSymbolicLink() || !ownerPrivate4(stats)) throw new HostRelayConfigError("config_not_private", "Host Relay config directory is not owner-private.");
|
|
4389
|
-
} catch (error) {
|
|
4390
|
-
if (error instanceof HostRelayConfigError) throw error;
|
|
4391
|
-
throw new HostRelayConfigError("config_unavailable", "Host Relay config directory is unavailable.");
|
|
4392
|
-
}
|
|
4393
|
-
}
|
|
4394
|
-
function ownerPrivate4(stats) {
|
|
4395
|
-
if (process.platform === "win32") return true;
|
|
4396
|
-
const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
|
|
4397
|
-
return stats.uid === uid && (stats.mode & 63) === 0;
|
|
4398
|
-
}
|
|
4399
4319
|
function assertNoDuplicateJsonKeys(text) {
|
|
4400
4320
|
rejectDuplicateJsonKeys(text, {
|
|
4401
4321
|
invalid: () => new HostRelayConfigError("invalid_json", "Host Relay config is not JSON."),
|
|
@@ -4414,12 +4334,14 @@ function exactOptionalKeys2(value, required, optional) {
|
|
|
4414
4334
|
}
|
|
4415
4335
|
|
|
4416
4336
|
// host/relay/bin/ceal-host-relay.ts
|
|
4337
|
+
var serviceArguments = consumeHostServiceLogArgument(process.argv.slice(2));
|
|
4338
|
+
if (serviceArguments.path !== void 0) process.env[HOST_SERVICE_LOG_ENVIRONMENT] = serviceArguments.path;
|
|
4417
4339
|
var controller = new AbortController();
|
|
4418
4340
|
var stop = () => controller.abort();
|
|
4419
4341
|
process.once("SIGINT", stop);
|
|
4420
4342
|
process.once("SIGTERM", stop);
|
|
4421
4343
|
var stopLogBound = boundHostServiceLog();
|
|
4422
|
-
void runHostRelayCli(
|
|
4344
|
+
void runHostRelayCli(serviceArguments.args, controller.signal).then((result2) => {
|
|
4423
4345
|
process.stdout.write(`${JSON.stringify({ schema_version: "ceal.host_relay_command.v1", ...result2 })}
|
|
4424
4346
|
`);
|
|
4425
4347
|
process.exitCode = result2.ok ? 0 : 1;
|