@corca-org/ceal-host-linux-arm64 1.0.20 → 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.
@@ -33,7 +33,7 @@ function isClaudeHookEvent(value) {
33
33
  }
34
34
 
35
35
  // host/claude/src/claude-decoder.ts
36
- import { createHash as createHash2 } from "node:crypto";
36
+ import { createHash as createHash3 } from "node:crypto";
37
37
 
38
38
  // packages/ceal-client-protocol/src/canonical-json.ts
39
39
  function cealCanonicalJson(value) {
@@ -62,16 +62,34 @@ function isPlainRecord(value) {
62
62
  return prototype === Object.prototype || prototype === null;
63
63
  }
64
64
 
65
+ // packages/ceal-client-protocol/src/client-wire-contract.ts
66
+ import { createHash } from "node:crypto";
67
+
65
68
  // packages/ceal-client-protocol/client-wire-contract.json
66
69
  var client_wire_contract_default = {
67
70
  schema_version: "ceal.client_wire_contract.v1",
71
+ doctor: {
72
+ schema_version: "ceal.doctor.v1"
73
+ },
74
+ client_commands: {
75
+ doctor: ["doctor"],
76
+ version: ["version"],
77
+ access_group_list: ["access-group", "list"]
78
+ },
79
+ client_environment: {
80
+ environment_prefix: "CEAL",
81
+ context_override_suffixes: ["GATEWAY_ORIGIN", "GATEWAY_TRANSPORT_ORIGIN", "ACCESS_TOKEN", "CONTEXT_STORE_PATH"]
82
+ },
68
83
  canonical_json: {
69
84
  ordering: "utf16_code_unit_ascending"
70
85
  },
71
86
  artifact_descriptor: {
72
- operation_contract_bundle_schema_version: "ceal.operation_contract_bundle.v1",
73
- schema_version: "ceal.verified_artifact_descriptor.v1",
74
- binding_schema_version: "ceal.verified_artifact_binding.v1",
87
+ operation_contract_bundle_schema_name: "ceal.operation_contract_bundle",
88
+ operation_contract_bundle_fields: ["schema_version", "catalog_revision", "operations", "failure_guidance"],
89
+ schema_name: "ceal.verified_artifact_descriptor",
90
+ fields: ["schema_version", "kind", "media_type", "byte_count", "sha256", "binding"],
91
+ binding_schema_name: "ceal.verified_artifact_binding",
92
+ binding_fields: ["schema_version", "kind", "catalog_revision"],
75
93
  kind: "verified_artifact",
76
94
  binding_kind: "catalog",
77
95
  media_type: "application/json",
@@ -87,14 +105,16 @@ var client_wire_contract_default = {
87
105
  length: 72,
88
106
  pattern: "^catalog:[a-f0-9]{64}$"
89
107
  },
90
- envelope_max_bytes: 389
108
+ envelope_max_bytes: 407
91
109
  },
92
110
  command_request: {
93
- schema_version: "ceal.command_request.v1",
111
+ schema_name: "ceal.command_request",
112
+ fields: ["schema_version", "argv", "operation", "client_nonce", "context_token"],
94
113
  absolute_transport_max_bytes: 65536
95
114
  },
96
115
  command_resolution: {
97
- schema_version: "ceal.gateway_command_resolution.v1",
116
+ schema_name: "ceal.gateway_command_resolution",
117
+ 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"],
98
118
  authority: {
99
119
  anonymous: {
100
120
  state: "anonymous",
@@ -134,7 +154,8 @@ var client_wire_contract_default = {
134
154
  }
135
155
  },
136
156
  command_response: {
137
- schema_version: "ceal.command_response.v1",
157
+ schema_name: "ceal.command_response",
158
+ fields: ["schema_version", "exit_code", "document", "operation_id", "visible_output_max_bytes", "local_delivery", "verified_artifact"],
138
159
  descriptor_field: "verified_artifact",
139
160
  required_fields: [
140
161
  "schema_version",
@@ -158,29 +179,41 @@ var client_wire_contract_default = {
158
179
  "required_action_sequence",
159
180
  "withheld"
160
181
  ],
161
- prompt_field: "prompt",
162
- prompt_schema_version: "ceal.prompt.v1",
163
- focused_prompt_document_fields: [
164
- "catalog_revision",
165
- "prompt"
166
- ],
167
- focused_prompt_continuation_document_fields: [
168
- "catalog_revision",
169
- "remaining_work",
170
- "prompt"
171
- ],
172
182
  discovery_command: "discovery",
173
- discovery_manifest_schema_version: "ceal.discovery_manifest.v1",
183
+ discovery_manifest_schema_name: "ceal.discovery_manifest",
184
+ discovery_manifest_fields: ["schema_version", "skill", "authority_scope", "connector_kinds", "personal_connector_kinds", "guide_markdown", "renderer_version", "digest"],
174
185
  discovery_manifest_document_fields: [
175
186
  "discovery_manifest"
176
187
  ],
177
188
  exit_code_max: 125,
178
189
  bare_document_max_bytes: 65536,
179
- complete_max_bytes: 66019
190
+ complete_max_bytes: 66046
191
+ },
192
+ host_invocation_authentication: {
193
+ request_schema_name: "ceal.host_invocation_authentication_request",
194
+ request_fields: [
195
+ "schema_version",
196
+ "client_nonce"
197
+ ],
198
+ result_schema_name: "ceal.host_invocation_authentication_result",
199
+ result_fields: [
200
+ "schema_version",
201
+ "context_token",
202
+ "expires_at"
203
+ ],
204
+ client_nonce: {
205
+ character_class_source: "A-Za-z0-9_-",
206
+ encoded_characters: 43
207
+ }
208
+ },
209
+ gateway_host_invocation_context_request: {
210
+ schema_name: "ceal.gateway_host_invocation_context_request",
211
+ fields: ["schema_version", "client_nonce", "context_expires_at", "request_work_context", "current_prompt", "visible_output_max_bytes"]
180
212
  },
181
213
  resource_stream: {
182
214
  path: "/api/ceal/v2/resources",
183
- request_schema_version: "ceal.resource_stream_request.v1",
215
+ request_schema_name: "ceal.resource_stream_request",
216
+ request_fields: ["schema_version", "resource_ref", "client_nonce", "context_token"],
184
217
  ref_pattern: "^resource:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
185
218
  media_type_pattern: "^[a-z0-9][a-z0-9!#$&^_.+-]{0,63}/[a-z0-9][a-z0-9!#$&^_.+-]{0,63}$",
186
219
  filename_max_bytes: 512,
@@ -197,88 +230,6 @@ var client_wire_contract_default = {
197
230
  }
198
231
  };
199
232
 
200
- // packages/ceal-client-protocol/src/client-wire-contract.ts
201
- var CEAL_CLIENT_WIRE_CONTRACT = Object.freeze(client_wire_contract_default);
202
- var commandResolution = CEAL_CLIENT_WIRE_CONTRACT.command_resolution;
203
- var commandResolutionAuthority = commandResolution.authority;
204
- var commandResolutionSemanticOptions = commandResolution.semantic_options;
205
- var CEAL_GATEWAY_COMMAND_RESOLUTION_SCHEMA_VERSION = commandResolution.schema_version;
206
- var CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES = CEAL_CLIENT_WIRE_CONTRACT.encoded_json_document.max_bytes;
207
- var artifact = CEAL_CLIENT_WIRE_CONTRACT.artifact_descriptor;
208
- var request = CEAL_CLIENT_WIRE_CONTRACT.command_request;
209
- var response = CEAL_CLIENT_WIRE_CONTRACT.command_response;
210
- var CEAL_OPERATION_CONTRACT_BUNDLE_SCHEMA_VERSION = artifact.operation_contract_bundle_schema_version;
211
- var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_SCHEMA_VERSION = artifact.schema_version;
212
- var CEAL_VERIFIED_ARTIFACT_BINDING_SCHEMA_VERSION = artifact.binding_schema_version;
213
- var CEAL_VERIFIED_ARTIFACT_KIND = artifact.kind;
214
- var CEAL_VERIFIED_ARTIFACT_BINDING_KIND = artifact.binding_kind;
215
- var CEAL_VERIFIED_ARTIFACT_MEDIA_TYPE = artifact.media_type;
216
- var CEAL_VERIFIED_ARTIFACT_DIGEST_LENGTH = artifact.digest.length;
217
- var CEAL_VERIFIED_ARTIFACT_DIGEST_PATTERN = artifact.digest.pattern;
218
- var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_LENGTH = artifact.catalog_revision.length;
219
- var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_PATTERN = artifact.catalog_revision.pattern;
220
- var CEAL_VERIFIED_ARTIFACT_MAX_BYTES = CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES;
221
- var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_MAX_BYTES = artifact.envelope_max_bytes;
222
- var CEAL_COMMAND_REQUEST_SCHEMA_VERSION = request.schema_version;
223
- var CEAL_COMMAND_REQUEST_MAX_BYTES = request.absolute_transport_max_bytes;
224
- var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_MAX_BYTES = response.bare_document_max_bytes;
225
- var CEAL_COMMAND_RESPONSE_PROMPT_FIELD = response.prompt_field;
226
- var CEAL_COMMAND_RESPONSE_PROMPT_SCHEMA_VERSION = response.prompt_schema_version;
227
- var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_OPTIONAL_FIELDS = Object.freeze([...response.bare_document_optional_fields]);
228
- var CEAL_COMMAND_RESPONSE_PROMPT_DOCUMENT_FIELDS = Object.freeze([...response.bare_document_fields, response.prompt_field]);
229
- var CEAL_COMMAND_RESPONSE_FOCUSED_PROMPT_DOCUMENT_FIELDS = Object.freeze([...response.focused_prompt_document_fields]);
230
- var CEAL_COMMAND_RESPONSE_FOCUSED_PROMPT_CONTINUATION_DOCUMENT_FIELDS = Object.freeze([...response.focused_prompt_continuation_document_fields]);
231
- var CEAL_COMMAND_RESPONSE_DISCOVERY_COMMAND = response.discovery_command;
232
- var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_SCHEMA_VERSION = response.discovery_manifest_schema_version;
233
- var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_DOCUMENT_FIELDS = Object.freeze([...response.discovery_manifest_document_fields]);
234
-
235
- // packages/ceal-client-protocol/src/vocabulary.ts
236
- function vocabulary(words) {
237
- return Object.freeze(words);
238
- }
239
-
240
- // packages/ceal-client-protocol/src/gateway-response-types.ts
241
- var CEAL_OPERATION_READINESS_VALUES = vocabulary(["ready", "degraded", "unavailable", "unknown"]);
242
- var CEAL_TARGET_REQUIREMENTS = vocabulary(["required", "optional", "none"]);
243
- var CEAL_AUDIT_OUTCOMES = vocabulary(["succeeded", "denied", "failed"]);
244
- var CEAL_GATEWAY_PROVIDER_STEP_OUTCOMES = vocabulary(["completed", "rejected", "throttled", "failed"]);
245
- 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"]);
246
- var CEAL_GATEWAY_READ_REUSE_UNAVAILABLE_REASONS = vocabulary(["not_reported", "storage_unavailable", "coverage_incomplete", "freshness_refresh_required", "not_reusable", "bypassed"]);
247
- var CEAL_GATEWAY_DERIVED_STORE_FAILURE_CLASSES = vocabulary(["storage_unavailable", "decode_mismatch", "write_conflict", "quota", "store_error"]);
248
- var CEAL_POLICY_DECISIONS = vocabulary(["allowed", "denied", "not_evaluated"]);
249
- var CEAL_CONNECTOR_ROUTE_PHASES = vocabulary(["scope_observation", "target_selection", "route_resolution"]);
250
- var CEAL_CONNECTOR_ROUTE_CAUSES = vocabulary(["provider_throttled", "provider_unavailable", "binding_invalid", "scope_limit_exceeded"]);
251
- var CEAL_WRITE_SOURCE_KINDS = vocabulary(["authenticated_registered_client", "provider_authenticated_event"]);
252
- var CEAL_REFRESH_DELIVERIES = vocabulary(["initial", "recovery", "replay", "terminal_failure", "recovery_unavailable"]);
253
- var CEAL_CLIENT_OPERATIONS = vocabulary(["handshake", "discover", "call", "readback"]);
254
- var CEAL_WRITE_IDEMPOTENCY_POSTURES = vocabulary(["required", "optional", "not_required"]);
255
- var CEAL_WRITE_PROVIDER_READBACK_POSTURES = vocabulary(["required", "best_effort", "not_available"]);
256
- var CEAL_WRITE_ATTRIBUTIONS = vocabulary(["subject", "requester_event", "connector_integration"]);
257
- var CEAL_WRITE_RECEIPT_PROVIDER_STATES = vocabulary(["outcome_unknown", "verified", "not_dispatched"]);
258
- var CEAL_WRITE_RECEIPT_PROVIDER_READBACKS = vocabulary(["outcome_unknown", "verified", "not_applied"]);
259
- var CEAL_PROTOCOL_VERSION = "1.4.0";
260
- var CEAL_OPERATION_ACCESS_SCHEMA_VERSION = "ceal.operation_access.v1";
261
- var CEAL_GATEWAY_CONTINUATION_CURSOR_PHASES = vocabulary(["scan", "delivery", "terminal"]);
262
- var CEAL_GATEWAY_ACTIVE_CALL_PHASES = vocabulary([
263
- "audited_call_pending",
264
- "continuation_read_pending",
265
- "continuation_write_pending",
266
- "provider_pacing_pending",
267
- "corpus_replay_pending",
268
- "corpus_append_pending",
269
- "corpus_fence_pending",
270
- "corpus_content_pending",
271
- "corpus_transaction_pending"
272
- ]);
273
- var CEAL_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "phase", "schema_version"]);
274
- var CEAL_CLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["cause", ...CEAL_CONNECTOR_ROUTE_FAILURE_KEYS]);
275
- var CEAL_UNCLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "error_class", "phase", "schema_version"]);
276
- var CEAL_GATEWAY_ORGANIZATION_ROUTE_FACT_CALL_KEYS = Object.freeze([
277
- "connector_ref",
278
- "connector_revision",
279
- "policy_decision_revision"
280
- ]);
281
-
282
233
  // packages/ceal-client-protocol/src/operation-next-actions.ts
283
234
  var CEAL_REPEAT_REQUEST_NEXT_ACTION = "repeat_request";
284
235
  var CEAL_INSPECT_RECEIPT_NEXT_ACTION = "inspect_receipt";
@@ -345,7 +296,10 @@ var CALLER_RECOVERABLE_NEXT_ACTION_VALUES = [
345
296
  "follow_required_action_sequence",
346
297
  // The command needs caller-owned local materialization (for example an
347
298
  // @path file) before the Gateway can receive the argument.
348
- "materialize_local_argument"
299
+ "materialize_local_argument",
300
+ // A peer's derived schema identifier changed with its field set; install or
301
+ // upgrade the CLI and Host Relay to the Gateway's release before retrying.
302
+ "upgrade_ceal_client"
349
303
  ];
350
304
  var CEAL_CALLER_RECOVERABLE_NEXT_ACTIONS = Object.freeze(CALLER_RECOVERABLE_NEXT_ACTION_VALUES);
351
305
  var NON_REMEDY_NEXT_ACTION_VALUES = [
@@ -458,47 +412,53 @@ var CEAL_OPERATION_WITHHELD_NEXT_ACTIONS = Object.freeze({
458
412
  personal_authorization_required: "connect_personal_account"
459
413
  });
460
414
 
461
- // packages/ceal-client-protocol/src/operation-withheld-cause.ts
462
- var CEAL_OPERATION_WITHHELD_CAUSES = vocabulary(["provider_permission", "access_level", "personal_authorization_required"]);
415
+ // packages/ceal-client-protocol/src/vocabulary.ts
416
+ function vocabulary(words) {
417
+ return Object.freeze(words);
418
+ }
463
419
 
464
- // packages/ceal-client-protocol/src/operation-contract-types.ts
465
- var CEAL_JSON_SCHEMA_2020_12 = "https://json-schema.org/draft/2020-12/schema";
466
- var CEAL_OPERATION_ERROR_SCHEMA_VERSION = "ceal.operation_error.v1";
467
- var CEAL_ARTIFACT_STAGE_TERMINALS = vocabulary(["chunk_accepted", "artifact_ready", "idempotency_replayed"]);
468
- var CEAL_RETRY_AFTER_MAX_MS = 60 * 60 * 1e3;
469
- var CEAL_JSON_SCHEMA_TYPES = vocabulary(["object", "array", "string", "integer", "number", "boolean", "null"]);
470
- var OBSERVATION_POSTURES = vocabulary(["direct_readback", "approved_no_readback", "operator_mediated", "excluded"]);
471
- var EFFECT_DOMAINS = vocabulary(["none", "gateway_state", "provider", "external_world"]);
472
- var SAFETY_CLASSES = vocabulary(["routine", "sensitive", "high_impact"]);
473
- var IDEMPOTENCY_CLASSES = vocabulary(["idempotent", "conditionally_idempotent", "non_idempotent"]);
474
- var REVERSIBILITY_CLASSES = vocabulary(["none", "reversible", "compensatable", "irreversible"]);
475
- var COMPENSATION_OWNERS = vocabulary(["none", "connector", "operator"]);
476
- var CANCELLATION_BOUNDARIES = vocabulary(["before_admission", "before_handoff", "after_handoff_reconcile"]);
477
- var HANDOFF_CLASSES = vocabulary(["not_applicable", "submission", "effect"]);
478
- var AUTHORITATIVE_READBACK_CLASSES = vocabulary(["required", "unavailable"]);
479
- var PROVIDER_READBACK_CLASSES = vocabulary(["required", "not_available", "not_applicable"]);
480
- var RECONCILIATION_CLASSES = vocabulary(["none", "automatic", "operator"]);
481
- var NO_READBACK_APPROVAL_CLASSES = vocabulary(["not_applicable", "explicit", "operator"]);
482
- var REPLAY_PREREQUISITES = vocabulary(["never", "fresh_admission", "reconciled_non_application", "operator_approval"]);
483
- var UNKNOWN_OUTCOMES = vocabulary(["reconcile", "operator_action", "excluded"]);
484
- var RECOVERY_OWNERS = vocabulary(["gateway", "connector", "operator"]);
485
- var CEAL_OPERATION_ADMISSION_STATES = vocabulary(["proposed", "refused", "admitted"]);
486
- var CEAL_OPERATION_EXECUTION_STATES = vocabulary(["not_started", "started", "completed"]);
487
- var CEAL_OPERATION_PROVIDER_HANDOFF_STATES = vocabulary(["not_offered", "offered", "accepted", "rejected", "unknown"]);
488
- var CEAL_OPERATION_EFFECT_STATES = vocabulary(["none", "applied", "failed", "unknown", "compensated"]);
489
- var CEAL_OPERATION_OBSERVATION_STATES = vocabulary(["not_started", "not_required", "pending", "observed", "unavailable"]);
490
- var CEAL_OPERATION_RECOVERY_KINDS = vocabulary(["none", "reconcile", "compensate", "operator_action"]);
491
- var CEAL_OPERATION_RECOVERY_STATUSES = vocabulary(["not_started", "active", "completed"]);
492
- var CEAL_OPERATION_PROVIDER_READBACK_AVAILABILITY = vocabulary(["not_started", "not_applicable", "not_available", "pending", "available"]);
493
- var CEAL_OPERATION_OFFERED_HANDOFF_STATES = vocabulary(["accepted", "rejected", "unknown"]);
494
- var CEAL_OPERATION_RECEIPT_AUTHORITY_KEYS = Object.freeze(["instance_ref", "access_group_ref", "principal_ref", "execution_subject_ref", "authority_revision_ref"]);
495
- var CEAL_OPERATION_APPROVAL_DISPOSITIONS = vocabulary([
496
- "approval_not_required_by_policy",
497
- "interactive_pending",
498
- "interactive_granted",
499
- "interactive_denied",
500
- "interactive_expired",
501
- "interactive_unavailable"
420
+ // packages/ceal-client-protocol/src/gateway-response-types.ts
421
+ var CEAL_OPERATION_READINESS_VALUES = vocabulary(["ready", "degraded", "unavailable", "unknown"]);
422
+ var CEAL_TARGET_REQUIREMENTS = vocabulary(["required", "optional", "none"]);
423
+ var CEAL_AUDIT_OUTCOMES = vocabulary(["succeeded", "denied", "failed"]);
424
+ var CEAL_GATEWAY_PROVIDER_STEP_OUTCOMES = vocabulary(["completed", "rejected", "throttled", "failed"]);
425
+ 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"]);
426
+ var CEAL_GATEWAY_READ_REUSE_UNAVAILABLE_REASONS = vocabulary(["not_reported", "storage_unavailable", "coverage_incomplete", "freshness_refresh_required", "not_reusable", "bypassed"]);
427
+ var CEAL_GATEWAY_DERIVED_STORE_FAILURE_CLASSES = vocabulary(["storage_unavailable", "decode_mismatch", "write_conflict", "quota", "store_error"]);
428
+ var CEAL_POLICY_DECISIONS = vocabulary(["allowed", "denied", "not_evaluated"]);
429
+ var CEAL_CONNECTOR_ROUTE_PHASES = vocabulary(["scope_observation", "target_selection", "route_resolution"]);
430
+ var CEAL_CONNECTOR_ROUTE_CAUSES = vocabulary(["provider_throttled", "provider_unavailable", "binding_invalid", "scope_limit_exceeded"]);
431
+ var CEAL_WRITE_SOURCE_KINDS = vocabulary(["authenticated_registered_client", "provider_authenticated_event"]);
432
+ var CEAL_REFRESH_DELIVERIES = vocabulary(["initial", "recovery", "replay", "terminal_failure", "recovery_unavailable"]);
433
+ var CEAL_CLIENT_OPERATIONS = vocabulary(["handshake", "discover", "call", "readback"]);
434
+ var CEAL_WRITE_IDEMPOTENCY_POSTURES = vocabulary(["required", "optional", "not_required"]);
435
+ var CEAL_WRITE_PROVIDER_READBACK_POSTURES = vocabulary(["required", "best_effort", "not_available"]);
436
+ var CEAL_WRITE_ATTRIBUTIONS = vocabulary(["subject", "requester_event", "connector_integration"]);
437
+ var CEAL_WRITE_RECEIPT_PROVIDER_STATES = vocabulary(["outcome_unknown", "verified", "not_dispatched"]);
438
+ var CEAL_WRITE_RECEIPT_PROVIDER_READBACKS = vocabulary(["outcome_unknown", "verified", "not_applied"]);
439
+ var CEAL_PROTOCOL_VERSION = "1.4.0";
440
+ var CEAL_OPERATION_ACCESS_SCHEMA_VERSION = "ceal.operation_access.v1";
441
+ var CEAL_GATEWAY_CALL_RESULT_SCHEMA_VERSION = "ceal.gateway_call_result.v1";
442
+ var CEAL_GATEWAY_CONTINUATION_CURSOR_PHASES = vocabulary(["scan", "delivery", "terminal"]);
443
+ var CEAL_GATEWAY_ACTIVE_CALL_PHASES = vocabulary([
444
+ "audited_call_pending",
445
+ "continuation_read_pending",
446
+ "continuation_write_pending",
447
+ "provider_pacing_pending",
448
+ "corpus_replay_pending",
449
+ "corpus_append_pending",
450
+ "corpus_fence_pending",
451
+ "corpus_content_pending",
452
+ "corpus_transaction_pending"
453
+ ]);
454
+ var CEAL_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "phase", "schema_version"]);
455
+ var CEAL_CLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["cause", ...CEAL_CONNECTOR_ROUTE_FAILURE_KEYS]);
456
+ var CEAL_UNCLASSIFIED_CONNECTOR_ROUTE_FAILURE_KEYS = vocabulary(["connector_kind", "error_class", "phase", "schema_version"]);
457
+ var CEAL_GATEWAY_AUDIT_CALL_DETAIL_SCHEMA_VERSION = "ceal.gateway_audit_call_detail.v1";
458
+ var CEAL_GATEWAY_ORGANIZATION_ROUTE_FACT_CALL_KEYS = Object.freeze([
459
+ "connector_ref",
460
+ "connector_revision",
461
+ "policy_decision_revision"
502
462
  ]);
503
463
 
504
464
  // packages/ceal-client-protocol/src/gateway-validation-primitives.ts
@@ -540,10 +500,10 @@ function assertSafeJsonArray(value, options, depth, count) {
540
500
  if (value.length > CEAL_WIRE_SAFE_ARRAY_MAX_ITEMS) invalidByContext(options);
541
501
  for (const item of value) assertSafeJsonValue(item, options, depth + 1, count);
542
502
  }
543
- function assertSafeJsonRecord(record6, options, depth, count) {
544
- const entries = Object.entries(record6);
503
+ function assertSafeJsonRecord(record4, options, depth, count) {
504
+ const entries = Object.entries(record4);
545
505
  if (entries.length > CEAL_WIRE_SAFE_ARRAY_MAX_ITEMS) invalidByContext(options);
546
- const sourceUrlColumn = options.allowHttpsUrl ? compactSourceUrlColumn(record6.fields) : null;
506
+ const sourceUrlColumn = options.allowHttpsUrl ? compactSourceUrlColumn(record4.fields) : null;
547
507
  for (const [key, child] of entries) {
548
508
  if (key === "credential_material_included" && child !== false) invalidByContext(options);
549
509
  if (!isSafeNegativeMaterialAssertion(key, child)) assertSafeJsonKey(key, options);
@@ -712,51 +672,6 @@ function cut(text) {
712
672
  return text.length <= REFUSAL_VALUE_MAX_CHARS ? text : `${text.slice(0, REFUSAL_VALUE_MAX_CHARS)}...(+${text.length - REFUSAL_VALUE_MAX_CHARS} chars)`;
713
673
  }
714
674
 
715
- // packages/ceal-client-protocol/src/operation-access-class.ts
716
- var CEAL_OPERATION_ACCESS_CLASSES = vocabulary(["read", "write"]);
717
-
718
- // packages/ceal-client-protocol/src/domain-vocabulary.ts
719
- var CEAL_CONNECTOR_STATUSES = vocabulary(["active", "disabled", "disconnected"]);
720
- var CEAL_CONNECTOR_CUSTODIES = vocabulary(["organization", "personal"]);
721
- var CEAL_ACCESS_LEVELS = vocabulary(["none", "read", "read_write"]);
722
- var CEAL_PERSONAL_ACCESS_CEILINGS = vocabulary(["disabled", "read", "read_write"]);
723
- var CEAL_TARGET_REF_PATTERN = "^[^\\p{Cc}]+$";
724
- var TARGET_REF = new RegExp(CEAL_TARGET_REF_PATTERN, "u");
725
- var CEAL_TARGET_VISIBILITIES = vocabulary(["public", "private"]);
726
- var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "runtime"]);
727
- var CEAL_PERSISTED_PRINCIPAL_KINDS = vocabulary(["human", "service", "runtime"]);
728
- var CEAL_ORGANIZATION_ROLES = vocabulary(["member", "admin"]);
729
- var CEAL_CALENDAR_PERIODS = vocabulary(["day", "week", "month"]);
730
- var LABEL_EXPECTATION = `public-safe display text of at most ${CEAL_PUBLIC_DISPLAY_NAME_MAX_BYTES} bytes`;
731
-
732
- // packages/ceal-client-protocol/src/safe-json-budget.ts
733
- var SAFE_JSON_MIN_BYTES_PER_NODE = 4;
734
- function safeJsonNodeBudgetForBytes(maxBytes) {
735
- if (!Number.isSafeInteger(maxBytes) || maxBytes < SAFE_JSON_MIN_BYTES_PER_NODE) {
736
- throw new RangeError(`safe-JSON byte cap is invalid: ${refusalFact("maxBytes", maxBytes, `at least ${SAFE_JSON_MIN_BYTES_PER_NODE} bytes`)}`);
737
- }
738
- return Math.floor(maxBytes / SAFE_JSON_MIN_BYTES_PER_NODE);
739
- }
740
-
741
- // packages/ceal-client-protocol/src/protocol-bounds.ts
742
- var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES = 64 * 1024;
743
- var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_NODES = safeJsonNodeBudgetForBytes(CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES);
744
- var OPERATION_SCHEMA_LITERAL_MAX_BYTES = CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES / 4;
745
- var CEAL_OPERATION_JSON_SCHEMA_BUDGET = Object.freeze({
746
- max_depth: 8,
747
- max_properties: 96,
748
- max_string_length: OPERATION_SCHEMA_LITERAL_MAX_BYTES / 4,
749
- max_alternatives: 8,
750
- max_enum_values: 64,
751
- max_literal_bytes: OPERATION_SCHEMA_LITERAL_MAX_BYTES,
752
- /** @basis our_policy -- bounds explanatory schema prose carried in one catalog. */
753
- max_description_characters: 1024,
754
- /** @basis our_policy -- bounds regular-expression compilation input from one schema. */
755
- max_pattern_characters: 256,
756
- /** @basis our_policy -- bounds traversal work inside one already byte-bounded schema literal. */
757
- max_literal_nodes: 512
758
- });
759
-
760
675
  // packages/ceal-client-protocol/src/operation-validation-primitives.ts
761
676
  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;
762
677
  var CEAL_CALENDAR_DATE_PATTERN_SOURCE = "^\\d{4}-\\d{2}-\\d{2}$";
@@ -824,6 +739,317 @@ function requireIsoTimestamp(value, message) {
824
739
  if (!isCealIsoTimestamp(value)) throw new TypeError(message);
825
740
  }
826
741
 
742
+ // packages/ceal-client-protocol/src/failure-guidance-table.ts
743
+ var CEAL_FAILURE_REDACTION_CLASSES = vocabulary(["model_safe", "withheld"]);
744
+ var CEAL_GATEWAY_STARTING_FAILURE_CODE = "gateway_starting";
745
+ var CEAL_OWNER_BOOTSTRAP_ABSENT_FAILURE_CODE = "bootstrap_absent";
746
+ var CEAL_OWNER_BOOTSTRAP_UNAVAILABLE_FAILURE_CODE = "bootstrap_unavailable";
747
+ var LOCAL_COMMAND_INVALID = "The local command or argument is invalid.";
748
+ var LOCAL_TRANSPORT_INCOMPLETE = "The local Ceal transport could not complete; report this as a gap.";
749
+ var GATEWAY_TRANSPORT_UNAVAILABLE = "The Gateway transport is unavailable; ask an administrator to restore Gateway availability.";
750
+ var HOST_AUTHENTICATION_UNAVAILABLE = "The Host could not authenticate this invocation; report this as a gap.";
751
+ var SESSION_CONTEXT_UNAVAILABLE = "The local Gateway session context is unavailable.";
752
+ function guidance(message, next_action, http_status, retryable = false, redaction_class = "model_safe") {
753
+ return Object.freeze({ message, next_action, http_status, retryable, redaction_class });
754
+ }
755
+ var CEAL_FAILURE_GUIDANCE_TABLE = Object.freeze({
756
+ invalid_command: guidance(LOCAL_COMMAND_INVALID, "correct_argument", 400),
757
+ invalid_argument: guidance(LOCAL_COMMAND_INVALID, "correct_argument", 400),
758
+ command_not_found: guidance(LOCAL_COMMAND_INVALID, "correct_argument", 404),
759
+ 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),
760
+ local_route_unavailable: guidance("The requested local route is unavailable or its descriptor is invalid; report this as a gap.", "answer_with_gap", 404),
761
+ local_route_ambiguous: guidance("The requested local route is ambiguous; no local executable was selected. Report this as a gap.", "answer_with_gap", 400),
762
+ local_execution_unavailable: guidance("The selected local route executable is unavailable; report this as a gap.", "answer_with_gap", 500),
763
+ local_pickup_failed: guidance("The Host Request pickup failed; the Gateway command was not sent. Report this as a gap.", "answer_with_gap", 502),
764
+ invocation_authentication_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 401),
765
+ 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),
766
+ gateway_origin_required: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
767
+ access_token_required: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
768
+ context_pair_required: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
769
+ context_not_selected: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
770
+ context_store_invalid: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
771
+ context_store_conflict: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
772
+ refresh_invalid: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
773
+ refresh_expired: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
774
+ refresh_replayed: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
775
+ refresh_revoked: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
776
+ refresh_recovery_unavailable: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
777
+ refresh_custody_invalid: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
778
+ 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),
779
+ local_output_exists: guidance("The selected local output path already exists; nothing was written to it.", "choose_new_output_path", 400),
780
+ 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),
781
+ 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),
782
+ 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),
783
+ 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),
784
+ 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),
785
+ response_encoding_failed: guidance("The local response could not be encoded; report this as a gap.", "answer_with_gap", 500),
786
+ request_timeout: guidance(GATEWAY_TRANSPORT_UNAVAILABLE, "ask_admin", 504),
787
+ request_failed: guidance(GATEWAY_TRANSPORT_UNAVAILABLE, "ask_admin", 502),
788
+ gateway_unavailable: guidance(GATEWAY_TRANSPORT_UNAVAILABLE, "ask_admin", 503, true),
789
+ 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),
790
+ // `gateway_starting` is the one retryable envelope the serving gate answers on every route while starting
791
+ // (`cli/internal/client/gateway_startup_wait.go` re-drives it); the sentence is the client default that renders
792
+ // when the wait ends still unserved, moved from the `localFailureText` default branch.
793
+ [CEAL_GATEWAY_STARTING_FAILURE_CODE]: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 503, true),
794
+ gateway_internal_error: guidance("The Gateway failed without exposing internal state; report this as a gap.", "answer_with_gap", 500),
795
+ // The serving gate emits this code with no message; the sentence is the owner-normalized form of the
796
+ // `gateway_internal_error` guidance (decided with the operator 2026-10-13).
797
+ internal_error: guidance("The Gateway failed without exposing internal state.", "answer_with_gap", 500),
798
+ // Owned by the Gateway canonical adapter (`gateway/core/src/gateway-canonical-operation-http-adapter.ts`):
799
+ // the client variant of this sentence in `localFailureText` was retired when the client started reading this table.
800
+ request_body_too_large: guidance("The Gateway request body exceeds its bounded limit.", "reduce_request_size", 413),
801
+ // Restored verbatim from the retired `localFailureText` `unknown_outcome`
802
+ // case: the outcome is unknown, so a read may be retried while a write
803
+ // must receipt-check first. Collapsing both to "do not repeat the call"
804
+ // forbids the safe read retry the sentence exists to allow.
805
+ 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),
806
+ // Restored verbatim from the retired `localFailureText`
807
+ // `refresh_unknown_outcome` case: a refresh that ends unknown leaves the
808
+ // session context unavailable, so the caller repairs or adopts the
809
+ // session instead of reporting a transport gap.
810
+ refresh_unknown_outcome: guidance(SESSION_CONTEXT_UNAVAILABLE, "repair_or_adopt_session", 401),
811
+ request_cancelled: guidance("The caller cancelled the request before it completed.", "retry", 400, true),
812
+ 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),
813
+ denied: guidance("An administrator denied this device's activation request; ask an administrator whether to reconsider it.", "ask_admin", 403),
814
+ 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),
815
+ 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),
816
+ 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),
817
+ recovery_required: guidance("The Gateway could not safely complete the activation transaction automatically; ask an administrator to recover this transaction.", "ask_admin", 500),
818
+ 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),
819
+ // Activation commit codes rendered through the `localFailureText` default before phase 2b; `retryable` is moved from the
820
+ // emitting sites in `cli/internal/client/gateway_installation_activation.go` and `activation.go:483`.
821
+ activation_state_commit_failed: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500, true),
822
+ activation_response_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 502, true),
823
+ activation_rejected: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 502),
824
+ activation_failed: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
825
+ activation_binding: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
826
+ activation_crypto: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
827
+ activation_state_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
828
+ // Long-tail local codes rendered through the `localFailureText` default before phase 2b; sentences moved from that branch.
829
+ invalid_response: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
830
+ invalid_configuration: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
831
+ activation_state_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
832
+ activation_code_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
833
+ activation_code_file_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
834
+ activation_code_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 404),
835
+ bootstrap_file_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
836
+ bootstrap_not_active: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 409),
837
+ bootstrap_unauthorized: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 401),
838
+ // The Gateway owner-bootstrap store file was never created, so the Gateway
839
+ // holds no owner bootstrap to exchange (`gateway/src/host/owner-bootstrap-http-composition.ts`
840
+ // `absent`, stored at `gateway/src/shared/installation/owner-bootstrap-store.ts:162`);
841
+ // the sentence is the owner-normalized form of that store error. Non-retryable:
842
+ // repeating the exchange cannot create the store; only an administrator
843
+ // placing a bootstrap (fresh gateway setup) changes the outcome.
844
+ [CEAL_OWNER_BOOTSTRAP_ABSENT_FAILURE_CODE]: guidance("The Gateway holds no owner bootstrap to exchange; report this as a gap.", "answer_with_gap", 503),
845
+ // The Gateway owner-bootstrap store file exists but cannot be read, so the
846
+ // exchange cannot proceed (`gateway/src/host/owner-bootstrap-http-composition.ts`
847
+ // `unavailable`, stored at `gateway/src/shared/installation/owner-bootstrap-store.ts:163`);
848
+ // the sentence is the owner-normalized form of that store error. Retryable:
849
+ // unlike `absent`, a later exchange may succeed once the store is readable.
850
+ [CEAL_OWNER_BOOTSTRAP_UNAVAILABLE_FAILURE_CODE]: guidance("The Gateway owner bootstrap store is unavailable; retry the exchange.", "retry", 503, true),
851
+ context_selection_ambiguous: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
852
+ session_authority_unverified: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 401),
853
+ session_authority_mismatch: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 409),
854
+ local_credential_lost: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 401),
855
+ local_capability_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
856
+ host_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 503),
857
+ host_activation_failed: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 500),
858
+ doctor_unavailable: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 503),
859
+ admin_sign_in_invalid: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 502),
860
+ // `invalid_request` renders through the client default today (local encode/path faults); Gateway-answered
861
+ // envelopes carry the Gateway message verbatim and never read this sentence.
862
+ invalid_request: guidance(LOCAL_TRANSPORT_INCOMPLETE, "answer_with_gap", 400),
863
+ // Gateway canonical-route codes, sentences moved from their minting adapters; statuses moved from the
864
+ // `valid*Failure` decoder maps in `cli/internal/client/gateway_api.go` and `transport.go`.
865
+ invalid_json: guidance("The Gateway request body must be valid JSON.", "answer_with_gap", 400),
866
+ // Per-route variants name their route; the table holds the one stable guidance and the route's `allow`
867
+ // header stays the measured fact (decided with the operator 2026-10-13).
868
+ method_not_allowed: guidance("The HTTP method is not allowed for this Gateway route.", "answer_with_gap", 405),
869
+ request_target_too_large: guidance("The Gateway request target exceeds its bounded limit.", "reduce_request_size", 414),
870
+ route_not_found: guidance("The Gateway route is not present.", "answer_with_gap", 404),
871
+ artifact_unavailable: guidance("The Gateway artifact is unavailable.", "answer_with_gap", 503),
872
+ discovery_unavailable: guidance("The Gateway discovery manifest is unavailable.", "answer_with_gap", 503),
873
+ serialization_failed: guidance("The Gateway response could not be serialized.", "answer_with_gap", 500),
874
+ request_header_too_large: guidance("A consumed Gateway request header exceeds its bounded limit.", "answer_with_gap", 431),
875
+ request_work_context_missing: guidance("The Gateway command requires a Request work context.", "answer_with_gap", 400),
876
+ request_work_context_invalid: guidance("The Gateway Request work context encoding is invalid.", "answer_with_gap", 400),
877
+ request_work_context_refused: guidance("The Host-only discovery manifest takes no Request work context.", "answer_with_gap", 400),
878
+ // Moved from `gateway/src/operations/http-composition.ts` (`unauthorized`) and `command-runtime.ts:827`
879
+ // (`artifact_stale`); both travel with Gateway messages verbatim online, so these sentences are the
880
+ // owner fallback guidance. `unauthorized` re-authenticates through the session, hence its action.
881
+ unauthorized: guidance("Unauthorized.", "repair_or_adopt_session", 401),
882
+ artifact_stale: guidance("The requested contract artifact is not the current catalog artifact.", "answer_with_gap", 409),
883
+ // Host mint classifications. The CLI emits `invocation_authentication_unavailable` with the classification
884
+ // in its detail fact; rows without their own sentence share that code's moved sentence, while `next_action`
885
+ // is moved from `canonicalHostAuthenticationNextActions` (generated from `CEAL_HOST_AUTHENTICATION_POLICY`)
886
+ // and `retryable` from `hostAuthenticationFailureForCause` (`failure.nextAction == "retry"`). The three rows
887
+ // with their own sentences carry the retired policy remedies as Gateway-owned guidance. Statuses are the
888
+ // Gateway-owned mapping decided with the operator 2026-10-13. This covers every wire cause the maps at
889
+ // `host/invocation-authentication-command.ts:217-242` emit, through their policy classifications.
890
+ host_arguments_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 400),
891
+ host_configuration_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 500),
892
+ host_session_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 401),
893
+ // Moved from the retired `CEAL_HOST_AUTHENTICATION_POLICY` remedy: the
894
+ // missing exact-turn fact names the fresh Host turn, not a reinstall.
895
+ 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),
896
+ host_invocation_context_stale: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "retry", 401, true),
897
+ host_invocation_context_expired: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "retry", 401, true),
898
+ host_source_refused: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 403),
899
+ host_registration_credential_refused: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "ask_admin", 403),
900
+ host_mint_gateway_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "ask_admin", 503),
901
+ host_mint_gateway_rate_limited: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 429),
902
+ host_mint_gateway_refused: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 502),
903
+ host_mint_gateway_response_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 502),
904
+ // Moved from the retired `CEAL_HOST_AUTHENTICATION_POLICY` remedy: the
905
+ // producer-frame fault names the administrator repair step.
906
+ host_producer_frame_invalid: guidance("Ask an administrator to inspect and repair the producer frame, then start a fresh Host turn.", "ask_admin", 400),
907
+ // Moved from the retired `CEAL_HOST_AUTHENTICATION_POLICY` remedy: the
908
+ // sandboxed Relay socket names the run-alone retry.
909
+ 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),
910
+ host_relay_unavailable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 503),
911
+ host_mint_transient_retryable: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "retry", 503, true),
912
+ host_mint_failed: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 500),
913
+ host_relay_failed: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 502),
914
+ host_refusal_invalid: guidance(HOST_AUTHENTICATION_UNAVAILABLE, "answer_with_gap", 500)
915
+ });
916
+ var FAILURE_GUIDANCE_ENTRY_KEYS = Object.freeze(["message", "next_action", "http_status", "retryable", "redaction_class"]);
917
+
918
+ // packages/ceal-client-protocol/src/client-wire-contract.ts
919
+ var CEAL_CLIENT_WIRE_CONTRACT = Object.freeze(client_wire_contract_default);
920
+ var CEAL_CLIENT_DOCTOR_SCHEMA_VERSION = CEAL_CLIENT_WIRE_CONTRACT.doctor.schema_version;
921
+ var clientCommands = CEAL_CLIENT_WIRE_CONTRACT.client_commands;
922
+ var CEAL_CLIENT_DOCTOR_COMMAND = Object.freeze([...clientCommands.doctor]);
923
+ var CEAL_CLIENT_VERSION_COMMAND = Object.freeze([...clientCommands.version]);
924
+ var CEAL_CLIENT_ACCESS_GROUP_LIST_COMMAND = Object.freeze([...clientCommands.access_group_list]);
925
+ function deriveCealClientWireSchemaVersion(schema) {
926
+ 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))) {
927
+ throw new TypeError("Client wire schema name or field set is invalid.");
928
+ }
929
+ const fields = schema.fields.filter((field) => field !== "schema_version").sort();
930
+ const hash8 = createHash("sha256").update(cealCanonicalJson(fields), "utf8").digest("hex").slice(0, 8);
931
+ return `${schema.schema_name}.v1.${hash8}`;
932
+ }
933
+ var commandResolution = CEAL_CLIENT_WIRE_CONTRACT.command_resolution;
934
+ var commandResolutionAuthority = commandResolution.authority;
935
+ var commandResolutionSemanticOptions = commandResolution.semantic_options;
936
+ var CEAL_GATEWAY_COMMAND_RESOLUTION_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(commandResolution);
937
+ var CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES = CEAL_CLIENT_WIRE_CONTRACT.encoded_json_document.max_bytes;
938
+ var artifact = CEAL_CLIENT_WIRE_CONTRACT.artifact_descriptor;
939
+ var request = CEAL_CLIENT_WIRE_CONTRACT.command_request;
940
+ var response = CEAL_CLIENT_WIRE_CONTRACT.command_response;
941
+ var CEAL_OPERATION_CONTRACT_BUNDLE_SCHEMA_VERSION = deriveCealClientWireSchemaVersion({ schema_name: artifact.operation_contract_bundle_schema_name, fields: artifact.operation_contract_bundle_fields });
942
+ var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(artifact);
943
+ var CEAL_VERIFIED_ARTIFACT_BINDING_SCHEMA_VERSION = deriveCealClientWireSchemaVersion({ schema_name: artifact.binding_schema_name, fields: artifact.binding_fields });
944
+ var CEAL_VERIFIED_ARTIFACT_KIND = artifact.kind;
945
+ var CEAL_VERIFIED_ARTIFACT_BINDING_KIND = artifact.binding_kind;
946
+ var CEAL_VERIFIED_ARTIFACT_MEDIA_TYPE = artifact.media_type;
947
+ var CEAL_VERIFIED_ARTIFACT_DIGEST_LENGTH = artifact.digest.length;
948
+ var CEAL_VERIFIED_ARTIFACT_DIGEST_PATTERN = artifact.digest.pattern;
949
+ var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_LENGTH = artifact.catalog_revision.length;
950
+ var CEAL_VERIFIED_ARTIFACT_CATALOG_REVISION_PATTERN = artifact.catalog_revision.pattern;
951
+ var CEAL_VERIFIED_ARTIFACT_MAX_BYTES = CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES;
952
+ var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_MAX_BYTES = artifact.envelope_max_bytes;
953
+ var CEAL_COMMAND_REQUEST_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(request);
954
+ var CEAL_COMMAND_REQUEST_MAX_BYTES = request.absolute_transport_max_bytes;
955
+ var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_MAX_BYTES = response.bare_document_max_bytes;
956
+ var CEAL_COMMAND_RESPONSE_BARE_DOCUMENT_OPTIONAL_FIELDS = Object.freeze([...response.bare_document_optional_fields]);
957
+ var CEAL_COMMAND_RESPONSE_DISCOVERY_COMMAND = response.discovery_command;
958
+ var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_SCHEMA_VERSION = deriveCealClientWireSchemaVersion({ schema_name: response.discovery_manifest_schema_name, fields: response.discovery_manifest_fields });
959
+ var CEAL_COMMAND_RESPONSE_SCHEMA_VERSION = deriveCealClientWireSchemaVersion(response);
960
+ var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_DOCUMENT_FIELDS = Object.freeze([...response.discovery_manifest_document_fields]);
961
+ 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 });
962
+ var CEAL_RESOURCE_STREAM_REQUEST_FIELDS = Object.freeze([...CEAL_CLIENT_WIRE_CONTRACT.resource_stream.request_fields]);
963
+
964
+ // packages/ceal-client-protocol/src/operation-withheld-cause.ts
965
+ var CEAL_OPERATION_WITHHELD_CAUSES = vocabulary(["provider_permission", "access_level", "personal_authorization_required"]);
966
+
967
+ // packages/ceal-client-protocol/src/operation-contract-types.ts
968
+ var CEAL_JSON_SCHEMA_2020_12 = "https://json-schema.org/draft/2020-12/schema";
969
+ var CEAL_OPERATION_DESCRIPTOR_SCHEMA_VERSION = "ceal.operation_descriptor.v1";
970
+ var CEAL_OPERATION_ERROR_SCHEMA_VERSION = "ceal.operation_error.v1";
971
+ var CEAL_ARTIFACT_STAGE_TERMINALS = vocabulary(["chunk_accepted", "artifact_ready", "idempotency_replayed"]);
972
+ var CEAL_RETRY_AFTER_MAX_MS = 60 * 60 * 1e3;
973
+ var CEAL_JSON_SCHEMA_TYPES = vocabulary(["object", "array", "string", "integer", "number", "boolean", "null"]);
974
+ var OBSERVATION_POSTURES = vocabulary(["direct_readback", "approved_no_readback", "operator_mediated", "excluded"]);
975
+ var EFFECT_DOMAINS = vocabulary(["none", "gateway_state", "provider", "external_world"]);
976
+ var SAFETY_CLASSES = vocabulary(["routine", "sensitive", "high_impact"]);
977
+ var IDEMPOTENCY_CLASSES = vocabulary(["idempotent", "conditionally_idempotent", "non_idempotent"]);
978
+ var REVERSIBILITY_CLASSES = vocabulary(["none", "reversible", "compensatable", "irreversible"]);
979
+ var COMPENSATION_OWNERS = vocabulary(["none", "connector", "operator"]);
980
+ var CANCELLATION_BOUNDARIES = vocabulary(["before_admission", "before_handoff", "after_handoff_reconcile"]);
981
+ var HANDOFF_CLASSES = vocabulary(["not_applicable", "submission", "effect"]);
982
+ var AUTHORITATIVE_READBACK_CLASSES = vocabulary(["required", "unavailable"]);
983
+ var PROVIDER_READBACK_CLASSES = vocabulary(["required", "not_available", "not_applicable"]);
984
+ var RECONCILIATION_CLASSES = vocabulary(["none", "automatic", "operator"]);
985
+ var NO_READBACK_APPROVAL_CLASSES = vocabulary(["not_applicable", "explicit", "operator"]);
986
+ var REPLAY_PREREQUISITES = vocabulary(["never", "fresh_admission", "reconciled_non_application", "operator_approval"]);
987
+ var UNKNOWN_OUTCOMES = vocabulary(["reconcile", "operator_action", "excluded"]);
988
+ var RECOVERY_OWNERS = vocabulary(["gateway", "connector", "operator"]);
989
+ var CEAL_OPERATION_ADMISSION_STATES = vocabulary(["proposed", "refused", "admitted"]);
990
+ var CEAL_OPERATION_EXECUTION_STATES = vocabulary(["not_started", "started", "completed"]);
991
+ var CEAL_OPERATION_PROVIDER_HANDOFF_STATES = vocabulary(["not_offered", "offered", "accepted", "rejected", "unknown"]);
992
+ var CEAL_OPERATION_EFFECT_STATES = vocabulary(["none", "applied", "failed", "unknown", "compensated"]);
993
+ var CEAL_OPERATION_OBSERVATION_STATES = vocabulary(["not_started", "not_required", "pending", "observed", "unavailable"]);
994
+ var CEAL_OPERATION_RECOVERY_KINDS = vocabulary(["none", "reconcile", "compensate", "operator_action"]);
995
+ var CEAL_OPERATION_RECOVERY_STATUSES = vocabulary(["not_started", "active", "completed"]);
996
+ var CEAL_OPERATION_PROVIDER_READBACK_AVAILABILITY = vocabulary(["not_started", "not_applicable", "not_available", "pending", "available"]);
997
+ var CEAL_OPERATION_OFFERED_HANDOFF_STATES = vocabulary(["accepted", "rejected", "unknown"]);
998
+ var CEAL_OPERATION_RECEIPT_AUTHORITY_KEYS = Object.freeze(["instance_ref", "access_group_ref", "principal_ref", "execution_subject_ref", "authority_revision_ref"]);
999
+ var CEAL_OPERATION_APPROVAL_DISPOSITIONS = vocabulary([
1000
+ "approval_not_required_by_policy",
1001
+ "interactive_pending",
1002
+ "interactive_granted",
1003
+ "interactive_denied",
1004
+ "interactive_expired",
1005
+ "interactive_unavailable"
1006
+ ]);
1007
+
1008
+ // packages/ceal-client-protocol/src/operation-access-class.ts
1009
+ var CEAL_OPERATION_ACCESS_CLASSES = vocabulary(["read", "write"]);
1010
+
1011
+ // packages/ceal-client-protocol/src/domain-vocabulary.ts
1012
+ var CEAL_CONNECTOR_STATUSES = vocabulary(["active", "disabled", "disconnected"]);
1013
+ var CEAL_CONNECTOR_CUSTODIES = vocabulary(["organization", "personal"]);
1014
+ var CEAL_ACCESS_LEVELS = vocabulary(["none", "read", "read_write"]);
1015
+ var CEAL_PERSONAL_ACCESS_CEILINGS = vocabulary(["disabled", "read", "read_write"]);
1016
+ var CEAL_TARGET_REF_PATTERN = "^[^\\p{Cc}]+$";
1017
+ var TARGET_REF = new RegExp(CEAL_TARGET_REF_PATTERN, "u");
1018
+ var CEAL_TARGET_VISIBILITIES = vocabulary(["public", "private"]);
1019
+ var CEAL_PRINCIPAL_KINDS = vocabulary(["human", "runtime"]);
1020
+ var CEAL_PERSISTED_PRINCIPAL_KINDS = vocabulary(["human", "service", "runtime"]);
1021
+ var CEAL_ORGANIZATION_ROLES = vocabulary(["member", "admin"]);
1022
+ var CEAL_CALENDAR_PERIODS = vocabulary(["day", "week", "month"]);
1023
+ var LABEL_EXPECTATION = `public-safe display text of at most ${CEAL_PUBLIC_DISPLAY_NAME_MAX_BYTES} bytes`;
1024
+
1025
+ // packages/ceal-client-protocol/src/safe-json-budget.ts
1026
+ var SAFE_JSON_MIN_BYTES_PER_NODE = 4;
1027
+ function safeJsonNodeBudgetForBytes(maxBytes) {
1028
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < SAFE_JSON_MIN_BYTES_PER_NODE) {
1029
+ throw new RangeError(`safe-JSON byte cap is invalid: ${refusalFact("maxBytes", maxBytes, `at least ${SAFE_JSON_MIN_BYTES_PER_NODE} bytes`)}`);
1030
+ }
1031
+ return Math.floor(maxBytes / SAFE_JSON_MIN_BYTES_PER_NODE);
1032
+ }
1033
+
1034
+ // packages/ceal-client-protocol/src/protocol-bounds.ts
1035
+ var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES = 64 * 1024;
1036
+ var CEAL_PROTOCOL_RESPONSE_VALUE_MAX_NODES = safeJsonNodeBudgetForBytes(CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES);
1037
+ var OPERATION_SCHEMA_LITERAL_MAX_BYTES = CEAL_PROTOCOL_RESPONSE_VALUE_MAX_BYTES / 4;
1038
+ var CEAL_OPERATION_JSON_SCHEMA_BUDGET = Object.freeze({
1039
+ max_depth: 8,
1040
+ max_properties: 96,
1041
+ max_string_length: OPERATION_SCHEMA_LITERAL_MAX_BYTES / 4,
1042
+ max_alternatives: 8,
1043
+ max_enum_values: 64,
1044
+ max_literal_bytes: OPERATION_SCHEMA_LITERAL_MAX_BYTES,
1045
+ /** @basis our_policy -- bounds explanatory schema prose carried in one catalog. */
1046
+ max_description_characters: 1024,
1047
+ /** @basis our_policy -- bounds regular-expression compilation input from one schema. */
1048
+ max_pattern_characters: 256,
1049
+ /** @basis our_policy -- bounds traversal work inside one already byte-bounded schema literal. */
1050
+ max_literal_nodes: 512
1051
+ });
1052
+
827
1053
  // packages/ceal-client-protocol/src/operation-json-schema.ts
828
1054
  var JSON_DESCRIPTION_CONTROL_CHARACTER = new RegExp("\\p{Cc}", "u");
829
1055
 
@@ -865,12 +1091,12 @@ var CEAL_SCHEMA_LITERALS = [
865
1091
  "ceal.enrollment_result.v1",
866
1092
  "ceal.first_activation_causal_join.v1",
867
1093
  "ceal.gateway_announcement_policy.v1",
868
- "ceal.gateway_audit_call_detail.v1",
1094
+ CEAL_GATEWAY_AUDIT_CALL_DETAIL_SCHEMA_VERSION,
869
1095
  "ceal.gateway_audit_event.v1",
870
1096
  "ceal.gateway_audit_readback.v1",
871
1097
  "ceal.gateway_authorization_snapshot.v1",
872
1098
  "ceal.gateway_cache_origin.v1",
873
- "ceal.gateway_call_result.v1",
1099
+ CEAL_GATEWAY_CALL_RESULT_SCHEMA_VERSION,
874
1100
  CEAL_GATEWAY_COMMAND_RESOLUTION_SCHEMA_VERSION,
875
1101
  "ceal.gateway_connector_route_failure.v1",
876
1102
  "ceal.gateway_discovery.v1",
@@ -895,7 +1121,7 @@ var CEAL_SCHEMA_LITERALS = [
895
1121
  "ceal.host_observation.v1",
896
1122
  "ceal.host_registration_request.v1",
897
1123
  "ceal.host_registration_result.v1",
898
- "ceal.operation_descriptor.v1",
1124
+ CEAL_OPERATION_DESCRIPTOR_SCHEMA_VERSION,
899
1125
  "ceal.operation_error.v1",
900
1126
  "ceal.operation_failure.v1",
901
1127
  "ceal.operation_failure.compact.v1",
@@ -1000,10 +1226,20 @@ var CEAL_VERIFIED_ARTIFACT_DESCRIPTOR_JSON_SCHEMA = Object.freeze({
1000
1226
  var CEAL_OPAQUE_HANDLE_KINDS = vocabulary(["target", "message", "thread", "artifact", "document", "block", "object"]);
1001
1227
  var CEAL_OPAQUE_REF_KINDS = vocabulary([...CEAL_OPAQUE_HANDLE_KINDS, "result", "operation-receipt", "reply-receipt", "field", "notion-data-source"]);
1002
1228
 
1229
+ // packages/ceal-client-protocol/src/client-environment-generated.ts
1230
+ var CEAL_CLIENT_CONTEXT_OVERRIDE_ENVIRONMENTS = Object.freeze(["CEAL_GATEWAY_ORIGIN", "CEAL_GATEWAY_TRANSPORT_ORIGIN", "CEAL_ACCESS_TOKEN", "CEAL_CONTEXT_STORE_PATH"]);
1231
+
1232
+ // packages/ceal-client-protocol/src/client-process-contract.ts
1233
+ var CEAL_CLIENT_VERSION_SCHEMA_SUFFIX = ".version.v1";
1234
+ var CEAL_CLIENT_VERSION_SCHEMA_VERSION = `ceal${CEAL_CLIENT_VERSION_SCHEMA_SUFFIX}`;
1235
+
1003
1236
  // packages/ceal-client-protocol/src/bare-document.ts
1004
1237
  var REQUIRED_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_fields;
1005
1238
  var OPTIONAL_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_optional_fields;
1006
1239
 
1240
+ // packages/ceal-client-protocol/src/actor-kind.ts
1241
+ var CEAL_ACTOR_KINDS = vocabulary(["human", "bot", "app", "unknown"]);
1242
+
1007
1243
  // packages/ceal-client-protocol/src/resource-row.ts
1008
1244
  var CEAL_RESOURCE_ROW_TITLE_MATCH_DISPOSITIONS = vocabulary(["exact", "not_exact", "unknown"]);
1009
1245
  var CEAL_RESOURCE_SEARCH_ORDERS = vocabulary(["relevance", "modified", "viewed", "created"]);
@@ -1018,6 +1254,8 @@ var CEAL_ACTIVITY_TIMESTAMP_BASES = vocabulary([
1018
1254
  "resource_created",
1019
1255
  "review_submitted",
1020
1256
  "comment_created",
1257
+ "reaction_created",
1258
+ "ceal_write_completed",
1021
1259
  "event_start",
1022
1260
  "all_day_start",
1023
1261
  "file_created",
@@ -1052,6 +1290,10 @@ var CEAL_ACTIVITY_EVENT_KINDS = vocabulary([
1052
1290
  "github.review.submitted",
1053
1291
  "github.issue_comment.created",
1054
1292
  "github.review_comment.created",
1293
+ "github.reaction.created",
1294
+ "github.reaction.deleted",
1295
+ "github.pull_request.review_requested",
1296
+ "github.pull_request.review_request_removed",
1055
1297
  "calendar.organized",
1056
1298
  "calendar.accepted_invite",
1057
1299
  "calendar.recorded",
@@ -1073,6 +1315,10 @@ var CEAL_ACTIVITY_EVENT_TIMESTAMP_BASIS_MAP = Object.freeze({
1073
1315
  "github.pull_request.opened": "resource_created",
1074
1316
  "github.issue.edited": "resource_modified",
1075
1317
  "github.pull_request.edited": "resource_modified",
1318
+ "github.reaction.created": "reaction_created",
1319
+ "github.reaction.deleted": "ceal_write_completed",
1320
+ "github.pull_request.review_requested": "ceal_write_completed",
1321
+ "github.pull_request.review_request_removed": "ceal_write_completed",
1076
1322
  "calendar.organized": "resource_created",
1077
1323
  "drive.file.created": "file_created",
1078
1324
  "drive.file.modified": "file_modified_by_me",
@@ -1096,6 +1342,7 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
1096
1342
  source_url: { required: false, timestamp: false },
1097
1343
  source: { required: true, timestamp: false },
1098
1344
  owner: { required: false, timestamp: false },
1345
+ actor_kind: { required: false, timestamp: false },
1099
1346
  counterparts: { required: false, timestamp: false },
1100
1347
  attachments: { required: false, timestamp: false },
1101
1348
  created_at: { required: false, timestamp: true },
@@ -1103,9 +1350,18 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
1103
1350
  modified_at: { required: false, timestamp: true },
1104
1351
  modified_at_local: { required: false, timestamp: true },
1105
1352
  viewed_by_me_at: { required: false, timestamp: true },
1353
+ modified_by_me_at: { required: false, timestamp: true },
1106
1354
  own_ceal_write: { required: false, timestamp: false },
1107
1355
  owned_by_me: { required: false, timestamp: false },
1356
+ mime_type: { required: false, timestamp: false },
1357
+ size_bytes: { required: false, timestamp: false },
1358
+ version: { required: false, timestamp: false },
1359
+ trashed: { required: false, timestamp: false },
1108
1360
  visibility: { required: false, timestamp: false },
1361
+ archived: { required: false, timestamp: false },
1362
+ tag: { required: false, timestamp: false },
1363
+ labels: { required: false, timestamp: false },
1364
+ labels_incomplete: { required: false, timestamp: false },
1109
1365
  start: { required: false, timestamp: false },
1110
1366
  end: { required: false, timestamp: false },
1111
1367
  status: { required: false, timestamp: false },
@@ -1178,18 +1434,29 @@ var CEAL_REFRESH_FAILURE_CODES = vocabulary([
1178
1434
  // The serving gate answers this while the Gateway is still starting and the
1179
1435
  // refresh was never attempted; the same attempt stays retryable. It is never
1180
1436
  // stored by the session owner, only answered at the serving edge.
1181
- "gateway_starting"
1437
+ CEAL_GATEWAY_STARTING_FAILURE_CODE
1438
+ ]);
1439
+ var CEAL_CLIENT_REVOKE_FAILURE_CODES = vocabulary([
1440
+ "refresh_invalid",
1441
+ "refresh_expired",
1442
+ "refresh_replayed",
1443
+ "refresh_revoked"
1182
1444
  ]);
1445
+ var CEAL_PERSONAL_CLIENT_ACCESS_TOKEN_PREFIX = "ceal_personal_";
1446
+ var CEAL_PERSONAL_CLIENT_REFRESH_TOKEN_PREFIX = "ceal_refresh_";
1447
+ var PERSONAL_CLIENT_TOKEN_SUFFIX_PATTERN = "[A-Za-z0-9_-]{43}";
1448
+ var CEAL_PERSONAL_CLIENT_ACCESS_TOKEN = new RegExp(`^${CEAL_PERSONAL_CLIENT_ACCESS_TOKEN_PREFIX}${PERSONAL_CLIENT_TOKEN_SUFFIX_PATTERN}$`, "u");
1449
+ var CEAL_PERSONAL_CLIENT_REFRESH_TOKEN = new RegExp(`^${CEAL_PERSONAL_CLIENT_REFRESH_TOKEN_PREFIX}${PERSONAL_CLIENT_TOKEN_SUFFIX_PATTERN}$`, "u");
1183
1450
 
1184
1451
  // packages/ceal-client-protocol/src/request-validation-primitives.ts
1185
- import { createHash } from "node:crypto";
1452
+ import { createHash as createHash2 } from "node:crypto";
1186
1453
  var DIGEST = /^[a-f0-9]{64}$/u;
1187
1454
  var CEAL_SHA256_DIGEST_PATTERN = DIGEST.source;
1188
1455
  function isCealDigest(value) {
1189
1456
  return typeof value === "string" && DIGEST.test(value);
1190
1457
  }
1191
1458
  function cealRequestSha256(value) {
1192
- return createHash("sha256").update(cealCanonicalJson(value), "utf8").digest("hex");
1459
+ return createHash2("sha256").update(cealCanonicalJson(value), "utf8").digest("hex");
1193
1460
  }
1194
1461
  function operationRecord(value, message) {
1195
1462
  requireOperationRecord(value, message);
@@ -1205,40 +1472,71 @@ function requirePrefixedRef(value, prefix, message) {
1205
1472
  // packages/ceal-client-protocol/src/request-authority.ts
1206
1473
  var CEAL_REQUEST_SCHEMA_VERSION = "ceal.request.v1";
1207
1474
  var CEAL_REQUEST_ARTIFACT_OWNERS = vocabulary(["gateway", "host", "connector"]);
1208
- var CEAL_REQUEST_HOST_BINDING_KEYS = vocabulary(["prompt_ref", "host_session_ref", "host_turn_ref", "host_source_ref"]);
1475
+ var CEAL_HOST_INVOCATION_IDENTITY_KEYS = vocabulary(["host_session_ref", "host_turn_ref", "host_source_ref"]);
1209
1476
  var CEAL_REQUEST_ARTIFACT_RETENTION_CLASSES = vocabulary(["request", "source", "result"]);
1210
1477
  var CANCELLATION_STATES = vocabulary(["requested", "cancelled"]);
1211
- var CEAL_REQUEST_KEYS = Object.freeze(["schema_version", "ceal_request_ref", "created_at", "authority", "prompt_ref", "host_session_ref", "host_turn_ref", "host_source_ref"]);
1478
+ var CEAL_REQUEST_KEYS = Object.freeze(["schema_version", "ceal_request_ref", "created_at", "authority"]);
1479
+ var CEAL_REQUEST_COMMON_PROPERTIES = Object.freeze({
1480
+ schema_version: { type: "string", const: CEAL_REQUEST_SCHEMA_VERSION, maxLength: 32 },
1481
+ ceal_request_ref: { type: "string", minLength: 1, maxLength: 128, pattern: "^ceal-request:[A-Za-z0-9][A-Za-z0-9._:-]{0,114}$" },
1482
+ created_at: { type: "string", minLength: 1, maxLength: 64 },
1483
+ authority: {
1484
+ type: "object",
1485
+ maxProperties: 5,
1486
+ additionalProperties: false,
1487
+ properties: {
1488
+ instance_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1489
+ access_group_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1490
+ principal_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1491
+ execution_subject_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1492
+ authority_revision_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source }
1493
+ },
1494
+ required: ["instance_ref", "access_group_ref", "principal_ref", "execution_subject_ref", "authority_revision_ref"]
1495
+ }
1496
+ });
1497
+ var CEAL_REQUEST_HOST_PROPERTIES = Object.freeze({
1498
+ prompt_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1499
+ host_session_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1500
+ host_turn_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1501
+ host_input_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1502
+ host_source_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source }
1503
+ });
1504
+ var CEAL_REQUEST_CALLER_PROPERTIES = Object.freeze({
1505
+ 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." },
1506
+ 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." }
1507
+ });
1508
+ var CEAL_REQUEST_OPTIONAL_PROPERTIES = Object.freeze({
1509
+ requester_timezone: { type: "string", minLength: 1, maxLength: 128, description: "IANA timezone observed for the requester on this Request." },
1510
+ 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." }
1511
+ });
1512
+ var CEAL_REQUEST_HOST_REQUIRED_KEYS = Object.freeze([...CEAL_REQUEST_KEYS, ...CEAL_HOST_INVOCATION_IDENTITY_KEYS, "prompt_ref"]);
1513
+ var CEAL_REQUEST_CALLER_REQUIRED_KEYS = Object.freeze([...CEAL_REQUEST_KEYS, "caller_call_group_ref", "caller_call_input_sha256"]);
1212
1514
  var CEAL_REQUEST_JSON_SCHEMA = {
1213
1515
  type: "object",
1214
- maxProperties: 11,
1516
+ 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.",
1517
+ maxProperties: 13,
1215
1518
  additionalProperties: false,
1216
1519
  properties: {
1217
- schema_version: { type: "string", const: CEAL_REQUEST_SCHEMA_VERSION, maxLength: 32 },
1218
- ceal_request_ref: { type: "string", minLength: 1, maxLength: 128, pattern: "^ceal-request:[A-Za-z0-9][A-Za-z0-9._:-]{0,114}$" },
1219
- created_at: { type: "string", minLength: 1, maxLength: 64 },
1220
- authority: {
1520
+ ...CEAL_REQUEST_COMMON_PROPERTIES,
1521
+ ...CEAL_REQUEST_HOST_PROPERTIES,
1522
+ ...CEAL_REQUEST_CALLER_PROPERTIES,
1523
+ ...CEAL_REQUEST_OPTIONAL_PROPERTIES
1524
+ },
1525
+ required: CEAL_REQUEST_KEYS,
1526
+ anyOf: [
1527
+ {
1221
1528
  type: "object",
1222
- maxProperties: 5,
1223
- additionalProperties: false,
1224
- properties: {
1225
- instance_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1226
- access_group_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1227
- principal_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1228
- execution_subject_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1229
- authority_revision_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source }
1230
- },
1231
- required: ["instance_ref", "access_group_ref", "principal_ref", "execution_subject_ref", "authority_revision_ref"]
1529
+ required: CEAL_REQUEST_HOST_REQUIRED_KEYS,
1530
+ properties: { ...CEAL_REQUEST_COMMON_PROPERTIES, ...CEAL_REQUEST_HOST_PROPERTIES, ...CEAL_REQUEST_OPTIONAL_PROPERTIES },
1531
+ additionalProperties: false
1232
1532
  },
1233
- prompt_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1234
- host_session_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1235
- host_turn_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1236
- host_input_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1237
- host_source_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source },
1238
- requester_timezone: { type: "string", minLength: 1, maxLength: 128, description: "IANA timezone observed for the requester on this Request." },
1239
- device_ref: { type: "string", minLength: 1, maxLength: 128, pattern: SAFE_REF.source, description: "Device that submitted this Request through its client session; absent for Host turns and older records." }
1240
- },
1241
- required: CEAL_REQUEST_KEYS
1533
+ {
1534
+ type: "object",
1535
+ required: CEAL_REQUEST_CALLER_REQUIRED_KEYS,
1536
+ properties: { ...CEAL_REQUEST_COMMON_PROPERTIES, ...CEAL_REQUEST_CALLER_PROPERTIES, ...CEAL_REQUEST_OPTIONAL_PROPERTIES },
1537
+ additionalProperties: false
1538
+ }
1539
+ ]
1242
1540
  };
1243
1541
 
1244
1542
  // packages/ceal-client-protocol/src/request-operands.ts
@@ -1349,10 +1647,6 @@ var CEAL_REQUEST_RUN_BATCH_JSON_SCHEMA = {
1349
1647
  };
1350
1648
 
1351
1649
  // packages/ceal-client-protocol/src/host-observation.ts
1352
- var CEAL_HOST_INPUT_KINDS = vocabulary(["human", "host_automatic"]);
1353
- function isCealHostInputKind(value) {
1354
- return typeof value === "string" && CEAL_HOST_INPUT_KINDS.some((kind) => kind === value);
1355
- }
1356
1650
  var CEAL_HOST_OBSERVATION_REF_PREFIX = "host-observation:";
1357
1651
  var HOST_OBSERVATION_PRODUCER = /^[a-z][a-z0-9]{0,31}$/u;
1358
1652
  var HOST_OBSERVATION_DIGEST = /^[a-f0-9]{8,64}$/u;
@@ -1398,14 +1692,14 @@ function safeCount(value) {
1398
1692
  return Number.isSafeInteger(value) && Number(value) >= 0;
1399
1693
  }
1400
1694
  function validateCealHostObservationDraft(value) {
1401
- const record6 = operationRecord(value, "Ceal Host observation draft is invalid.");
1402
- requirePrefixedRef(record6.observation_ref, CEAL_HOST_OBSERVATION_REF_PREFIX, "Ceal Host observation reference is invalid.");
1403
- requireClosedValue(record6.observation_kind, CEAL_HOST_OBSERVATION_KINDS, "Ceal Host observation kind is invalid.");
1404
- requireIsoTimestamp(record6.observed_at, "Ceal Host observation time is invalid.");
1405
- assertHostObservationValue(record6.value, record6.observation_kind);
1695
+ const record4 = operationRecord(value, "Ceal Host observation draft is invalid.");
1696
+ requirePrefixedRef(record4.observation_ref, CEAL_HOST_OBSERVATION_REF_PREFIX, "Ceal Host observation reference is invalid.");
1697
+ requireClosedValue(record4.observation_kind, CEAL_HOST_OBSERVATION_KINDS, "Ceal Host observation kind is invalid.");
1698
+ requireIsoTimestamp(record4.observed_at, "Ceal Host observation time is invalid.");
1699
+ assertHostObservationValue(record4.value, record4.observation_kind);
1406
1700
  }
1407
1701
  var CEAL_REQUEST_MAX_OBSERVATION_VALUE_BYTES = 64 * 1024;
1408
- var CEAL_HOST_OBSERVATION_KINDS = vocabulary(["prompt", "session", "turn", "model", "token", "cost", "timing"]);
1702
+ var CEAL_HOST_OBSERVATION_KINDS = vocabulary(["prompt", "session", "turn", "model", "token", "timing"]);
1409
1703
  function isCealHostPromptObservationValue(value) {
1410
1704
  return isOperationRecord(value) && Object.keys(value).length === 1 && isCealDigest(value.body_sha256);
1411
1705
  }
@@ -1528,16 +1822,41 @@ var CEAL_REQUEST_GRAPH_NODE_PREPARE_READINESS_JSON_SCHEMA = {
1528
1822
  };
1529
1823
 
1530
1824
  // packages/ceal-client-protocol/src/host-invocation-authentication.ts
1531
- var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_SCHEMA = "ceal.host_invocation_authentication_result.v1";
1532
- var CEAL_HOST_NONCE_ENCODED_CHARACTERS = 43;
1533
- var CEAL_HOST_CLIENT_NONCE_PATTERN = new RegExp(`^[A-Za-z0-9_-]{${CEAL_HOST_NONCE_ENCODED_CHARACTERS}}$`, "u");
1534
- var CEAL_HOST_CONTEXT_TOKEN_PATTERN = new RegExp(`^ceal_context_[A-Za-z0-9_-]{${CEAL_HOST_NONCE_ENCODED_CHARACTERS}}$`, "u");
1535
- var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_MAX_BYTES = new TextEncoder().encode(`${JSON.stringify({
1536
- schema_version: CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_SCHEMA,
1537
- context_token: `ceal_context_${"A".repeat(CEAL_HOST_NONCE_ENCODED_CHARACTERS)}`,
1538
- expires_at: (/* @__PURE__ */ new Date("+010000-01-01T00:00:00.000Z")).toISOString()
1539
- })}
1825
+ var hostInvocationAuthenticationContract = CEAL_CLIENT_WIRE_CONTRACT.host_invocation_authentication;
1826
+ var CEAL_HOST_INVOCATION_AUTHENTICATION_REQUEST_SCHEMA = deriveCealClientWireSchemaVersion({ schema_name: hostInvocationAuthenticationContract.request_schema_name, fields: hostInvocationAuthenticationContract.request_fields });
1827
+ var CEAL_HOST_INVOCATION_AUTHENTICATION_REQUEST_FIELDS = Object.freeze([...hostInvocationAuthenticationContract.request_fields]);
1828
+ var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_SCHEMA = deriveCealClientWireSchemaVersion({ schema_name: hostInvocationAuthenticationContract.result_schema_name, fields: hostInvocationAuthenticationContract.result_fields });
1829
+ var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_FIELDS = Object.freeze([...hostInvocationAuthenticationContract.result_fields]);
1830
+ var CEAL_HOST_AUTHENTICATION_BUDGET_PATTERN_SOURCE = "^(?:0|[1-9][0-9]{0,8})$";
1831
+ var CEAL_HOST_AUTHENTICATION_BUDGET_PATTERN = new RegExp(CEAL_HOST_AUTHENTICATION_BUDGET_PATTERN_SOURCE, "u");
1832
+ var CEAL_HOST_INVOCATION_CONTEXT_REQUEST_MAX_BYTES = 16 * 1024;
1833
+ var CEAL_HOST_INVOCATION_HUMAN_MESSAGE_WINDOW_MAX_BYTES = 2 * 1024;
1834
+ var CEAL_HOST_CONTEXT_TOKEN_PREFIX = "ceal_context_";
1835
+ function deriveCealHostInvocationNonceContract(contract = CEAL_CLIENT_WIRE_CONTRACT) {
1836
+ const clientNonce = contract.host_invocation_authentication.client_nonce;
1837
+ return {
1838
+ encoded_characters: clientNonce.encoded_characters,
1839
+ client_nonce_pattern_source: `^[${clientNonce.character_class_source}]{${clientNonce.encoded_characters}}$`,
1840
+ context_token_pattern_source: `^${CEAL_HOST_CONTEXT_TOKEN_PREFIX}[${clientNonce.character_class_source}]{${clientNonce.encoded_characters}}$`
1841
+ };
1842
+ }
1843
+ var hostNonceContract = deriveCealHostInvocationNonceContract();
1844
+ var CEAL_HOST_NONCE_ENCODED_CHARACTERS = hostNonceContract.encoded_characters;
1845
+ var CEAL_HOST_CLIENT_NONCE_PATTERN = new RegExp(hostNonceContract.client_nonce_pattern_source, "u");
1846
+ var CEAL_HOST_CONTEXT_TOKEN_PATTERN = new RegExp(hostNonceContract.context_token_pattern_source, "u");
1847
+ var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_MAX_BYTES = deriveCealHostInvocationAuthenticationResultMaxBytes();
1848
+ function deriveCealHostInvocationAuthenticationResultMaxBytes(contract = CEAL_CLIENT_WIRE_CONTRACT) {
1849
+ const hostAuthentication = contract.host_invocation_authentication;
1850
+ const values = {
1851
+ schema_version: deriveCealClientWireSchemaVersion({ schema_name: hostAuthentication.result_schema_name, fields: hostAuthentication.result_fields }),
1852
+ context_token: `${CEAL_HOST_CONTEXT_TOKEN_PREFIX}${"A".repeat(hostAuthentication.client_nonce.encoded_characters)}`,
1853
+ expires_at: (/* @__PURE__ */ new Date("+010000-01-01T00:00:00.000Z")).toISOString()
1854
+ };
1855
+ const fields = hostAuthentication.result_fields;
1856
+ if (fields.some((field) => !Object.hasOwn(values, field))) throw new TypeError("Host invocation result fields have no bounded sample value.");
1857
+ return new TextEncoder().encode(`${JSON.stringify(Object.fromEntries(fields.map((field) => [field, values[field]])))}
1540
1858
  `).byteLength;
1859
+ }
1541
1860
  var CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS = vocabulary([
1542
1861
  "request_authority_unavailable",
1543
1862
  "request_missing",
@@ -1561,6 +1880,7 @@ var CEAL_HOST_AUTHENTICATION_CAUSES = vocabulary([
1561
1880
  "host_credential_refused",
1562
1881
  "gateway_unavailable",
1563
1882
  "gateway_rate_limited",
1883
+ "contract_mismatch",
1564
1884
  "gateway_refused",
1565
1885
  "gateway_response_invalid",
1566
1886
  "producer_frame_invalid",
@@ -1575,19 +1895,23 @@ var CEAL_HOST_AUTHENTICATION_POLICY = Object.freeze({
1575
1895
  invalid_arguments: Object.freeze({ classification: "host_arguments_invalid", next_action: "answer_with_gap" }),
1576
1896
  options_invalid: Object.freeze({ classification: "host_configuration_invalid", next_action: "answer_with_gap" }),
1577
1897
  session_unavailable: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
1578
- context_unavailable: Object.freeze({ classification: "host_context_unavailable", next_action: "refresh_host_invocation_context", remedy: "the Host has no invocation context from the current transcript turn; send a new message to start a fresh Host turn, then retry this command on that turn (run `ceal-host install` only if the Host hook registration is missing or drifted)" }),
1579
- context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
1580
- no_binding: Object.freeze({ classification: "host_binding_unavailable", next_action: "refresh_host_invocation_context" }),
1581
- context_stale: Object.freeze({ classification: "host_invocation_context_stale", next_action: "refresh_host_invocation_context" }),
1582
- context_expired: Object.freeze({ classification: "host_invocation_context_expired", next_action: "refresh_host_invocation_context" }),
1898
+ context_unavailable: Object.freeze({ classification: "host_context_unavailable", next_action: "retry" }),
1899
+ context_replaced: Object.freeze({ classification: "host_invocation_context_stale", next_action: "retry" }),
1900
+ // `no_binding` now means the producer has no real current Host session. The
1901
+ // Host Relay never uses it for an uncreated Gateway Request; there is no
1902
+ // useful retry until the consumer is running inside a Host session.
1903
+ no_binding: Object.freeze({ classification: "host_session_unavailable", next_action: "answer_with_gap" }),
1904
+ context_stale: Object.freeze({ classification: "host_invocation_context_stale", next_action: "retry" }),
1905
+ context_expired: Object.freeze({ classification: "host_invocation_context_expired", next_action: "retry" }),
1583
1906
  host_source_refused: Object.freeze({ classification: "host_source_refused", next_action: "answer_with_gap" }),
1584
1907
  host_credential_refused: Object.freeze({ classification: "host_registration_credential_refused", next_action: "ask_admin" }),
1585
1908
  gateway_unavailable: Object.freeze({ classification: "host_mint_gateway_unavailable", next_action: "ask_admin" }),
1586
1909
  gateway_rate_limited: Object.freeze({ classification: "host_mint_gateway_rate_limited", next_action: "answer_with_gap" }),
1910
+ contract_mismatch: Object.freeze({ classification: "contract_mismatch", next_action: "upgrade_ceal_client" }),
1587
1911
  gateway_refused: Object.freeze({ classification: "host_mint_gateway_refused", next_action: "answer_with_gap" }),
1588
1912
  gateway_response_invalid: Object.freeze({ classification: "host_mint_gateway_response_invalid", next_action: "answer_with_gap" }),
1589
- producer_frame_invalid: Object.freeze({ classification: "host_producer_frame_invalid", next_action: "ask_admin", remedy: "ask an administrator to inspect and repair the producer frame, then start a fresh Host turn" }),
1590
- relay_permission_denied: Object.freeze({ classification: "host_relay_permission_denied", next_action: "rerun_outside_agent_sandbox", remedy: "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" }),
1913
+ producer_frame_invalid: Object.freeze({ classification: "host_producer_frame_invalid", next_action: "ask_admin" }),
1914
+ relay_permission_denied: Object.freeze({ classification: "host_relay_permission_denied", next_action: "rerun_outside_agent_sandbox" }),
1591
1915
  relay_unavailable: Object.freeze({ classification: "host_relay_unavailable", next_action: "answer_with_gap" }),
1592
1916
  transient_retryable: Object.freeze({ classification: "host_mint_transient_retryable", next_action: "retry" }),
1593
1917
  mint_failed: Object.freeze({ classification: "host_mint_failed", next_action: "answer_with_gap" }),
@@ -1602,10 +1926,6 @@ var CEAL_HOST_SERVICE_DIAGNOSTIC_EVENTS = vocabulary([
1602
1926
  "hook_invoked",
1603
1927
  "registration_written",
1604
1928
  "hook_failed",
1605
- "watcher_turn_observed",
1606
- "current_prompt_registered",
1607
- "current_prompt_registration_failed",
1608
- "current_prompt_capacity_exhausted",
1609
1929
  "activation_recorded",
1610
1930
  "activation_failed",
1611
1931
  "watcher_scan_failed",
@@ -1631,6 +1951,9 @@ var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "items_path", "row_co
1631
1951
  // packages/ceal-client-protocol/src/session-start-affordance.ts
1632
1952
  var CEAL_SESSION_ENTRY_INVOCATION = "ceal";
1633
1953
 
1954
+ // packages/ceal-client-protocol/src/discovery-manifest.ts
1955
+ var DISCOVERY_MANIFEST_KEYS = CEAL_CLIENT_WIRE_CONTRACT.command_response.discovery_manifest_fields;
1956
+
1634
1957
  // packages/ceal-client-protocol/src/gateway-cache-origin-validation.ts
1635
1958
  var CEAL_MAX_CACHE_ORIGIN_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
1636
1959
 
@@ -1689,6 +2012,7 @@ var ADMIN_UNAVAILABLE_REASONS = vocabulary([
1689
2012
  "legacy",
1690
2013
  "invalid_observation",
1691
2014
  "redacted",
2015
+ "organization_setting_disabled",
1692
2016
  "owner_unavailable",
1693
2017
  "unit_unknown",
1694
2018
  "retention_gap",
@@ -1718,15 +2042,13 @@ var ADMIN_APPROVAL_DISPOSITIONS = CEAL_OPERATION_APPROVAL_DISPOSITIONS;
1718
2042
  var ADMIN_APPROVAL_POSTURES = vocabulary([...ADMIN_APPROVAL_DISPOSITIONS, "not_applicable"]);
1719
2043
  var ADMIN_LINEAGE_NODE_KINDS = vocabulary(["source_read", "synthesis_preview", "approval", "effect", "readback"]);
1720
2044
 
1721
- // packages/ceal-client-protocol/src/actor-kind.ts
1722
- var CEAL_ACTOR_KINDS = vocabulary(["human", "bot", "app", "unknown"]);
1723
-
1724
2045
  // packages/ceal-client-protocol/src/device-enrollment.ts
1725
2046
  var CEAL_DEVICE_ENROLLMENT_POLL_FAILURE_CODES = Object.freeze(["unsupported_feature", "recovery_required", "expired", "denied"]);
1726
2047
  var POLL_RESULT_KEYS = Object.freeze(["retry_after_ms", "schema_version", "status"]);
1727
2048
 
1728
2049
  // packages/ceal-client-protocol/src/personal-client-session.ts
1729
2050
  var REFRESH_FAILURE_CODES = new Set(CEAL_REFRESH_FAILURE_CODES);
2051
+ var REVOKE_FAILURE_CODES = new Set(CEAL_CLIENT_REVOKE_FAILURE_CODES);
1730
2052
 
1731
2053
  // packages/ceal-client-protocol/src/index.ts
1732
2054
  var MAX_REQUEST_BYTES = 32 * 1024;
@@ -1760,6 +2082,60 @@ function decodeClaudeHookInput(event, raw) {
1760
2082
  const model = optionalText(value.model, 256);
1761
2083
  return { event, ...common, hook_event_name: "SessionStart", source: value.source, ...model === void 0 ? {} : { model } };
1762
2084
  }
2085
+ function decodeClaudeQueuedPromptRow(row, sessionId) {
2086
+ if (!record(row)) return null;
2087
+ if (row.type === "user") return decodeArrayTextPrompt(row, sessionId);
2088
+ if (row.type === "attachment") return decodeQueuedCommandPrompt(row, sessionId);
2089
+ if (row.type === "queue-operation") return decodeQueueEnqueuePrompt(row, sessionId);
2090
+ return null;
2091
+ }
2092
+ function decodeArrayTextPrompt(row, sessionId) {
2093
+ if (!record(row.message) || !Array.isArray(row.message.content)) return null;
2094
+ if (!sessionMatches(row, sessionId)) return null;
2095
+ if (row.isMeta === true && (!record(row.origin) || typeof row.origin.kind !== "string")) return null;
2096
+ const texts = arrayTextBlocks(row.message.content);
2097
+ if (texts === null) return null;
2098
+ const identity = queuedRowIdentity(row);
2099
+ if (identity === null) return null;
2100
+ return { turnId: identity.uuid, timestamp: identity.timestamp, content: texts.join("") };
2101
+ }
2102
+ function arrayTextBlocks(content) {
2103
+ const texts = [];
2104
+ for (const block of content) {
2105
+ if (!record(block) || block.type !== "text" || typeof block.text !== "string") return null;
2106
+ texts.push(block.text);
2107
+ }
2108
+ return texts.length === 0 ? null : texts;
2109
+ }
2110
+ function decodeQueuedCommandPrompt(row, sessionId) {
2111
+ if (!record(row.attachment) || row.attachment.type !== "queued_command" || row.attachment.commandMode !== "prompt") return null;
2112
+ if (typeof row.attachment.prompt !== "string" || row.attachment.prompt.length === 0) return null;
2113
+ if (!sessionMatches(row, sessionId)) return null;
2114
+ const identity = queuedRowIdentity(row);
2115
+ if (identity === null) return null;
2116
+ return { turnId: identity.uuid, timestamp: identity.timestamp, content: row.attachment.prompt };
2117
+ }
2118
+ function decodeQueueEnqueuePrompt(row, sessionId) {
2119
+ if (row.operation !== "enqueue" || typeof row.content !== "string" || row.content.length === 0) return null;
2120
+ if (!sessionMatches(row, sessionId)) return null;
2121
+ if (typeof row.timestamp !== "string" || !Number.isFinite(Date.parse(row.timestamp))) return null;
2122
+ return { turnId: queueEnqueueTurnId(sessionId, row.timestamp, row.content), timestamp: row.timestamp, content: row.content };
2123
+ }
2124
+ function sessionMatches(row, sessionId) {
2125
+ const hasSessionId = Object.hasOwn(row, "sessionId");
2126
+ const hasSnakeSessionId = Object.hasOwn(row, "session_id");
2127
+ return (hasSessionId || hasSnakeSessionId) && (!hasSessionId || row.sessionId === sessionId) && (!hasSnakeSessionId || row.session_id === sessionId);
2128
+ }
2129
+ function queuedRowIdentity(row) {
2130
+ if (typeof row.uuid !== "string" || row.uuid.length === 0 || row.uuid.length > 256) return null;
2131
+ if (typeof row.timestamp !== "string" || !Number.isFinite(Date.parse(row.timestamp))) return null;
2132
+ return { uuid: row.uuid, timestamp: row.timestamp };
2133
+ }
2134
+ function queueEnqueueTurnId(sessionId, timestamp4, content) {
2135
+ return `queue-enqueue:${createHash3("sha256").update(`${sessionId}
2136
+ ${timestamp4}
2137
+ ${content}`, "utf8").digest("hex").slice(0, 32)}`;
2138
+ }
1763
2139
  function claudeLocalSessionRef(sessionId) {
1764
2140
  return `claude-session:${digest(sessionId)}`;
1765
2141
  }
@@ -1769,9 +2145,6 @@ function claudeLocalTurnRef(sessionId, turnId) {
1769
2145
  function claudeLocalInputRef(sessionId, turnId) {
1770
2146
  return `claude-input:${cealRequestSha256({ session_id: sessionId, prompt_id: turnId })}`;
1771
2147
  }
1772
- function claudeWorkspaceRef(cwd) {
1773
- return `workspace:${digest(cwd)}`;
1774
- }
1775
2148
  function isSessionSource(value) {
1776
2149
  return typeof value === "string" && SESSION_SOURCES.some((source) => source === value);
1777
2150
  }
@@ -1792,7 +2165,7 @@ function optionalText(value, max) {
1792
2165
  return requiredText(value, max, "Claude optional hook field is invalid.");
1793
2166
  }
1794
2167
  function digest(value) {
1795
- return createHash2("sha256").update(value, "utf8").digest("hex");
2168
+ return createHash3("sha256").update(value, "utf8").digest("hex");
1796
2169
  }
1797
2170
  function record(value) {
1798
2171
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -1802,17 +2175,17 @@ function invalid2(message) {
1802
2175
  }
1803
2176
 
1804
2177
  // host/claude/src/claude-outbox.ts
1805
- import { mkdir as mkdir3, readdir, readFile as readFile2, unlink as unlink2 } from "node:fs/promises";
1806
- import { isAbsolute, join as join3 } from "node:path";
2178
+ import { mkdir as mkdir2, readdir, readFile, unlink as unlink2 } from "node:fs/promises";
2179
+ import { isAbsolute, join as join2 } from "node:path";
1807
2180
 
1808
2181
  // host/invocation-context.ts
1809
- var HOST_INVOCATION_IDENTITY_KEYS = ["host_session_ref", "host_turn_ref", "host_source_ref"];
2182
+ var HOST_INVOCATION_TURN_SELECTION_SCHEMA = "ceal.host_invocation_turn_selection.v1";
1810
2183
  var HOST_INVOCATION_CONTEXT_REQUIRED_KEYS = ["schema_version", "identity", "prompt_ref", "expires_at"];
1811
2184
  function invocationContextKey(identity) {
1812
2185
  return cealRequestSha256({ host_source_ref: identity.host_source_ref, host_session_ref: identity.host_session_ref });
1813
2186
  }
1814
2187
  function hasHostInvocationIdentityKeys(value) {
1815
- return exactKeys(value, HOST_INVOCATION_IDENTITY_KEYS);
2188
+ return exactKeys(value, CEAL_HOST_INVOCATION_IDENTITY_KEYS);
1816
2189
  }
1817
2190
  function hasHostInvocationContextKeys(value, optional = []) {
1818
2191
  return requiredKeys(value, HOST_INVOCATION_CONTEXT_REQUIRED_KEYS, optional);
@@ -1838,10 +2211,7 @@ function invocationContextFromTurn(identity, promptRef, expiresAt, descriptor) {
1838
2211
  return Object.freeze(context);
1839
2212
  }
1840
2213
  function isHostInvocationTurnSelection(value) {
1841
- return isRecord(value) && Object.keys(value).length === 5 && value.schema_version === "ceal.host_invocation_turn_selection.v1" && ["host_source_ref", "host_session_ref", "prompt_ref", "turn_generation_ref"].every((key) => safeRef(value[key]));
1842
- }
1843
- function invocationTurnSelectionMatches(context, selection) {
1844
- return selection === null || context !== null && selection.host_source_ref === context.identity.host_source_ref && selection.host_session_ref === context.identity.host_session_ref && selection.prompt_ref === context.prompt_ref && selection.turn_generation_ref === context.turn_generation_ref;
2214
+ return isRecord(value) && Object.keys(value).length === 5 && value.schema_version === HOST_INVOCATION_TURN_SELECTION_SCHEMA && ["host_source_ref", "host_session_ref", "prompt_ref", "turn_generation_ref"].every((key) => safeRef(value[key]));
1845
2215
  }
1846
2216
  function exactKeys(value, keys) {
1847
2217
  const actual = Object.keys(value);
@@ -1860,16 +2230,9 @@ function safeRef(value) {
1860
2230
  }
1861
2231
 
1862
2232
  // host/relay/src/producer-frame.ts
1863
- var HOST_PRODUCER_FRAME_SCHEMA = "ceal.host_producer_frame.v1";
1864
- var HOST_PRODUCER_MAX_JSON_BYTES = 262144;
1865
2233
  var HOST_PRODUCER_FRAME_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
1866
- var SAFE_REF2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
1867
- var PRODUCERS = /* @__PURE__ */ new Set(["codex", "claude"]);
1868
- var HOST_PRODUCER_FRAME_KINDS = ["session_started", "turn_started", "user_input", "turn_terminal", "producer_status"];
2234
+ var HOST_PRODUCER_FRAME_KINDS = ["session_started", "turn_started", "turn_terminal", "producer_status"];
1869
2235
  var FRAME_KINDS = new Set(HOST_PRODUCER_FRAME_KINDS);
1870
- function isHostProducerFrameKind(value) {
1871
- return typeof value === "string" && FRAME_KINDS.has(value);
1872
- }
1873
2236
  var HOST_PRODUCER_FRAME_ERROR_CODES = [
1874
2237
  "invalid_frame_encoding",
1875
2238
  "invalid_frame_length",
@@ -1879,485 +2242,127 @@ var HOST_PRODUCER_FRAME_ERROR_CODES = [
1879
2242
  "invalid_producer_identity",
1880
2243
  "frame_too_large"
1881
2244
  ];
1882
- var HostProducerFrameError = class extends Error {
1883
- code;
1884
- constructor(code2, message = code2) {
1885
- super(message);
1886
- this.code = code2;
1887
- }
1888
- };
1889
- function encodeHostProducerFrame(frame) {
1890
- validateFrame(frame);
1891
- const json = Buffer.from(JSON.stringify(frame), "utf8");
1892
- if (json.byteLength > HOST_PRODUCER_MAX_JSON_BYTES) throw new HostProducerFrameError("frame_too_large", `frame_too_large: ${refusalFact("frame_bytes", json.byteLength, `at most ${HOST_PRODUCER_MAX_JSON_BYTES}`)}`);
1893
- const result = Buffer.allocUnsafe(json.byteLength + HOST_PRODUCER_FRAME_LENGTH_BYTES);
1894
- result.writeUInt32BE(json.byteLength, 0);
1895
- json.copy(result, HOST_PRODUCER_FRAME_LENGTH_BYTES);
1896
- return result;
1897
- }
1898
- function decodeHostProducerFrame(input) {
1899
- if (input.byteLength < HOST_PRODUCER_FRAME_LENGTH_BYTES) throw new HostProducerFrameError("frame_truncated", `frame_truncated: ${refusalFact("frame_bytes", input.byteLength, `at least ${HOST_PRODUCER_FRAME_LENGTH_BYTES}`)}`);
1900
- const length = input.readUInt32BE(0);
1901
- if (length > HOST_PRODUCER_MAX_JSON_BYTES) throw new HostProducerFrameError("frame_too_large", `frame_too_large: ${refusalFact("declared_bytes", length, `at most ${HOST_PRODUCER_MAX_JSON_BYTES}`)}`);
1902
- if (length === 0) throw new HostProducerFrameError("invalid_frame_length");
1903
- if (input.byteLength !== length + HOST_PRODUCER_FRAME_LENGTH_BYTES) throw new HostProducerFrameError(input.byteLength < length + HOST_PRODUCER_FRAME_LENGTH_BYTES ? "frame_truncated" : "invalid_frame_length");
1904
- const bytes = input.subarray(HOST_PRODUCER_FRAME_LENGTH_BYTES);
1905
- const text = new TextDecoder("utf-8", { fatal: true });
1906
- let value;
1907
- try {
1908
- value = JSON.parse(text.decode(bytes));
1909
- } catch (error) {
1910
- if (error instanceof TypeError) throw new HostProducerFrameError("invalid_frame_encoding");
1911
- throw new HostProducerFrameError("frame_truncated");
1912
- }
1913
- validateFrame(value);
1914
- return value;
1915
- }
1916
- function isHostSafeRef(value) {
1917
- return safeRef2(value);
1918
- }
1919
- function validateFrame(value) {
1920
- if (!record2(value)) throw new HostProducerFrameError("invalid_frame_shape");
1921
- 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)}`);
1922
- if (!exactKeys2(value, ["schema_version", "producer_id", "producer_installation_ref", "producer_epoch", "sequence", "frame_kind", "occurred_at", "payload"])) throw new HostProducerFrameError("invalid_frame_shape");
1923
- if (!PRODUCERS.has(String(value.producer_id)) || !safeRef2(value.producer_installation_ref)) throw new HostProducerFrameError("invalid_producer_identity");
1924
- if (!positive(value.producer_epoch) || !positive(value.sequence) || !hostProducerFrameKind(value.frame_kind) || !timestamp(value.occurred_at) || !record2(value.payload)) throw new HostProducerFrameError("invalid_frame_shape");
1925
- validatePayload(value.frame_kind, value.payload);
1926
- }
1927
- function validatePayload(kind, value) {
1928
- const valid = kind === "session_started" ? validSession(value) : kind === "turn_started" ? validTurn(value) : kind === "user_input" ? validInput(value) : kind === "turn_terminal" ? validTerminal(value) : validStatus(value);
1929
- if (!valid) throw new HostProducerFrameError("invalid_frame_shape");
1930
- }
1931
- function validSession(value) {
1932
- return exactOptional(value, ["local_session_ref", "working_directory_ref"], ["model", "host_version", "observations"]) && safeRef2(value.local_session_ref) && safeRef2(value.working_directory_ref) && optionalText2(value.model, 256) && optionalText2(value.host_version, 256) && validObservations(value.observations);
1933
- }
1934
- function validTurn(value) {
1935
- return exactOptional(value, ["local_session_ref", "local_turn_ref"], ["model", "observations"]) && safeRef2(value.local_session_ref) && safeRef2(value.local_turn_ref) && optionalText2(value.model, 256) && validObservations(value.observations);
1936
- }
1937
- function validInput(value) {
1938
- return exactOptional(value, ["local_session_ref", "local_turn_ref", "local_input_ref", "input_kind", "origin_input", "observations"], ["local_turn_aliases", "context_expires_at"]) && safeRef2(value.local_session_ref) && safeRef2(value.local_turn_ref) && safeRef2(value.local_input_ref) && validInputObservations(value.observations) && isCealHostInputKind(value.input_kind) && typeof value.origin_input === "boolean" && validInputOptionals(value);
1939
- }
1940
- function validInputObservations(value) {
1941
- return validObservations(value) && Array.isArray(value) && value.filter((item) => record2(item) && item.observation_kind === "prompt").length === 1;
1942
- }
1943
- function validInputOptionals(value) {
1944
- return optionalRefs(value.local_turn_aliases) && (value.context_expires_at === void 0 || timestamp(value.context_expires_at));
1945
- }
1946
- function validTerminal(value) {
1947
- return exactOptional(value, ["local_session_ref", "local_turn_ref", "terminal_state"], ["abort_reason", "duration_ms", "time_to_first_token_ms", "usage", "observations"]) && safeRef2(value.local_session_ref) && safeRef2(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);
1948
- }
1949
- function validTerminalDisposition(state, reason) {
1950
- return (state === "completed" || state === "aborted") && (reason === void 0 || state === "aborted" && safeRef2(reason));
1951
- }
1952
- function validStatus(value) {
1953
- return exactOptional(value, ["status", "reason_code"], ["detail"]) && ["ready", "degraded", "stopping"].includes(String(value.status)) && safeRef2(value.reason_code) && optionalText2(value.detail, 4096);
1954
- }
1955
- function optionalText2(value, maxLength) {
1956
- return value === void 0 || typeof value === "string" && value.length > 0 && value.length <= maxLength;
1957
- }
1958
- function optionalNonNegative(value) {
1959
- return value === void 0 || typeof value === "number" && Number.isFinite(value) && value >= 0;
1960
- }
1961
- function optionalRefs(value) {
1962
- return value === void 0 || Array.isArray(value) && value.length <= 16 && value.every(safeRef2);
1963
- }
1964
- function validObservations(value) {
1965
- if (value === void 0) return true;
1966
- if (!Array.isArray(value) || value.length > 32) return false;
1967
- return value.every((item) => {
1968
- try {
1969
- validateCealHostObservationDraft(item);
1970
- return true;
1971
- } catch {
1972
- return false;
1973
- }
1974
- });
1975
- }
1976
- function jsonValue(value, depth = 0) {
1977
- if (depth > 16) return false;
1978
- if (value === null || typeof value === "string" || typeof value === "boolean") return true;
1979
- if (typeof value === "number") return Number.isFinite(value);
1980
- if (Array.isArray(value)) return value.length <= 256 && value.every((item) => jsonValue(item, depth + 1));
1981
- return record2(value) && Object.keys(value).length <= 256 && Object.entries(value).every(([key, item]) => key.length > 0 && key.length <= 128 && jsonValue(item, depth + 1));
1982
- }
1983
- function record2(value) {
1984
- return value !== null && typeof value === "object" && !Array.isArray(value);
1985
- }
1986
- function safeRef2(value) {
1987
- return typeof value === "string" && SAFE_REF2.test(value);
1988
- }
1989
- function positive(value) {
1990
- return Number.isSafeInteger(value) && Number(value) > 0;
1991
- }
1992
- function timestamp(value) {
1993
- return typeof value === "string" && Number.isFinite(Date.parse(value));
1994
- }
1995
- function hostProducerFrameKind(value) {
1996
- return isHostProducerFrameKind(value);
1997
- }
1998
- function exactKeys2(value, keys) {
1999
- return JSON.stringify(Object.keys(value).sort()) === JSON.stringify([...keys].sort());
2000
- }
2001
- function exactOptional(value, required, optional) {
2002
- const keys = Object.keys(value);
2003
- return required.every((key) => keys.includes(key)) && keys.every((key) => required.includes(key) || optional.includes(key));
2004
- }
2005
2245
 
2006
2246
  // host/relay/src/host-producer-core.ts
2007
- import { createHash as createHash3 } from "node:crypto";
2008
- import { O_NOFOLLOW, O_RDONLY as O_RDONLY2 } from "node:constants";
2009
- import { mkdir as mkdir2, open as open2, readFile, rename as rename2, writeFile } from "node:fs/promises";
2010
- import { join as join2 } from "node:path";
2011
-
2012
- // host/relay/src/relay-ipc.ts
2013
- import { createConnection, createServer } from "node:net";
2014
-
2015
- // host/relay/src/durable-atomic-replacement.ts
2016
- import { O_RDONLY } from "node:constants";
2017
- import { lstat, mkdir, open, rename, unlink } from "node:fs/promises";
2018
- import { dirname } from "node:path";
2019
- var DEFAULT_PORT = {
2020
- platform: process.platform,
2021
- mkdir: (path, options) => mkdir(path, options),
2022
- lstat: (path) => lstat(path),
2023
- open_file: (path) => open(path, "wx", 384),
2024
- rename,
2025
- unlink,
2026
- open_directory: (path) => open(path, O_RDONLY),
2027
- unique_ref: () => `${process.pid}.${crypto.randomUUID()}`
2028
- };
2029
- async function durableAtomicReplace(target, data, port = DEFAULT_PORT) {
2030
- const parent = dirname(target);
2031
- await port.mkdir(parent, { recursive: true, mode: 448 });
2032
- const parentStats = await port.lstat(parent);
2033
- if (!parentStats.isDirectory() || parentStats.isSymbolicLink() || !ownerPrivate(parentStats, port.platform)) throw new TypeError(`Host Relay durable-state directory is unsafe: ${refusalFacts([{ name: "parent_stats", value: parentStats, expected: "an owner-private directory" }, { name: "platform", value: port.platform }])}.`);
2034
- const temporary = `${target}.${port.unique_ref()}.new`;
2035
- let staged;
2036
- const failures = [];
2037
- try {
2038
- staged = await port.open_file(temporary);
2039
- await staged.writeFile(data, typeof data === "string" ? { encoding: "utf8" } : void 0);
2040
- await staged.sync();
2041
- const closing = staged;
2042
- staged = void 0;
2043
- await closing.close();
2044
- await port.rename(temporary, target);
2045
- await syncParentDirectory(parent, port);
2046
- } catch (error) {
2047
- failures.push(error);
2048
- }
2049
- await closeStaged(staged, failures);
2050
- await removeTemporary(temporary, port, failures);
2051
- throwFailures(failures);
2052
- }
2053
- async function syncParentDirectory(path, port) {
2054
- if (port.platform === "win32") return;
2055
- const handle = await port.open_directory(path);
2056
- let failure;
2057
- try {
2058
- await handle.sync();
2059
- } catch (error) {
2060
- if (!directorySyncUnsupported(error)) failure = error;
2061
- }
2062
- try {
2063
- await handle.close();
2064
- } catch (error) {
2065
- if (failure === void 0) failure = error;
2066
- else failure = new AggregateError([failure, error], "Host Relay directory sync and close failed.");
2067
- }
2068
- if (failure !== void 0) throw failure;
2069
- }
2070
- function ownerPrivate(stats, platform) {
2071
- if (platform === "win32") return true;
2072
- const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
2073
- return stats.uid === uid && (stats.mode & 63) === 0;
2074
- }
2075
- async function closeStaged(staged, failures) {
2076
- if (staged !== void 0) try {
2077
- await staged.close();
2078
- } catch (error) {
2079
- failures.push(error);
2080
- }
2081
- }
2082
- async function removeTemporary(path, port, failures) {
2083
- try {
2084
- await port.unlink(path);
2085
- } catch (error) {
2086
- if (errorCode(error) !== "ENOENT") failures.push(error);
2087
- }
2088
- }
2089
- function throwFailures(failures) {
2090
- if (failures.length === 1) throw failures[0];
2091
- if (failures.length > 1) throw new AggregateError(failures, "Host Relay durable replacement and cleanup failed.");
2092
- }
2093
- function directorySyncUnsupported(error) {
2094
- return ["EINVAL", "ENOSYS", "ENOTSUP"].includes(errorCode(error) ?? "");
2095
- }
2096
- function errorCode(error) {
2097
- return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
2098
- }
2099
-
2100
- // host/relay/src/relay-request-binding-store.ts
2101
- var MAX_STATE_BYTES = 8 * 1024 * 1024;
2102
-
2103
- // host/relay/src/current-prompt-custody.ts
2104
- var HOST_CURRENT_PROMPT_MAX_BYTES = 256 * 1024;
2105
-
2106
- // host/service-log.ts
2107
- function writeHostServiceDiagnostic(producer_id, event, failure_kind, cause, sink) {
2108
- const line = `${JSON.stringify({ schema_version: CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA, producer_id, event, ...event === "watcher_scan_failed" && failure_kind !== void 0 ? { failure_kind } : {}, ...event === "watcher_scan_failed" && cause !== void 0 ? { cause } : {} })}
2109
- `;
2110
- try {
2111
- (sink ?? defaultHostServiceDiagnosticSink)(line);
2112
- } catch {
2113
- }
2114
- }
2115
- function defaultHostServiceDiagnosticSink(line) {
2116
- process.stderr.write(line);
2117
- }
2118
- var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
2119
- var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
2247
+ import { createHash as createHash4 } from "node:crypto";
2120
2248
 
2121
- // host/relay/src/relay-ipc.ts
2122
- var HOST_RELAY_REQUEST_BINDING_KEYS = Object.freeze([
2123
- "schema_version",
2124
- "producer_id",
2125
- "producer_installation_ref",
2126
- "local_session_ref",
2127
- "local_turn_ref",
2128
- "local_turn_aliases",
2129
- "prompt_ref",
2130
- "request_ref",
2131
- "bound_at",
2132
- "host_source_ref",
2133
- "host_session_ref",
2134
- "host_turn_ref",
2135
- "host_input_ref"
2136
- ]);
2137
- var HOST_RELAY_IPC_RESPONSE_SCHEMA = "ceal.host_relay_admission.v1";
2138
- var HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA = "ceal.host_relay_current_prompt_register.v1";
2139
- var HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA = "ceal.host_relay_current_prompt_release.v1";
2140
- var HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA = "ceal.host_relay_current_prompt_result.v1";
2141
- var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
2142
- var HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES = 4096;
2143
- var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
2144
- "context_stale",
2145
- "context_expired",
2146
- "host_source_refused",
2147
- "host_credential_refused",
2148
- "gateway_unavailable",
2149
- "gateway_refused",
2150
- "gateway_response_invalid",
2151
- "gateway_rate_limited",
2152
- "producer_frame_invalid",
2153
- "deadline",
2154
- "cancelled",
2155
- "mint_failed",
2156
- "relay_failed",
2157
- "request_authority_unavailable",
2158
- "request_missing",
2159
- "authority_mismatch",
2160
- "host_source_mismatch",
2161
- "host_session_mismatch",
2162
- "host_turn_mismatch",
2163
- "host_input_mismatch",
2164
- "host_prompt_mismatch"
2165
- ]);
2166
- var HOST_RELAY_DIRECT_WIRE_MINT_REASONS = vocabulary([
2167
- "context_stale",
2168
- "context_expired",
2169
- "host_source_refused",
2170
- "host_credential_refused",
2171
- "gateway_unavailable",
2172
- "gateway_rate_limited",
2173
- "gateway_refused",
2174
- "gateway_response_invalid",
2175
- "producer_frame_invalid"
2176
- ]);
2177
- var HOST_RELAY_INVOCATION_MINT_OUTCOMES = vocabulary(["completed", "caller_cancelled", "deadline", "failed", "no_binding"]);
2178
- var HOST_RELAY_EXCHANGE_FAILURES = vocabulary([
2179
- "permission_denied",
2180
- "socket_missing",
2181
- "connection_refused",
2182
- "closed_early",
2183
- "response_invalid",
2184
- "timed_out",
2185
- "cancelled",
2186
- "unavailable"
2187
- ]);
2188
- var HostRelayIpcError = class extends Error {
2189
- code;
2190
- /** Set only when the client-side socket exchange itself failed. */
2191
- failure;
2192
- constructor(code2, message = code2, failure) {
2249
+ // host/secure-file.ts
2250
+ import { execFile } from "node:child_process";
2251
+ import { constants } from "node:fs";
2252
+ import { lstat, open } from "node:fs/promises";
2253
+ import { dirname, win32 } from "node:path";
2254
+ import { fileURLToPath } from "node:url";
2255
+ var SecureFileCustodyError = class extends Error {
2256
+ name = "SecureFileCustodyError";
2257
+ reason;
2258
+ constructor(reason, message) {
2193
2259
  super(message);
2194
- this.code = code2;
2195
- this.failure = failure;
2196
- }
2197
- };
2198
- var HostRelayCurrentPromptCapacityRefusal = class extends Error {
2199
- constructor() {
2200
- super("current_prompt_capacity_exhausted");
2201
- this.name = "HostRelayCurrentPromptCapacityRefusal";
2260
+ this.reason = reason;
2202
2261
  }
2203
- };
2204
- function hostRelayExchangeFailure(error) {
2205
- return error instanceof HostRelayIpcError ? error.failure : void 0;
2206
- }
2207
- function socketErrorFailure(error) {
2208
- switch (errorCode2(error)) {
2209
- case "EACCES":
2210
- case "EPERM":
2211
- return "permission_denied";
2212
- case "ENOENT":
2213
- return "socket_missing";
2214
- case "ECONNREFUSED":
2215
- return "connection_refused";
2216
- default:
2217
- return "unavailable";
2218
- }
2219
- }
2220
- var HostRelayIpcAdmissionPort = class {
2221
- endpoint;
2222
- timeout_ms;
2223
- constructor(endpoint, timeoutMs = 5e3) {
2224
- this.endpoint = endpoint;
2225
- this.timeout_ms = timeoutMs;
2226
- }
2227
- async admit(frame) {
2228
- const response2 = await submitHostProducerFrame(this.endpoint, frame, this.timeout_ms);
2229
- if (!relayAdmissionCode(response2.code)) throw new HostRelayIpcError(response2.code);
2230
- return { code: response2.code, admitted_through_sequence: response2.admitted_through_sequence, delivered_through_sequence: response2.delivered_through_sequence };
2262
+ get code() {
2263
+ if (this.reason === "reparse_point") return "ELOOP";
2264
+ if (this.reason === "not_found") return "ENOENT";
2265
+ if (this.reason === "too_large") return "secure_file_too_large";
2266
+ if (this.reason === "already_exists") return "EEXIST";
2267
+ return this.reason === "not_private" ? "secure_file_not_private" : "secure_file_unavailable";
2231
2268
  }
2232
2269
  };
2233
- var HostRelayCurrentPromptPort = class {
2234
- endpoint;
2235
- timeout_ms;
2236
- constructor(endpoint, timeoutMs = 5e3) {
2237
- this.endpoint = endpoint;
2238
- this.timeout_ms = timeoutMs;
2239
- }
2240
- async register(input) {
2241
- const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_REGISTER_SCHEMA, ...input };
2242
- const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
2243
- if (response2.status === "capacity_exhausted") throw new HostRelayCurrentPromptCapacityRefusal();
2244
- if (response2.status !== "registered") throw new HostRelayIpcError("relay_unavailable", response2.status);
2245
- }
2246
- async release(identity) {
2247
- const request2 = { schema_version: HOST_RELAY_CURRENT_PROMPT_RELEASE_SCHEMA, ...identity };
2248
- const response2 = await exchange(this.endpoint, encodeLengthPrefixed(request2), this.timeout_ms, decodeCurrentPromptResponseBuffer);
2249
- if (response2.status !== "released") throw new HostRelayIpcError("relay_unavailable", response2.status, "response_invalid");
2270
+ var WINDOWS_SECUREFS_REFUSAL_SCHEMA = "ceal.host_securefs_refusal.v1";
2271
+ var WINDOWS_SECUREFS_TIMEOUT_MS = 1e4;
2272
+ var WINDOWS_SECUREFS_DIAGNOSTIC_BYTES = 4096;
2273
+ function secureOpenFlags(flags, platform) {
2274
+ if (platform === "win32") return flags;
2275
+ const nofollow = constants.O_NOFOLLOW;
2276
+ if (typeof nofollow === "number" && nofollow !== 0) return flags | nofollow;
2277
+ throw new Error("Secure file reads require O_NOFOLLOW on this platform.");
2278
+ }
2279
+ async function openNoFollow(path, flags = constants.O_RDONLY, mode, platform = process.platform, _custody = {}) {
2280
+ if (platform !== "win32") return await open(path, secureOpenFlags(flags, platform), mode);
2281
+ return await openWindowsIdentityScreened(path, flags, mode);
2282
+ }
2283
+ async function openWindowsIdentityScreened(path, flags, mode) {
2284
+ const handle = await open(path, flags, mode);
2285
+ try {
2286
+ const first = await handle.stat();
2287
+ if (!first.isFile()) throw new SecureFileCustodyError("not_private", "The Windows Host file is not a regular file.");
2288
+ await assertWindowsLinkFree(path, first);
2289
+ await assertWindowsIdentityStable(handle, first, path);
2290
+ return handle;
2291
+ } catch (error) {
2292
+ await handle.close();
2293
+ throw error;
2250
2294
  }
2251
- };
2252
- function submitHostProducerFrame(endpoint, frame, timeoutMs = 5e3) {
2253
- return exchange(endpoint, encodeHostProducerFrame(frame), timeoutMs, decodeResponseBuffer);
2254
- }
2255
- function exchange(endpoint, encoded, timeoutMs, decode, signal) {
2256
- return new Promise((resolve2, reject) => {
2257
- const socket = createConnection(endpoint);
2258
- let buffer = Buffer.alloc(0);
2259
- let settled = false;
2260
- const aborted = () => finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC was cancelled.", "cancelled"));
2261
- const finish = (error, response2) => {
2262
- if (settled) return;
2263
- settled = true;
2264
- signal?.removeEventListener("abort", aborted);
2265
- socket.destroy();
2266
- if (error !== null) reject(error);
2267
- else if (response2 !== void 0) resolve2(response2);
2268
- };
2269
- if (timeoutMs !== void 0) socket.setTimeout(timeoutMs, () => finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC timed out.", "timed_out")));
2270
- if (signal?.aborted === true) {
2271
- aborted();
2272
- return;
2273
- }
2274
- signal?.addEventListener("abort", aborted, { once: true });
2275
- socket.once("error", (error) => {
2276
- const failure = socketErrorFailure(error);
2277
- finish(new HostRelayIpcError("relay_unavailable", `Host Relay IPC is unavailable: ${failure}.`, failure));
2278
- });
2279
- socket.once("connect", () => socket.write(encoded));
2280
- socket.on("data", (chunk) => {
2281
- try {
2282
- buffer = Buffer.concat([buffer, Buffer.from(chunk)]);
2283
- const decoded = decode(buffer);
2284
- if (decoded !== null) finish(null, decoded);
2285
- } catch (error) {
2286
- finish(new HostRelayIpcError("relay_unavailable", error instanceof Error ? error.message : "Host Relay IPC response is invalid.", "response_invalid"));
2295
+ }
2296
+ async function assertWindowsLinkFree(path, first) {
2297
+ const entry = await lstat(path);
2298
+ if (entry.isSymbolicLink() || entry.dev !== first.dev || entry.ino !== first.ino) {
2299
+ throw new SecureFileCustodyError("reparse_point", "The Windows Host file path resolves through a reparse point.");
2300
+ }
2301
+ }
2302
+ async function assertWindowsIdentityStable(handle, first, path) {
2303
+ const second = await handle.stat();
2304
+ if (second.dev !== first.dev || second.ino !== first.ino || second.size !== first.size || second.mtimeMs !== first.mtimeMs) {
2305
+ throw new SecureFileCustodyError("unavailable", "The Windows Host file changed while its identity was checked.");
2306
+ }
2307
+ const entry = await lstat(path);
2308
+ if (entry.isSymbolicLink()) throw new SecureFileCustodyError("reparse_point", "The Windows Host file path resolves through a reparse point.");
2309
+ if (entry.dev !== second.dev || entry.ino !== second.ino) throw new SecureFileCustodyError("unavailable", "The Windows Host file path no longer names the opened file.");
2310
+ }
2311
+ async function readPrivateFileWithSecurefs(path, maxBytes, custody = {}) {
2312
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) throw new TypeError("The securefs read bound must be a positive safe integer.");
2313
+ const result = await runWindowsSecurefs(["internal", "securefs", "read-private-file", path, String(maxBytes)], maxBytes, custody);
2314
+ if (result.exit_code === 0 && result.stderr.length === 0 && result.stdout.length <= maxBytes) return result.stdout;
2315
+ if (result.stdout.length !== 0) throw new SecureFileCustodyError("unavailable", "The Windows Host securefs command returned bytes with a refusal.");
2316
+ if (result.exit_code === 0) throw new SecureFileCustodyError("unavailable", "The Windows Host securefs command exceeded its output contract.");
2317
+ throw securefsRefusal(result.stderr);
2318
+ }
2319
+ async function runWindowsSecurefs(args, maxBytes, custody) {
2320
+ if (custody.run_securefs_command !== void 0) return await custody.run_securefs_command(args);
2321
+ const executable = custody.securefs_executable ?? resolveInstalledHostExecutable();
2322
+ const processArgs = [...custody.securefs_args_prefix ?? [], ...args];
2323
+ return await new Promise((resolve2, reject) => {
2324
+ execFile(executable, processArgs, { encoding: "buffer", timeout: WINDOWS_SECUREFS_TIMEOUT_MS, maxBuffer: maxBytes + WINDOWS_SECUREFS_DIAGNOSTIC_BYTES }, (error, stdout, stderr) => {
2325
+ const exitCode = error === null ? 0 : typeof error.code === "number" ? error.code : null;
2326
+ if (exitCode === null) {
2327
+ reject(new SecureFileCustodyError("unavailable", "The installed Windows Host securefs command could not run."));
2328
+ return;
2287
2329
  }
2288
- });
2289
- socket.once("end", () => {
2290
- if (!settled) finish(new HostRelayIpcError("relay_unavailable", "Host Relay IPC closed without a response.", "closed_early"));
2330
+ resolve2({ exit_code: exitCode, stdout: Buffer.from(stdout), stderr: Buffer.from(stderr) });
2291
2331
  });
2292
2332
  });
2293
2333
  }
2294
- function decodeHostRelayIpcResponse(input) {
2295
- if (input.length < HOST_RELAY_IPC_LENGTH_BYTES) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC response is truncated: ${refusalFact("response_bytes", input.byteLength, `at least ${HOST_RELAY_IPC_LENGTH_BYTES}`)}.`);
2296
- const length = input.readUInt32BE(0);
2297
- if (length === 0 || length > HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES || input.length !== length + HOST_RELAY_IPC_LENGTH_BYTES) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC response length is invalid: ${refusalFacts([{ name: "declared_bytes", value: length, expected: `between 1 and ${HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES}` }, { name: "received_bytes", value: input.byteLength, expected: `exactly ${length + HOST_RELAY_IPC_LENGTH_BYTES}` }])}.`);
2334
+ function securefsRefusal(stderr) {
2298
2335
  let value;
2299
2336
  try {
2300
- value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.subarray(HOST_RELAY_IPC_LENGTH_BYTES)));
2337
+ value = JSON.parse(stderr.toString("utf8"));
2301
2338
  } catch {
2302
- throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC response is invalid.");
2339
+ return new SecureFileCustodyError("unavailable", "The Windows Host securefs command returned an unreadable refusal.");
2303
2340
  }
2304
- if (!validResponse(value)) throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC response shape is invalid.");
2305
- return value;
2306
- }
2307
- function relayAdmissionCode(value) {
2308
- return value === "admitted" || value === "already_admitted" || value === "producer_epoch_stale" || value === "sequence_gap" || value === "frame_identity_conflict" || value === "producer_queue_full" || value === "relay_queue_full" || value === "producer_already_connected" || value === "invalid_producer_identity";
2309
- }
2310
- function validResponse(value) {
2311
- return record3(value) && value.schema_version === HOST_RELAY_IPC_RESPONSE_SCHEMA && hostRelayIpcCode(value.code) && nonnegative(value.admitted_through_sequence) && nonnegative(value.delivered_through_sequence) && Object.keys(value).length === 4;
2341
+ if (!isRecord2(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.");
2342
+ if (value.code === "reparse_point") return new SecureFileCustodyError("reparse_point", "The Windows Host securefs file is a reparse point.");
2343
+ if (value.code === "not_found") return new SecureFileCustodyError("not_found", "The Windows Host securefs file was not found.");
2344
+ if (value.code === "too_large") return new SecureFileCustodyError("too_large", "The Windows Host securefs file exceeds its read bound.");
2345
+ if (value.code === "already_exists") return new SecureFileCustodyError("already_exists", "The Windows Host securefs target already exists.");
2346
+ if (value.code === "not_private") return new SecureFileCustodyError("not_private", "The Windows Host securefs file is not private to the current user.");
2347
+ return new SecureFileCustodyError("unavailable", "The Windows Host securefs command refused the file read.");
2312
2348
  }
2313
- function hostRelayIpcCode(value) {
2314
- return typeof value === "string" && ["admitted", "already_admitted", "producer_epoch_stale", "sequence_gap", "frame_identity_conflict", "producer_queue_full", "relay_queue_full", "invalid_frame_encoding", "invalid_frame_length", "frame_truncated", "unsupported_frame_schema", "invalid_frame_shape", "invalid_producer_identity", "frame_too_large", "producer_already_connected", "relay_unavailable"].includes(value);
2349
+ function resolveInstalledHostExecutable() {
2350
+ const privateRuntimeRoot = dirname(fileURLToPath(import.meta.url));
2351
+ return win32.join(privateRuntimeRoot, "..", "..", "bin", "ceal-host.exe");
2315
2352
  }
2316
- function nonnegative(value) {
2317
- return Number.isSafeInteger(value) && Number(value) >= 0;
2318
- }
2319
- function record3(value) {
2353
+ function isRecord2(value) {
2320
2354
  return value !== null && typeof value === "object" && !Array.isArray(value);
2321
2355
  }
2322
- function decodeResponseBuffer(buffer) {
2323
- if (buffer.length < HOST_RELAY_IPC_LENGTH_BYTES) return null;
2324
- const length = buffer.readUInt32BE(0);
2325
- if (length === 0 || length > HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC response length is invalid: ${refusalFact("declared_bytes", length, `between 1 and ${HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES}`)}.`);
2326
- if (buffer.length < length + HOST_RELAY_IPC_LENGTH_BYTES) return null;
2327
- if (buffer.length !== length + HOST_RELAY_IPC_LENGTH_BYTES) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC returned trailing bytes: ${refusalFacts([{ name: "declared_bytes", value: length, expected: `the received message length minus ${HOST_RELAY_IPC_LENGTH_BYTES}` }, { name: "received_bytes", value: buffer.byteLength, expected: `exactly ${length + HOST_RELAY_IPC_LENGTH_BYTES}` }])}.`);
2328
- return decodeHostRelayIpcResponse(buffer);
2329
- }
2330
- function decodeCurrentPromptResponseBuffer(buffer) {
2331
- const value = decodeLengthPrefixedJson(buffer, HOST_RELAY_ADMISSION_RESPONSE_MAX_BYTES);
2332
- if (value === null) return null;
2333
- if (!record3(value) || !exactKeys3(value, ["schema_version", "status"]) || value.schema_version !== HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA || value.status !== "registered" && value.status !== "capacity_exhausted" && value.status !== "current_prompt_unavailable" && value.status !== "released") throw new HostRelayIpcError("relay_unavailable", "Host Relay current-prompt response is invalid.", "response_invalid");
2334
- return { schema_version: HOST_RELAY_CURRENT_PROMPT_RESULT_SCHEMA, status: value.status };
2335
- }
2336
- function decodeLengthPrefixedJson(input, maximum) {
2337
- if (input.length < HOST_RELAY_IPC_LENGTH_BYTES) return null;
2338
- const length = input.readUInt32BE(0);
2339
- if (length === 0 || length > maximum) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC message length is invalid: ${refusalFact("declared_bytes", length, `between 1 and ${maximum}`)}.`);
2340
- if (input.length < length + HOST_RELAY_IPC_LENGTH_BYTES) return null;
2341
- if (input.length !== length + HOST_RELAY_IPC_LENGTH_BYTES) throw new HostRelayIpcError("relay_unavailable", `Host Relay IPC message has trailing bytes: ${refusalFacts([{ name: "declared_bytes", value: length, expected: `the received message length minus ${HOST_RELAY_IPC_LENGTH_BYTES}` }, { name: "received_bytes", value: input.byteLength, expected: `exactly ${length + HOST_RELAY_IPC_LENGTH_BYTES}` }])}.`);
2342
- try {
2343
- return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.subarray(HOST_RELAY_IPC_LENGTH_BYTES)));
2344
- } catch {
2345
- throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC message is invalid.");
2346
- }
2347
- }
2348
- function encodeLengthPrefixed(value) {
2349
- const body = Buffer.from(JSON.stringify(value), "utf8");
2350
- const output = Buffer.allocUnsafe(body.length + HOST_RELAY_IPC_LENGTH_BYTES);
2351
- output.writeUInt32BE(body.length, 0);
2352
- body.copy(output, HOST_RELAY_IPC_LENGTH_BYTES);
2353
- return output;
2356
+ function isOwnedBy(uid, expectedUid) {
2357
+ return Number.isSafeInteger(uid) && Number.isSafeInteger(expectedUid) && uid === expectedUid;
2354
2358
  }
2355
- function exactKeys3(value, required, optional = []) {
2356
- const keys = Object.keys(value);
2357
- return required.every((key) => Object.hasOwn(value, key)) && keys.every((key) => required.includes(key) || optional.includes(key));
2359
+ function isSelfOwned(uid, platform = process.platform, _handle) {
2360
+ if (platform === "win32" || typeof process.geteuid !== "function") return false;
2361
+ return isOwnedBy(uid, process.geteuid());
2358
2362
  }
2359
- function errorCode2(error) {
2360
- return record3(error) && typeof error.code === "string" ? error.code : null;
2363
+ function isPrivateFileMode(mode, platform = process.platform, _handle) {
2364
+ if (platform === "win32") return false;
2365
+ return (mode & 63) === 0;
2361
2366
  }
2362
2367
 
2363
2368
  // host/relay/src/host-producer-core.ts
@@ -2367,6 +2372,7 @@ function isHostTranscriptCursorReason(value) {
2367
2372
  }
2368
2373
  var TRANSCRIPT_MISSING_REASON = "transcript_missing";
2369
2374
  var TRANSCRIPT_SCAN_READ_ERROR_ERRNOS = ["EBADF", "EACCES", "EIO", "EMFILE", "ENFILE", "ENOENT", "ENOTDIR", "EPERM"];
2375
+ var HOST_INVOCATION_TRANSCRIPT_TAIL_MAX_BYTES = 1024 * 1024;
2370
2376
  function transcriptScanFailureKind(error, adapterFailureKind) {
2371
2377
  if (transcriptErrorCode(error) === "ELOOP") return "symlink";
2372
2378
  const adapter = adapterFailureKind(error);
@@ -2376,16 +2382,6 @@ function transcriptScanFailureKind(error, adapterFailureKind) {
2376
2382
  if (code2 !== null && TRANSCRIPT_SCAN_READ_ERROR_ERRNOS.includes(code2)) return "read_error";
2377
2383
  return "other";
2378
2384
  }
2379
- async function registerHostCurrentPromptDiagnosed(register, diagnostic) {
2380
- try {
2381
- await register();
2382
- diagnostic("current_prompt_registered");
2383
- return true;
2384
- } catch (error) {
2385
- if (!(error instanceof HostRelayCurrentPromptCapacityRefusal)) diagnostic("current_prompt_registration_failed");
2386
- throw error;
2387
- }
2388
- }
2389
2385
  function transcriptErrorCode(error) {
2390
2386
  return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
2391
2387
  }
@@ -2433,10 +2429,10 @@ async function transcriptCursorBoundsReason(handle, cursor, size) {
2433
2429
  return null;
2434
2430
  }
2435
2431
  function transcriptSha256(value) {
2436
- return createHash3("sha256").update(value).digest("hex");
2432
+ return createHash4("sha256").update(value).digest("hex");
2437
2433
  }
2438
2434
  function transcriptObjectSha256(value) {
2439
- return createHash3("sha256").update(JSON.stringify(value), "utf8").digest("hex");
2435
+ return createHash4("sha256").update(JSON.stringify(value), "utf8").digest("hex");
2440
2436
  }
2441
2437
  function isTranscriptRecord(value) {
2442
2438
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -2497,24 +2493,18 @@ var HostTranscriptWatcher = class {
2497
2493
  return scanTranscriptRegistrations(await this.listRegistrations(), (registration) => this.scanRegistration(registration));
2498
2494
  }
2499
2495
  async scanRegistration(registration) {
2500
- try {
2501
- return await this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
2502
- missing: () => this.retireMissing(registration),
2503
- failed: (handle, error) => this.scanFailed(handle, registration, error)
2504
- });
2505
- } catch (error) {
2506
- if (error instanceof HostRelayCurrentPromptCapacityRefusal) return { records: 0, enqueued: 0, quarantined: false };
2507
- throw error;
2508
- }
2496
+ return this.openTranscript(registration, (handle) => this.scanOpened(handle, registration), {
2497
+ missing: () => this.retireMissing(registration),
2498
+ failed: (handle, error) => this.scanFailed(handle, registration, error)
2499
+ });
2509
2500
  }
2510
2501
  async openTranscript(registration, scan, handlers) {
2511
2502
  let handle;
2512
2503
  try {
2513
- handle = await open2(this.transcriptPathOf(registration), O_RDONLY2 | O_NOFOLLOW);
2504
+ handle = await openNoFollow(this.transcriptPathOf(registration));
2514
2505
  return await scan(handle);
2515
2506
  } catch (error) {
2516
2507
  if (handle === void 0 && isMissingTranscriptError(error)) return handlers.missing();
2517
- if (hostRelayExchangeFailure(error) !== void 0 || error instanceof HostRelayCurrentPromptCapacityRefusal) throw error;
2518
2508
  return handlers.failed(handle, error);
2519
2509
  } finally {
2520
2510
  await handle?.close();
@@ -2560,29 +2550,12 @@ var HostTranscriptWatcher = class {
2560
2550
  return transcriptCursorBoundsReason(handle, cursor, size);
2561
2551
  }
2562
2552
  };
2563
- var HOST_PRODUCER_STATE_IDENTITY = {
2564
- codex: { file_name: "codex-producer-state.json", schema: "ceal.codex_producer_state.v1" },
2565
- claude: { file_name: "claude-producer-state.json", schema: "ceal.claude_producer_state.v1" }
2566
- };
2567
2553
  var HostProducerDrainCore = class {
2568
2554
  host;
2569
- relay;
2570
- statePath;
2571
- stateSchema;
2572
- installationRef;
2573
- epoch;
2574
2555
  now;
2575
2556
  tail = Promise.resolve();
2576
2557
  constructor(host, runtime) {
2577
- if (!isHostSafeRef(runtime.producer_installation_ref)) throw host.fail("configuration", `${host.producer_label} producer installation identity is invalid.`);
2578
- const epoch = runtime.producer_epoch ?? 1;
2579
- if (!Number.isSafeInteger(epoch) || epoch <= 0) throw host.fail("configuration", `${host.producer_label} producer epoch is invalid.`);
2580
2558
  this.host = host;
2581
- this.relay = runtime.relay;
2582
- this.statePath = join2(host.outbox_directory, HOST_PRODUCER_STATE_IDENTITY[host.producer_id].file_name);
2583
- this.stateSchema = HOST_PRODUCER_STATE_IDENTITY[host.producer_id].schema;
2584
- this.installationRef = runtime.producer_installation_ref;
2585
- this.epoch = epoch;
2586
2559
  this.now = runtime.now ?? (() => /* @__PURE__ */ new Date());
2587
2560
  }
2588
2561
  drainOutbox() {
@@ -2592,116 +2565,145 @@ var HostProducerDrainCore = class {
2592
2565
  }
2593
2566
  async drainLocked() {
2594
2567
  const read = await this.host.listEnvelopes();
2595
- let admitted = 0;
2568
+ let removed = 0;
2596
2569
  let expired = 0;
2597
- let retained = 0;
2598
2570
  for (const envelope of read.entries) {
2599
- const reserved = await this.hasReservation(this.host.submissionRefOf(envelope));
2600
- if (!reserved && Date.parse(this.host.expiresAtOf(envelope)) <= this.now().getTime()) {
2601
- await this.host.removeEnvelope(envelope);
2602
- expired += 1;
2603
- continue;
2604
- }
2605
- const frames = await this.reserve(envelope);
2606
- let complete = true;
2607
- for (const frame of frames) {
2608
- const outcome = await this.relay.admit(frame);
2609
- if (outcome.code !== "admitted" && outcome.code !== "already_admitted") {
2610
- complete = false;
2611
- break;
2612
- }
2613
- }
2614
- if (!complete) {
2615
- retained += 1;
2616
- break;
2617
- }
2571
+ if (Date.parse(this.host.expiresAtOf(envelope)) <= this.now().getTime()) expired += 1;
2572
+ else removed += 1;
2618
2573
  await this.host.removeEnvelope(envelope);
2619
- await this.release(this.host.submissionRefOf(envelope));
2620
- admitted += 1;
2621
2574
  }
2622
- return { admitted, retained: retained + Math.max(0, read.entries.length - admitted - expired - retained), expired, malformed: read.malformed };
2623
- }
2624
- async hasReservation(submissionRef2) {
2625
- return (await this.readState()).pending[submissionRef2] !== void 0;
2626
- }
2627
- async reserve(envelope) {
2628
- const state = await this.readState();
2629
- const submissionRef2 = this.host.submissionRefOf(envelope);
2630
- const existing = state.pending[submissionRef2];
2631
- if (existing !== void 0) return existing;
2632
- const drafts = await this.host.mapEnvelope(envelope);
2633
- const frames = drafts.map((draft, offset) => ({
2634
- schema_version: "ceal.host_producer_frame.v1",
2635
- producer_id: this.host.producer_id,
2636
- producer_installation_ref: this.installationRef,
2637
- producer_epoch: this.epoch,
2638
- sequence: state.next_sequence + offset,
2639
- frame_kind: draft.frame_kind,
2640
- occurred_at: this.host.createdAtOf(envelope),
2641
- payload: draft.payload
2642
- }));
2643
- await this.writeState({ ...state, next_sequence: state.next_sequence + frames.length, pending: { ...state.pending, [submissionRef2]: frames } });
2644
- return frames;
2645
- }
2646
- async release(submissionRef2) {
2647
- const state = await this.readState();
2648
- if (state.pending[submissionRef2] === void 0) return;
2649
- const pending = { ...state.pending };
2650
- delete pending[submissionRef2];
2651
- await this.writeState({ ...state, pending });
2652
- }
2653
- async readState() {
2575
+ return { removed, expired, malformed: read.malformed };
2576
+ }
2577
+ };
2578
+
2579
+ // host/relay/src/durable-atomic-replacement.ts
2580
+ import { lstat as lstat2, mkdir, open as open2, rename, unlink } from "node:fs/promises";
2581
+ import { dirname as dirname2 } from "node:path";
2582
+
2583
+ // host/windows-rename-retry.ts
2584
+ import { setTimeout as setTimeout2 } from "node:timers/promises";
2585
+ var RETRY_WINDOW_MS = 1e4;
2586
+ var RETRY_START_DELAY_MS = 100;
2587
+ var RETRY_MAX_DELAY_MS = 2e3;
2588
+ var RETRYABLE_CODES = /* @__PURE__ */ new Set(["EBUSY", "EPERM", "EACCES"]);
2589
+ var WindowsRenameRetryError = class extends Error {
2590
+ code = "rename_unavailable";
2591
+ constructor(target, attempts, options = {}) {
2592
+ 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);
2593
+ this.name = "WindowsRenameRetryError";
2594
+ }
2595
+ };
2596
+ function resolveRetrySchedule(tuning) {
2597
+ return {
2598
+ windowMs: tuning.windowMs ?? RETRY_WINDOW_MS,
2599
+ startDelayMs: tuning.startDelayMs ?? RETRY_START_DELAY_MS,
2600
+ maxDelayMs: tuning.maxDelayMs ?? RETRY_MAX_DELAY_MS,
2601
+ sleep: tuning.sleep ?? ((ms) => setTimeout2(ms)),
2602
+ now: tuning.now ?? (() => Date.now())
2603
+ };
2604
+ }
2605
+ async function renameWithWindowsRetry(operation, tuning) {
2606
+ const schedule = resolveRetrySchedule(tuning);
2607
+ const deadline = schedule.now() + schedule.windowMs;
2608
+ let delayMs = schedule.startDelayMs;
2609
+ let attempts = 0;
2610
+ for (; ; ) {
2611
+ attempts += 1;
2654
2612
  try {
2655
- const value = JSON.parse(await readFile(this.statePath, "utf8"));
2656
- return this.decodeState(value);
2613
+ await operation();
2614
+ return;
2657
2615
  } catch (error) {
2658
- if (errorCode3(error) !== "ENOENT") throw error;
2659
- return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: 1, pending: {} };
2660
- }
2661
- }
2662
- async writeState(state) {
2663
- await mkdir2(this.host.outbox_directory, { recursive: true, mode: 448 });
2664
- const temporary = `${this.statePath}.${process.pid}.new`;
2665
- await writeFile(temporary, JSON.stringify(state), { mode: 384 });
2666
- await rename2(temporary, this.statePath);
2667
- }
2668
- decodeState(value) {
2669
- const label = this.host.producer_label;
2670
- const fail = (code2, message) => this.host.fail(code2, message);
2671
- if (!isDrainStateHeader(value, this.stateSchema, this.installationRef)) {
2672
- throw fail("storage", `${label} producer state header is malformed or belongs to another installation (${refusalFacts([
2673
- { name: "schema_version", value: isTranscriptRecord(value) ? value.schema_version : void 0, expected: this.stateSchema },
2674
- { name: "producer_installation_ref", value: isTranscriptRecord(value) ? value.producer_installation_ref : void 0, expected: this.installationRef }
2675
- ])}).`);
2676
- }
2677
- if (Number(value.producer_epoch) > this.epoch) throw fail("storage", `${label} producer state epoch is ahead of this installation (${refusalFact("producer_epoch", value.producer_epoch, `at most ${this.epoch}`)}).`);
2678
- const pending = {};
2679
- const storedEpoch = Number(value.producer_epoch);
2680
- for (const [submissionRef2, frames] of Object.entries(value.pending)) {
2681
- if (!isHostSafeRef(submissionRef2) || !Array.isArray(frames) || frames.length === 0) throw fail("storage", `${label} producer pending reservation is malformed.`);
2682
- pending[submissionRef2] = frames.map((frame) => this.decodeStoredFrame(frame, storedEpoch));
2616
+ if (tuning.platform !== "win32" || !isRetryableRenameError(error)) throw error;
2617
+ await sleepWithinWindow(schedule, deadline, delayMs, () => new WindowsRenameRetryError(tuning.target, attempts, { cause: error }));
2618
+ delayMs = Math.min(delayMs * 2, schedule.maxDelayMs);
2683
2619
  }
2684
- return { schema_version: this.stateSchema, producer_installation_ref: this.installationRef, producer_epoch: this.epoch, next_sequence: Number(value.next_sequence), pending };
2685
- }
2686
- decodeStoredFrame(value, storedEpoch) {
2687
- const label = this.host.producer_label;
2688
- const json = Buffer.from(JSON.stringify(value), "utf8");
2689
- const encoded = Buffer.allocUnsafe(json.length + 4);
2690
- encoded.writeUInt32BE(json.length, 0);
2691
- json.copy(encoded, 4);
2692
- const frame = decodeHostProducerFrame(encoded);
2693
- if (frame.producer_id !== this.host.producer_id || frame.producer_installation_ref !== this.installationRef || frame.producer_epoch > storedEpoch) throw this.host.fail("storage", `${label} producer pending frame identity is malformed (${refusalFacts([
2694
- { name: "producer_id", value: frame.producer_id, expected: this.host.producer_id },
2695
- { name: "producer_installation_ref", value: frame.producer_installation_ref, expected: this.installationRef },
2696
- { name: "producer_epoch", value: frame.producer_epoch, expected: `at most ${storedEpoch}` }
2697
- ])}).`);
2698
- return frame;
2699
2620
  }
2621
+ }
2622
+ async function sleepWithinWindow(schedule, deadline, delayMs, exhausted) {
2623
+ if (schedule.now() >= deadline) throw exhausted();
2624
+ await schedule.sleep(Math.min(delayMs, deadline - schedule.now()));
2625
+ if (schedule.now() >= deadline) throw exhausted();
2626
+ }
2627
+ function isRetryableRenameError(error) {
2628
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" && RETRYABLE_CODES.has(error.code);
2629
+ }
2630
+
2631
+ // host/relay/src/durable-atomic-replacement.ts
2632
+ var DEFAULT_PORT = {
2633
+ platform: process.platform,
2634
+ mkdir: (path, options) => mkdir(path, options),
2635
+ lstat: (path) => lstat2(path),
2636
+ open_file: (path) => open2(path, "wx", 384),
2637
+ rename,
2638
+ unlink,
2639
+ open_directory: (path) => open2(path),
2640
+ unique_ref: () => `${process.pid}.${crypto.randomUUID()}`
2700
2641
  };
2701
- function isDrainStateHeader(value, schema, installationRef) {
2702
- return isTranscriptRecord(value) && value.schema_version === schema && value.producer_installation_ref === installationRef && Number.isSafeInteger(value.producer_epoch) && Number(value.producer_epoch) > 0 && Number.isSafeInteger(value.next_sequence) && Number(value.next_sequence) > 0 && isTranscriptRecord(value.pending);
2642
+ async function durableAtomicReplace(target, data, port = DEFAULT_PORT) {
2643
+ const parent = dirname2(target);
2644
+ await port.mkdir(parent, { recursive: true, mode: 448 });
2645
+ const parentStats = await port.lstat(parent);
2646
+ const expectedUid = typeof process.geteuid === "function" ? process.geteuid() : parentStats.uid;
2647
+ const ownerPrivate = port.platform === "win32" || isOwnedBy(parentStats.uid, expectedUid) && isPrivateFileMode(parentStats.mode, port.platform);
2648
+ 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 }])}.`);
2649
+ const temporary = `${target}.${port.unique_ref()}.new`;
2650
+ let staged;
2651
+ const failures = [];
2652
+ try {
2653
+ staged = await port.open_file(temporary);
2654
+ await staged.writeFile(data, typeof data === "string" ? { encoding: "utf8" } : void 0);
2655
+ await staged.sync();
2656
+ const closing = staged;
2657
+ staged = void 0;
2658
+ await closing.close();
2659
+ await renameWithWindowsRetry(() => port.rename(temporary, target), { ...port.rename_retry, platform: port.platform, target });
2660
+ await syncParentDirectory(parent, port);
2661
+ } catch (error) {
2662
+ failures.push(error);
2663
+ }
2664
+ await closeStaged(staged, failures);
2665
+ await removeTemporary(temporary, port, failures);
2666
+ throwFailures(failures);
2667
+ }
2668
+ async function syncParentDirectory(path, port) {
2669
+ if (port.platform === "win32") return;
2670
+ const handle = await port.open_directory(path);
2671
+ let failure;
2672
+ try {
2673
+ await handle.sync();
2674
+ } catch (error) {
2675
+ if (!directorySyncUnsupported(error)) failure = error;
2676
+ }
2677
+ try {
2678
+ await handle.close();
2679
+ } catch (error) {
2680
+ if (failure === void 0) failure = error;
2681
+ else failure = new AggregateError([failure, error], "Host Relay directory sync and close failed.");
2682
+ }
2683
+ if (failure !== void 0) throw failure;
2684
+ }
2685
+ async function closeStaged(staged, failures) {
2686
+ if (staged !== void 0) try {
2687
+ await staged.close();
2688
+ } catch (error) {
2689
+ failures.push(error);
2690
+ }
2691
+ }
2692
+ async function removeTemporary(path, port, failures) {
2693
+ try {
2694
+ await port.unlink(path);
2695
+ } catch (error) {
2696
+ if (errorCode(error) !== "ENOENT") failures.push(error);
2697
+ }
2703
2698
  }
2704
- function errorCode3(error) {
2699
+ function throwFailures(failures) {
2700
+ if (failures.length === 1) throw failures[0];
2701
+ if (failures.length > 1) throw new AggregateError(failures, "Host Relay durable replacement and cleanup failed.");
2702
+ }
2703
+ function directorySyncUnsupported(error) {
2704
+ return ["EINVAL", "ENOSYS", "ENOTSUP"].includes(errorCode(error) ?? "");
2705
+ }
2706
+ function errorCode(error) {
2705
2707
  return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
2706
2708
  }
2707
2709
 
@@ -2715,6 +2717,22 @@ var DEFAULT_LIMITS = { global_count: 8192, global_bytes: 64 * 1024 * 1024, produ
2715
2717
  var RETRY_SECONDS = [1, 2, 4, 8, 16, 32, 60];
2716
2718
  var MAX_RETRYABLE_ATTEMPTS = RETRY_SECONDS.length + 10;
2717
2719
 
2720
+ // host/relay/src/relay-pipe-handshake.ts
2721
+ var RELAY_PIPE_FRAME_BYTES = Uint32Array.BYTES_PER_ELEMENT;
2722
+
2723
+ // host/relay/src/relay-ipc.ts
2724
+ var HOST_RELAY_IPC_LENGTH_BYTES = Uint32Array.BYTES_PER_ELEMENT;
2725
+ var HOST_RELAY_EXCHANGE_FAILURES = vocabulary([
2726
+ "permission_denied",
2727
+ "socket_missing",
2728
+ "connection_refused",
2729
+ "closed_early",
2730
+ "response_invalid",
2731
+ "timed_out",
2732
+ "cancelled",
2733
+ "unavailable"
2734
+ ]);
2735
+
2718
2736
  // host/claude/src/claude-invocation-context.ts
2719
2737
  function claudeInvocationContextFromTurn(identity, promptRef, expiresAt, workingDirectory, turnGenerationRef) {
2720
2738
  return invocationContextFromTurn(identity, promptRef, expiresAt, { schema_version: CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION, validate: validateClaudeHostInvocationContext, working_directory: workingDirectory, turn_generation_ref: turnGenerationRef });
@@ -2726,42 +2744,38 @@ function validateClaudeHostInvocationContext(value) {
2726
2744
  validateContextTimes(value);
2727
2745
  }
2728
2746
  function contextShape(value) {
2729
- return record4(value) && value.schema_version === CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION && hasHostInvocationContextKeys(value, ["request_ref", "bound_at", "working_directory", "turn_generation_ref"]);
2747
+ return record2(value) && value.schema_version === CLAUDE_INVOCATION_CONTEXT_SCHEMA_VERSION && hasHostInvocationContextKeys(value, ["working_directory", "turn_generation_ref"]);
2730
2748
  }
2731
2749
  function validateIdentity(value) {
2732
2750
  if (!validIdentity(value)) throw new ClaudeHostError("storage", "Claude Host invocation identity is malformed.");
2733
2751
  }
2734
2752
  function validateContextReferences(value) {
2735
- if (!safeRef3(value.prompt_ref) || !optionalSafeRef(value.request_ref) || !optionalSafeRef(value.turn_generation_ref)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2753
+ if (!safeRef2(value.prompt_ref) || !optionalSafeRef(value.turn_generation_ref)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2736
2754
  }
2737
2755
  function validateContextTimes(value) {
2738
- if (!timestamp2(value.expires_at) || !optionalTimestamp(value.bound_at) || !optionalDirectory(value.working_directory)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2739
- if (typeof value.bound_at === "string" && Date.parse(value.bound_at) > Date.parse(String(value.expires_at))) throw new ClaudeHostError("storage", `Claude Host invocation context has invalid binding time (${refusalFact("bound_at", value.bound_at, `at or before ${String(value.expires_at)}`)}).`);
2756
+ if (!timestamp(value.expires_at) || !optionalDirectory(value.working_directory)) throw new ClaudeHostError("storage", "Claude Host invocation context is malformed.");
2740
2757
  }
2741
2758
  function validIdentity(value) {
2742
- return record4(value) && hasHostInvocationIdentityKeys(value) && [value.host_session_ref, value.host_turn_ref, value.host_source_ref].every(safeRef3);
2759
+ return record2(value) && hasHostInvocationIdentityKeys(value) && [value.host_session_ref, value.host_turn_ref, value.host_source_ref].every(safeRef2);
2743
2760
  }
2744
2761
  function optionalSafeRef(value) {
2745
- return value === void 0 || safeRef3(value);
2746
- }
2747
- function optionalTimestamp(value) {
2748
- return value === void 0 || timestamp2(value);
2762
+ return value === void 0 || safeRef2(value);
2749
2763
  }
2750
2764
  function optionalDirectory(value) {
2751
2765
  return value === void 0 || typeof value === "string" && value.startsWith("/") && !value.includes("\0") && value.length <= 4096;
2752
2766
  }
2753
- function safeRef3(value) {
2767
+ function safeRef2(value) {
2754
2768
  return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
2755
2769
  }
2756
- function timestamp2(value) {
2770
+ function timestamp(value) {
2757
2771
  return typeof value === "string" && value.length <= 64 && Number.isFinite(Date.parse(value));
2758
2772
  }
2759
- function record4(value) {
2773
+ function record2(value) {
2760
2774
  return value !== null && typeof value === "object" && !Array.isArray(value);
2761
2775
  }
2762
2776
 
2763
2777
  // host/claude/src/claude-outbox.ts
2764
- var SAFE_REF3 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
2778
+ var SAFE_REF2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
2765
2779
  var CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS = 4096;
2766
2780
  var CLAUDE_TRANSCRIPT_SUBAGENT_NAME_MAX_CHARACTERS = 256;
2767
2781
  var ClaudeOutboxStore = class {
@@ -2775,16 +2789,16 @@ var ClaudeOutboxStore = class {
2775
2789
  constructor(directory) {
2776
2790
  if (!directory.startsWith("/")) throw new ClaudeHostError("configuration", "Claude outbox directory must be absolute.");
2777
2791
  this.directory = directory;
2778
- this.envelopes = join3(directory, "envelopes");
2779
- this.sessions = join3(directory, "sessions");
2780
- this.registrations = join3(directory, "transcript-registrations");
2781
- this.cursors = join3(directory, "transcript-cursors");
2782
- this.activeContexts = join3(directory, "active-contexts");
2783
- this.turnSelections = join3(directory, "turn-selections");
2792
+ this.envelopes = join2(directory, "envelopes");
2793
+ this.sessions = join2(directory, "sessions");
2794
+ this.registrations = join2(directory, "transcript-registrations");
2795
+ this.cursors = join2(directory, "transcript-cursors");
2796
+ this.activeContexts = join2(directory, "active-contexts");
2797
+ this.turnSelections = join2(directory, "turn-selections");
2784
2798
  }
2785
2799
  async prepare() {
2786
2800
  for (const path of [this.envelopes, this.sessions, this.registrations, this.cursors, this.activeContexts, this.turnSelections]) {
2787
- await mkdir3(path, { recursive: true, mode: 448 });
2801
+ await mkdir2(path, { recursive: true, mode: 448 });
2788
2802
  }
2789
2803
  }
2790
2804
  async activateInvocationContext(identity, promptRef, expiresAt, workingDirectory, turnGenerationRef) {
@@ -2819,10 +2833,10 @@ var ClaudeOutboxStore = class {
2819
2833
  * a missing one. Every other caller keeps the fail-closed read above.
2820
2834
  */
2821
2835
  async readRawInvocationContext(hostSourceRef, hostSessionRef) {
2822
- if (!SAFE_REF3.test(hostSourceRef) || !SAFE_REF3.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host context identity or clock is invalid.");
2836
+ if (!SAFE_REF2.test(hostSourceRef) || !SAFE_REF2.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host context identity or clock is invalid.");
2823
2837
  await this.prepare();
2824
2838
  const context = await readLatestSessionProjection(this.activeContextPath({ host_source_ref: hostSourceRef, host_session_ref: hostSessionRef }), hostSourceRef, hostSessionRef, readProjectionFile);
2825
- return invocationTurnSelectionMatches(context, await this.readInvocationTurnSelection(hostSourceRef, hostSessionRef)) ? context : null;
2839
+ return context;
2826
2840
  }
2827
2841
  async listInvocationContexts(now = /* @__PURE__ */ new Date()) {
2828
2842
  await this.prepare();
@@ -2830,9 +2844,9 @@ var ClaudeOutboxStore = class {
2830
2844
  for (const name of await readdir(this.activeContexts)) {
2831
2845
  if (!name.endsWith(".json")) continue;
2832
2846
  try {
2833
- const value = JSON.parse(await readFile2(join3(this.activeContexts, name), "utf8"));
2847
+ const value = JSON.parse(await readFile(join2(this.activeContexts, name), "utf8"));
2834
2848
  validateClaudeHostInvocationContext(value);
2835
- if (Date.parse(value.expires_at) > now.getTime() && invocationTurnSelectionMatches(value, await this.readInvocationTurnSelection(value.identity.host_source_ref, value.identity.host_session_ref))) contexts.push(value);
2849
+ if (Date.parse(value.expires_at) > now.getTime()) contexts.push(value);
2836
2850
  } catch {
2837
2851
  }
2838
2852
  }
@@ -2850,7 +2864,7 @@ var ClaudeOutboxStore = class {
2850
2864
  for (const name of await readdir(this.envelopes)) {
2851
2865
  if (!name.endsWith(".json")) continue;
2852
2866
  try {
2853
- const value = JSON.parse(await readFile2(join3(this.envelopes, name), "utf8"));
2867
+ const value = JSON.parse(await readFile(join2(this.envelopes, name), "utf8"));
2854
2868
  validateEnvelope(value);
2855
2869
  entries.push(value);
2856
2870
  } catch {
@@ -2871,13 +2885,13 @@ var ClaudeOutboxStore = class {
2871
2885
  async saveSession(snapshot) {
2872
2886
  validateSession(snapshot);
2873
2887
  await this.prepare();
2874
- await durableAtomicReplace(join3(this.sessions, `${cealRequestSha256({ source: snapshot.host_source_ref, session: snapshot.host_session_ref })}.json`), JSON.stringify(snapshot));
2888
+ await durableAtomicReplace(join2(this.sessions, `${cealRequestSha256({ source: snapshot.host_source_ref, session: snapshot.host_session_ref })}.json`), JSON.stringify(snapshot));
2875
2889
  }
2876
2890
  async readSession(sourceRef, sessionRef) {
2877
- if (!SAFE_REF3.test(sourceRef) || !SAFE_REF3.test(sessionRef)) throw new ClaudeHostError("invalid_input", "Claude session identity is invalid.");
2891
+ if (!SAFE_REF2.test(sourceRef) || !SAFE_REF2.test(sessionRef)) throw new ClaudeHostError("invalid_input", "Claude session identity is invalid.");
2878
2892
  await this.prepare();
2879
2893
  try {
2880
- const value = JSON.parse(await readFile2(join3(this.sessions, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`), "utf8"));
2894
+ const value = JSON.parse(await readFile(join2(this.sessions, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`), "utf8"));
2881
2895
  validateSession(value);
2882
2896
  return value;
2883
2897
  } catch (error) {
@@ -2912,7 +2926,7 @@ var ClaudeOutboxStore = class {
2912
2926
  for (const name of await readdir(this.registrations)) {
2913
2927
  if (!name.endsWith(".json")) continue;
2914
2928
  try {
2915
- const value = JSON.parse(await readFile2(join3(this.registrations, name), "utf8"));
2929
+ const value = JSON.parse(await readFile(join2(this.registrations, name), "utf8"));
2916
2930
  validateRegistration(value);
2917
2931
  registrations.push(value);
2918
2932
  } catch {
@@ -2921,10 +2935,10 @@ var ClaudeOutboxStore = class {
2921
2935
  return registrations.sort((left, right) => left.registered_at.localeCompare(right.registered_at));
2922
2936
  }
2923
2937
  async readInvocationTurnSelection(hostSourceRef, hostSessionRef) {
2924
- if (!SAFE_REF3.test(hostSourceRef) || !SAFE_REF3.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host turn selection identity is invalid.");
2938
+ if (!SAFE_REF2.test(hostSourceRef) || !SAFE_REF2.test(hostSessionRef)) throw new ClaudeHostError("invalid_input", "Claude Host turn selection identity is invalid.");
2925
2939
  await this.prepare();
2926
2940
  try {
2927
- const value = JSON.parse(await readFile2(this.turnSelectionPath(hostSourceRef, hostSessionRef), "utf8"));
2941
+ const value = JSON.parse(await readFile(this.turnSelectionPath(hostSourceRef, hostSessionRef), "utf8"));
2928
2942
  if (!isHostInvocationTurnSelection(value) || value.host_source_ref !== hostSourceRef || value.host_session_ref !== hostSessionRef) throw new ClaudeHostError("storage", "Claude Host turn selection is malformed.");
2929
2943
  return value;
2930
2944
  } catch (error) {
@@ -2936,7 +2950,7 @@ var ClaudeOutboxStore = class {
2936
2950
  validateRegistration(registration);
2937
2951
  await this.prepare();
2938
2952
  try {
2939
- const value = JSON.parse(await readFile2(this.cursorPath(registration.host_source_ref, registration.host_session_ref), "utf8"));
2953
+ const value = JSON.parse(await readFile(this.cursorPath(registration.host_source_ref, registration.host_session_ref), "utf8"));
2940
2954
  validateCursor(value);
2941
2955
  return value;
2942
2956
  } catch (error) {
@@ -2950,20 +2964,20 @@ var ClaudeOutboxStore = class {
2950
2964
  await durableAtomicReplace(this.cursorPath(cursor.host_source_ref, cursor.host_session_ref), JSON.stringify(cursor));
2951
2965
  }
2952
2966
  envelopePath(ref) {
2953
- return join3(this.envelopes, `${cealRequestSha256(ref)}.json`);
2967
+ return join2(this.envelopes, `${cealRequestSha256(ref)}.json`);
2954
2968
  }
2955
2969
  registrationPath(sourceRef, sessionRef) {
2956
- return join3(this.registrations, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
2970
+ return join2(this.registrations, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
2957
2971
  }
2958
2972
  turnSelectionPath(sourceRef, sessionRef) {
2959
- return join3(this.turnSelections, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
2973
+ return join2(this.turnSelections, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
2960
2974
  }
2961
2975
  cursorPath(sourceRef, sessionRef) {
2962
- return join3(this.cursors, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
2976
+ return join2(this.cursors, `${cealRequestSha256({ source: sourceRef, session: sessionRef })}.json`);
2963
2977
  }
2964
2978
  async readRegistrationFile(path) {
2965
2979
  try {
2966
- const value = JSON.parse(await readFile2(path, "utf8"));
2980
+ const value = JSON.parse(await readFile(path, "utf8"));
2967
2981
  validateRegistration(value);
2968
2982
  return value;
2969
2983
  } catch (error) {
@@ -2979,15 +2993,15 @@ var ClaudeOutboxStore = class {
2979
2993
  }
2980
2994
  }
2981
2995
  activeContextPath(identity) {
2982
- return join3(this.activeContexts, `${invocationContextKey(identity)}.json`);
2996
+ return join2(this.activeContexts, `${invocationContextKey(identity)}.json`);
2983
2997
  }
2984
2998
  };
2985
2999
  function validateInvocationContextReadArguments(hostSourceRef, hostSessionRef, now) {
2986
- if (!SAFE_REF3.test(hostSourceRef) || !SAFE_REF3.test(hostSessionRef) || !(now instanceof Date) || !Number.isFinite(now.getTime())) throw new ClaudeHostError("invalid_input", "Claude Host context identity or clock is invalid.");
3000
+ if (!SAFE_REF2.test(hostSourceRef) || !SAFE_REF2.test(hostSessionRef) || !(now instanceof Date) || !Number.isFinite(now.getTime())) throw new ClaudeHostError("invalid_input", "Claude Host context identity or clock is invalid.");
2987
3001
  }
2988
3002
  async function readProjectionFile(file) {
2989
3003
  try {
2990
- const context = JSON.parse(await readFile2(file, "utf8"));
3004
+ const context = JSON.parse(await readFile(file, "utf8"));
2991
3005
  validateClaudeHostInvocationContext(context);
2992
3006
  return context;
2993
3007
  } catch (error) {
@@ -2996,65 +3010,63 @@ async function readProjectionFile(file) {
2996
3010
  throw new ClaudeHostError("storage", "Unable to read the Claude Host invocation context.");
2997
3011
  }
2998
3012
  }
2999
- function safeRef4(value) {
3000
- return typeof value === "string" && SAFE_REF3.test(value);
3013
+ function safeRef3(value) {
3014
+ return typeof value === "string" && SAFE_REF2.test(value);
3001
3015
  }
3002
- function timestamp3(value) {
3016
+ function timestamp2(value) {
3003
3017
  return typeof value === "string" && Number.isFinite(Date.parse(value));
3004
3018
  }
3005
- function record5(value) {
3019
+ function record3(value) {
3006
3020
  return value !== null && typeof value === "object" && !Array.isArray(value);
3007
3021
  }
3008
3022
  function code(error) {
3009
- return record5(error) && typeof error.code === "string" ? error.code : null;
3023
+ return record3(error) && typeof error.code === "string" ? error.code : null;
3010
3024
  }
3011
3025
  function validateEnvelope(value) {
3012
- if (!record5(value)) throw new ClaudeHostError("storage", "Claude outbox entry is malformed.");
3013
- if (!sameKeys(value, ["schema_version", "identity", "host_input_ref", "input_kind", "prompt_ref", "observations", "origin_input", "submission_ref", "created_at", "expires_at"], ["request_ref"])) {
3026
+ if (!record3(value)) throw new ClaudeHostError("storage", "Claude outbox entry is malformed.");
3027
+ if (!sameKeys(value, ["schema_version", "identity", "host_input_ref", "prompt_ref", "observations", "origin_input", "submission_ref", "created_at", "expires_at"])) {
3014
3028
  throw new ClaudeHostError("storage", `Claude outbox entry key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the outbox entry key set")}).`);
3015
3029
  }
3016
3030
  if (value.schema_version !== CLAUDE_OUTBOX_SCHEMA_VERSION) throw new ClaudeHostError("storage", `Claude outbox entry is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CLAUDE_OUTBOX_SCHEMA_VERSION}`)}).`);
3017
3031
  validateEnvelopeIdentity(value);
3018
3032
  validateEnvelopeObservations(value.observations);
3019
3033
  if (typeof value.origin_input !== "boolean") throw new ClaudeHostError("storage", "Claude outbox origin disposition is malformed.");
3020
- if (!timestamp3(value.created_at) || !timestamp3(value.expires_at)) throw new ClaudeHostError("storage", "Claude outbox timestamps are malformed.");
3034
+ if (!timestamp2(value.created_at) || !timestamp2(value.expires_at)) throw new ClaudeHostError("storage", "Claude outbox timestamps are malformed.");
3021
3035
  }
3022
3036
  function validateEnvelopeIdentity(value) {
3023
3037
  validateIdentity2(value.identity);
3024
- if (!safeRef4(value.host_input_ref)) throw new ClaudeHostError("storage", "Claude outbox input reference is malformed.");
3025
- if (!isCealHostInputKind(value.input_kind)) throw new ClaudeHostError("storage", "Claude outbox input provenance is malformed.");
3026
- if (!safeRef4(value.prompt_ref) || !safeRef4(value.submission_ref)) throw new ClaudeHostError("storage", "Claude outbox references are malformed.");
3027
- if (value.request_ref !== void 0 && !safeRef4(value.request_ref)) throw new ClaudeHostError("storage", "Claude outbox Request reference is malformed.");
3038
+ if (!safeRef3(value.host_input_ref)) throw new ClaudeHostError("storage", "Claude outbox input reference is malformed.");
3039
+ if (!safeRef3(value.prompt_ref) || !safeRef3(value.submission_ref)) throw new ClaudeHostError("storage", "Claude outbox references are malformed.");
3028
3040
  }
3029
3041
  function validateEnvelopeObservations(value) {
3030
3042
  if (!Array.isArray(value)) throw new ClaudeHostError("storage", "Claude outbox observations are malformed.");
3031
3043
  try {
3032
- for (const observation2 of value) validateCealHostObservationDraft(observation2);
3044
+ for (const observation of value) validateCealHostObservationDraft(observation);
3033
3045
  } catch {
3034
3046
  throw new ClaudeHostError("storage", "Claude outbox observations are malformed.");
3035
3047
  }
3036
3048
  }
3037
3049
  function validateSession(value) {
3038
- if (!record5(value)) throw new ClaudeHostError("storage", "Claude session snapshot is malformed.");
3050
+ if (!record3(value)) throw new ClaudeHostError("storage", "Claude session snapshot is malformed.");
3039
3051
  if (!sameKeys(value, ["schema_version", "host_session_ref", "host_source_ref", "observed_at", "value", "expires_at"])) {
3040
3052
  throw new ClaudeHostError("storage", `Claude session snapshot key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the session snapshot key set")}).`);
3041
3053
  }
3042
3054
  if (value.schema_version !== CLAUDE_SESSION_SCHEMA_VERSION) throw new ClaudeHostError("storage", `Claude session snapshot is malformed (${refusalFact("schema_version", value.schema_version, `expected ${CLAUDE_SESSION_SCHEMA_VERSION}`)}).`);
3043
- if (!safeRef4(value.host_session_ref) || !safeRef4(value.host_source_ref) || !timestamp3(value.observed_at) || !timestamp3(value.expires_at)) {
3055
+ if (!safeRef3(value.host_session_ref) || !safeRef3(value.host_source_ref) || !timestamp2(value.observed_at) || !timestamp2(value.expires_at)) {
3044
3056
  throw new ClaudeHostError("storage", "Claude session snapshot fields are malformed.");
3045
3057
  }
3046
3058
  }
3047
3059
  function validateRegistration(value) {
3048
- if (!record5(value)) throw new ClaudeHostError("storage", "Claude transcript registration is malformed.");
3060
+ if (!record3(value)) throw new ClaudeHostError("storage", "Claude transcript registration is malformed.");
3049
3061
  if (!sameKeys(value, ["schema_version", "host_source_ref", "host_session_ref", "claude_session_id", "transcript_path", "registered_at", "expires_at"], ["cwd"])) {
3050
3062
  throw new ClaudeHostError("storage", `Claude transcript registration key set is malformed (${refusalFact("keys", Object.keys(value), "exactly the registration key set")}).`);
3051
3063
  }
3052
3064
  if (value.schema_version !== CLAUDE_TRANSCRIPT_REGISTRATION_SCHEMA_VERSION) throw new ClaudeHostError("storage", "Claude transcript registration is malformed.");
3053
- if (![value.host_source_ref, value.host_session_ref, value.claude_session_id].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) {
3065
+ if (![value.host_source_ref, value.host_session_ref, value.claude_session_id].every((candidate) => typeof candidate === "string" && SAFE_REF2.test(candidate))) {
3054
3066
  throw new ClaudeHostError("storage", "Claude transcript registration references are malformed.");
3055
3067
  }
3056
3068
  validateRegistrationPaths(value.cwd, value.transcript_path);
3057
- if (!timestamp3(value.registered_at) || !timestamp3(value.expires_at)) throw new ClaudeHostError("storage", "Claude transcript registration timestamps are malformed.");
3069
+ if (!timestamp2(value.registered_at) || !timestamp2(value.expires_at)) throw new ClaudeHostError("storage", "Claude transcript registration timestamps are malformed.");
3058
3070
  }
3059
3071
  function validateRegistrationPaths(cwd, transcriptPath) {
3060
3072
  if (cwd !== void 0 && (typeof cwd !== "string" || !isAbsolute(cwd) || cwd.length > CLAUDE_TRANSCRIPT_PATH_MAX_CHARACTERS || cwd.includes("\0"))) {
@@ -3065,12 +3077,12 @@ function validateRegistrationPaths(cwd, transcriptPath) {
3065
3077
  }
3066
3078
  }
3067
3079
  function validateCursor(value) {
3068
- if (!record5(value)) throw new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
3080
+ if (!record3(value)) throw new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
3069
3081
  if (!sameKeys(value, ["schema_version", "host_source_ref", "host_session_ref", "transcript_path", "offset", "anchor_start", "anchor_sha256", "subagents", "status", "updated_at"], ["device", "inode", "open_turn", "unattributed", "session_observed", "reason"])) {
3070
3082
  throw new ClaudeHostError("storage", "Claude transcript cursor key set is malformed.");
3071
3083
  }
3072
3084
  if (value.schema_version !== CLAUDE_TRANSCRIPT_CURSOR_SCHEMA_VERSION) throw new ClaudeHostError("storage", "Claude transcript cursor is malformed.");
3073
- if (![value.host_source_ref, value.host_session_ref].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) throw new ClaudeHostError("storage", "Claude transcript cursor references are malformed.");
3085
+ if (![value.host_source_ref, value.host_session_ref].every((candidate) => typeof candidate === "string" && SAFE_REF2.test(candidate))) throw new ClaudeHostError("storage", "Claude transcript cursor references are malformed.");
3074
3086
  if (typeof value.transcript_path !== "string" || value.transcript_path.length === 0) throw new ClaudeHostError("storage", "Claude transcript cursor path is malformed.");
3075
3087
  validateCursorPosition(value);
3076
3088
  validateCursorTurn(value.open_turn, value.unattributed, value.session_observed);
@@ -3082,7 +3094,7 @@ function validateCursorPosition(value) {
3082
3094
  if (value.device !== void 0 && typeof value.device !== "string") throw new ClaudeHostError("storage", "Claude transcript cursor device is malformed.");
3083
3095
  if (value.inode !== void 0 && typeof value.inode !== "string") throw new ClaudeHostError("storage", "Claude transcript cursor inode is malformed.");
3084
3096
  if (value.device === void 0 && value.offset !== 0) throw new ClaudeHostError("storage", "Claude transcript cursor is unpinned past its start.");
3085
- if (value.status !== "active" && value.status !== "quarantined" || !timestamp3(value.updated_at)) throw new ClaudeHostError("storage", "Claude transcript cursor status is malformed.");
3097
+ if (value.status !== "active" && value.status !== "quarantined" || !timestamp2(value.updated_at)) throw new ClaudeHostError("storage", "Claude transcript cursor status is malformed.");
3086
3098
  }
3087
3099
  function validateCursorTurn(openTurn, unattributed, sessionObserved) {
3088
3100
  if (openTurn !== void 0 && !validOpenTurn(openTurn)) throw new ClaudeHostError("storage", "Claude transcript turn cursor is malformed.");
@@ -3090,13 +3102,13 @@ function validateCursorTurn(openTurn, unattributed, sessionObserved) {
3090
3102
  if (sessionObserved !== void 0 && typeof sessionObserved !== "boolean") throw new ClaudeHostError("storage", "Claude transcript cursor session marker is malformed.");
3091
3103
  }
3092
3104
  function validateSubagentCursors(value) {
3093
- if (!record5(value)) throw new ClaudeHostError("storage", "Claude transcript subagent cursors are malformed.");
3105
+ if (!record3(value)) throw new ClaudeHostError("storage", "Claude transcript subagent cursors are malformed.");
3094
3106
  for (const [name, entry] of Object.entries(value)) validateSubagentEntry(name, entry);
3095
3107
  }
3096
3108
  function validateSubagentEntry(name, entry) {
3097
3109
  if (name.length === 0 || name.length > CLAUDE_TRANSCRIPT_SUBAGENT_NAME_MAX_CHARACTERS || name.includes("/")) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3098
3110
  if (name.includes("\0")) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3099
- if (!record5(entry) || !sameKeys(entry, ["offset", "anchor_start", "anchor_sha256"], ["device", "inode"]) || !nonNegativeInteger(entry.offset)) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3111
+ if (!record3(entry) || !sameKeys(entry, ["offset", "anchor_start", "anchor_sha256"], ["device", "inode"]) || !nonNegativeInteger(entry.offset)) throw new ClaudeHostError("storage", "Claude transcript subagent cursor is malformed.");
3100
3112
  validateSubagentIdentity(entry);
3101
3113
  }
3102
3114
  function validateSubagentIdentity(entry) {
@@ -3112,9 +3124,9 @@ function validCursorPosition(value) {
3112
3124
  return nonNegativeInteger(value.offset) && nonNegativeInteger(value.anchor_start) && typeof value.anchor_sha256 === "string" && /^[a-f0-9]{64}$/u.test(value.anchor_sha256);
3113
3125
  }
3114
3126
  function validOpenTurn(value) {
3115
- if (!record5(value) || !sameKeys(value, ["turn_key", "user_uuid", "prompt_ref", "host_input_ref", "started_at", "last_observed_at", "message_ids", "counts"], ["last_terminal_digest"])) return false;
3116
- if (![value.turn_key, value.user_uuid, value.prompt_ref, value.host_input_ref].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) return false;
3117
- if (!timestamp3(value.started_at) || !timestamp3(value.last_observed_at)) return false;
3127
+ if (!record3(value) || !sameKeys(value, ["turn_key", "user_uuid", "prompt_ref", "host_input_ref", "started_at", "last_observed_at", "message_ids", "counts"], ["last_terminal_digest"])) return false;
3128
+ if (![value.turn_key, value.user_uuid, value.prompt_ref, value.host_input_ref].every((candidate) => typeof candidate === "string" && SAFE_REF2.test(candidate))) return false;
3129
+ if (!timestamp2(value.started_at) || !timestamp2(value.last_observed_at)) return false;
3118
3130
  if (!validMessageIds(value.message_ids)) return false;
3119
3131
  if (!validTurnCounts(value.counts)) return false;
3120
3132
  return value.last_terminal_digest === void 0 || typeof value.last_terminal_digest === "string" && /^[a-f0-9]{64}$/u.test(value.last_terminal_digest);
@@ -3125,17 +3137,17 @@ function validMessageIds(value) {
3125
3137
  return new Set(value).size === value.length;
3126
3138
  }
3127
3139
  function validUnattributed(value) {
3128
- if (!record5(value) || !sameKeys(value, ["input", "cached_input", "cache_write_input", "output", "message_ids", "last_observed_at"])) return false;
3140
+ if (!record3(value) || !sameKeys(value, ["input", "cached_input", "cache_write_input", "output", "message_ids", "last_observed_at"])) return false;
3129
3141
  if (!validMessageIds(value.message_ids) || value.message_ids.length === 0) return false;
3130
- if (!timestamp3(value.last_observed_at)) return false;
3142
+ if (!timestamp2(value.last_observed_at)) return false;
3131
3143
  return validTurnCounts(value);
3132
3144
  }
3133
3145
  function validTurnCounts(value) {
3134
- if (!record5(value) || !sameKeys(value, ["input", "cached_input", "cache_write_input", "output"])) return false;
3146
+ if (!record3(value) || !sameKeys(value, ["input", "cached_input", "cache_write_input", "output"])) return false;
3135
3147
  return [value.input, value.cached_input, value.cache_write_input, value.output].every(nonNegativeInteger);
3136
3148
  }
3137
3149
  function validateIdentity2(value) {
3138
- if (!record5(value) || ![value.host_session_ref, value.host_turn_ref, value.host_source_ref].every((candidate) => typeof candidate === "string" && SAFE_REF3.test(candidate))) {
3150
+ if (!record3(value) || ![value.host_session_ref, value.host_turn_ref, value.host_source_ref].every((candidate) => typeof candidate === "string" && SAFE_REF2.test(candidate))) {
3139
3151
  throw new ClaudeHostError("storage", "Claude Host identity is malformed.");
3140
3152
  }
3141
3153
  }
@@ -3147,14 +3159,11 @@ function sameKeys(value, required, optional = []) {
3147
3159
  }
3148
3160
 
3149
3161
  // host/claude/src/claude-transcript-watcher.ts
3150
- import { O_NOFOLLOW as O_NOFOLLOW2, O_RDONLY as O_RDONLY3 } from "node:constants";
3151
- import { open as open3, readdir as readdir2 } from "node:fs/promises";
3152
- import { join as join4 } from "node:path";
3162
+ import { readdir as readdir2 } from "node:fs/promises";
3163
+ import { join as join3 } from "node:path";
3153
3164
  var TOKEN_OBSERVATION_SOURCE = "claude_transcript.message.usage";
3154
3165
  var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3155
3166
  store;
3156
- registerCurrentPrompt;
3157
- releaseCurrentPrompt;
3158
3167
  diagnostic;
3159
3168
  constructor(options) {
3160
3169
  super(options, {
@@ -3163,8 +3172,6 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3163
3172
  clock_failure: () => new ClaudeHostError("storage", "Claude watcher clock is invalid.")
3164
3173
  });
3165
3174
  this.store = new ClaudeOutboxStore(options.outbox_dir);
3166
- this.registerCurrentPrompt = options.register_current_prompt;
3167
- this.releaseCurrentPrompt = options.release_current_prompt;
3168
3175
  this.diagnostic = options.diagnostic ?? (() => void 0);
3169
3176
  }
3170
3177
  transcriptPathOf(registration) {
@@ -3178,7 +3185,6 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3178
3185
  const cursor = await this.preflight(registration, handle, size, device, inode);
3179
3186
  if (cursor === null) return { records: 0, enqueued: 0, quarantined: true };
3180
3187
  if (cursor.status === "quarantined") {
3181
- if (cursor.reason !== "transcript_malformed") await this.releaseStoredPromptBestEffort(registration.host_session_ref, cursor);
3182
3188
  return { records: 0, enqueued: 0, quarantined: true };
3183
3189
  }
3184
3190
  const draft = mutableCursor(cursor);
@@ -3193,7 +3199,6 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3193
3199
  return { records: 0, enqueued: 0, quarantined: true };
3194
3200
  }
3195
3201
  async retireMissing(registration) {
3196
- await this.releaseStoredPromptBestEffort(registration.host_session_ref, await this.store.readTranscriptCursor(registration).catch(() => null));
3197
3202
  return this.retireMissingTranscript(registration.transcript_path, "Claude", () => this.store.retireTranscriptRegistration(registration));
3198
3203
  }
3199
3204
  initialCursor(registration) {
@@ -3235,72 +3240,72 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3235
3240
  async consumeMainChunk(registration, complete, draft) {
3236
3241
  for (const line of complete.toString("utf8").split("\n").filter((value) => value.length > 0)) {
3237
3242
  draft.consumed += 1;
3238
- const record6 = parseTranscriptLine(line);
3239
- if (record6 === null) throw new ClaudeHostError("payload_rejected", "Claude transcript line is not valid JSON.", "malformed_line");
3240
- if (isUserTurnLine(record6)) await this.openTurn(registration, draft, record6);
3241
- else if (isAssistantUsageLine(record6)) this.accumulateUsage(draft, toUsage(record6.uuid, record6.timestamp, record6.message.usage));
3242
- else if (isAssistantLine(record6) && record6.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude transcript usage is malformed.");
3243
- if (isAssistantTerminalLine(record6)) await this.releaseOpenTurn(registration, draft);
3243
+ const record4 = parseTranscriptLine(line);
3244
+ if (record4 === null) throw new ClaudeHostError("payload_rejected", "Claude transcript line is not valid JSON.", "malformed_line");
3245
+ if (isUserTurnLine(record4)) await this.openTurn(registration, draft, record4);
3246
+ else if (isAssistantUsageLine(record4)) this.accumulateUsage(draft, toUsage(record4.uuid, record4.timestamp, record4.message.usage));
3247
+ else if (isAssistantLine(record4) && record4.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude transcript usage is malformed.");
3248
+ else {
3249
+ const queued = decodeClaudeQueuedPromptRow(record4, registration.claude_session_id);
3250
+ if (queued !== null) await this.openQueuedTurn(registration, draft, queued);
3251
+ }
3244
3252
  }
3245
3253
  }
3246
- async openTurn(registration, draft, record6) {
3247
- await this.closeTurn(registration, draft, record6.timestamp);
3254
+ async openTurn(registration, draft, record4) {
3255
+ await this.openPromptTurn(registration, draft, { uuid: record4.uuid, timestamp: record4.timestamp, content: record4.message.content });
3256
+ }
3257
+ async openQueuedTurn(registration, draft, queued) {
3258
+ await this.openPromptTurn(registration, draft, { uuid: queued.turnId, timestamp: queued.timestamp, content: queued.content });
3259
+ }
3260
+ async openPromptTurn(registration, draft, prompt) {
3261
+ await this.closeTurn(registration, draft, prompt.timestamp);
3248
3262
  const sessionRef = registration.host_session_ref;
3249
- const turnRef = claudeLocalTurnRef(registration.claude_session_id, record6.uuid);
3250
- const inputRef = claudeLocalInputRef(registration.claude_session_id, record6.uuid);
3251
- const promptRef = hostProducerPromptRef(this.sha(record6.message.content));
3263
+ const turnRef = claudeLocalTurnRef(registration.claude_session_id, prompt.uuid);
3264
+ const inputRef = claudeLocalInputRef(registration.claude_session_id, prompt.uuid);
3265
+ const promptRef = hostProducerPromptRef(this.sha(prompt.content));
3252
3266
  const identity = { host_source_ref: registration.host_source_ref, host_session_ref: sessionRef, host_turn_ref: turnRef };
3253
3267
  const observations = [
3254
- { observation_ref: observationRef("transcript_user_text", inputRef, record6.message.content), observation_kind: "prompt", observed_at: record6.timestamp, value: { body_sha256: this.sha(record6.message.content) } },
3255
- { observation_ref: observationRef("transcript_turn_started", turnRef, record6.uuid), observation_kind: "turn", observed_at: record6.timestamp, value: { status: "started", host_session_id_sha256: this.sha(registration.claude_session_id), host_turn_ref: turnRef } }
3268
+ { observation_ref: observationRef(registration.host_source_ref, "transcript_user_text", inputRef, prompt.content), observation_kind: "prompt", observed_at: prompt.timestamp, value: { body_sha256: this.sha(prompt.content) } },
3269
+ { observation_ref: observationRef(registration.host_source_ref, "transcript_turn_started", turnRef, prompt.uuid), observation_kind: "turn", observed_at: prompt.timestamp, value: { status: "started", host_session_id_sha256: this.sha(registration.claude_session_id), host_turn_ref: turnRef } }
3256
3270
  ];
3257
3271
  if (!draft.session_observed) {
3258
3272
  const session = await this.store.readSession(registration.host_source_ref, sessionRef);
3259
- if (session !== null) observations.push({ observation_ref: observationRef("transcript_session", sessionRef, session.value), observation_kind: "session", observed_at: record6.timestamp, value: session.value });
3273
+ if (session !== null) observations.push({ observation_ref: observationRef(registration.host_source_ref, "transcript_session", sessionRef, session.value), observation_kind: "session", observed_at: prompt.timestamp, value: session.value });
3260
3274
  draft.session_observed = true;
3261
3275
  }
3262
- await this.persistOpenedTurnContext(registration, draft, record6, inputRef, turnRef, promptRef, identity, observations);
3276
+ await this.persistOpenedTurnContext(draft, { uuid: prompt.uuid, timestamp: prompt.timestamp, message: { content: prompt.content } }, inputRef, turnRef, promptRef, identity, observations);
3263
3277
  }
3264
- async persistOpenedTurnContext(registration, draft, record6, inputRef, turnRef, promptRef, identity, observations) {
3265
- const sessionRef = registration.host_session_ref;
3278
+ async persistOpenedTurnContext(draft, record4, inputRef, turnRef, promptRef, identity, observations) {
3266
3279
  const expiresAt = this.expire(this.outboxRetentionMs);
3267
- const custodyRegistered = await this.registerPrompt(sessionRef, turnRef, promptRef, record6.message.content, expiresAt);
3268
- if (custodyRegistered) this.diagnostic("watcher_turn_observed");
3269
- try {
3270
- await this.store.putEnvelope({
3271
- schema_version: "ceal.claude_observation_outbox.v1",
3272
- identity,
3273
- host_input_ref: inputRef,
3274
- input_kind: "human",
3275
- prompt_ref: promptRef,
3276
- observations,
3277
- origin_input: true,
3278
- submission_ref: submissionRef("input", turnRef, inputRef, promptRef),
3279
- created_at: record6.timestamp,
3280
- expires_at: expiresAt
3281
- });
3282
- draft.enqueued += 1;
3283
- await this.activateOpenedTurnContext(identity, promptRef, expiresAt);
3284
- this.setOpenedTurn(draft, record6, inputRef, turnRef, promptRef);
3285
- } catch (error) {
3286
- if (custodyRegistered) await this.releasePrompt(sessionRef, { turn_key: turnRef, prompt_ref: promptRef }).catch(() => void 0);
3287
- throw error;
3288
- }
3280
+ await this.store.putEnvelope({
3281
+ schema_version: "ceal.claude_observation_outbox.v1",
3282
+ identity,
3283
+ host_input_ref: inputRef,
3284
+ prompt_ref: promptRef,
3285
+ observations,
3286
+ origin_input: true,
3287
+ submission_ref: submissionRef("input", turnRef, inputRef, promptRef),
3288
+ created_at: record4.timestamp,
3289
+ expires_at: expiresAt
3290
+ });
3291
+ draft.enqueued += 1;
3292
+ await this.activateOpenedTurnContext(identity, promptRef, expiresAt);
3293
+ this.setOpenedTurn(draft, record4, inputRef, turnRef, promptRef);
3289
3294
  }
3290
3295
  async activateOpenedTurnContext(identity, promptRef, expiresAt) {
3291
3296
  const selection = await this.store.readInvocationTurnSelection(identity.host_source_ref, identity.host_session_ref);
3292
3297
  const turnGenerationRef = selection?.prompt_ref === promptRef ? selection.turn_generation_ref : void 0;
3293
3298
  await this.activateContext(identity, promptRef, turnGenerationRef, expiresAt);
3294
3299
  }
3295
- setOpenedTurn(draft, record6, inputRef, turnRef, promptRef) {
3300
+ setOpenedTurn(draft, record4, inputRef, turnRef, promptRef) {
3296
3301
  const folded = foldUnattributed(draft.unattributed);
3297
3302
  draft.open_turn = {
3298
3303
  turn_key: turnRef,
3299
- user_uuid: record6.uuid,
3304
+ user_uuid: record4.uuid,
3300
3305
  prompt_ref: promptRef,
3301
3306
  host_input_ref: inputRef,
3302
- started_at: record6.timestamp,
3303
- last_observed_at: folded?.last_observed_at ?? record6.timestamp,
3307
+ started_at: record4.timestamp,
3308
+ last_observed_at: folded?.last_observed_at ?? record4.timestamp,
3304
3309
  message_ids: folded?.message_ids ?? [],
3305
3310
  counts: folded?.counts ?? zeroCounts()
3306
3311
  };
@@ -3315,14 +3320,6 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3315
3320
  await this.putTerminalEnvelope(registration, turn, digest2, turn.message_ids.length === 0 ? at : void 0);
3316
3321
  draft.enqueued += 1;
3317
3322
  }
3318
- await this.releasePrompt(registration.host_session_ref, turn);
3319
- }
3320
- async releaseOpenTurn(registration, draft) {
3321
- if (draft.open_turn !== void 0) await this.releasePrompt(registration.host_session_ref, draft.open_turn);
3322
- }
3323
- async releasePrompt(sessionRef, turn) {
3324
- if (this.producerInstallationRef === void 0 || this.releaseCurrentPrompt === void 0) return;
3325
- await this.releaseCurrentPrompt({ producer_id: "claude", producer_installation_ref: this.producerInstallationRef, local_session_ref: sessionRef, local_turn_ref: turn.turn_key, prompt_ref: turn.prompt_ref });
3326
3323
  }
3327
3324
  accumulateUsage(draft, usage) {
3328
3325
  const turn = draft.open_turn;
@@ -3369,10 +3366,10 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3369
3366
  return consumed;
3370
3367
  }
3371
3368
  async consumeSubagentFile(draft, directory, name) {
3372
- const path = join4(directory, name);
3369
+ const path = join3(directory, name);
3373
3370
  let handle;
3374
3371
  try {
3375
- handle = await open3(path, O_RDONLY3 | O_NOFOLLOW2);
3372
+ handle = await openNoFollow(path);
3376
3373
  const { size, device, inode } = await ownedClaudeTranscriptFile(handle);
3377
3374
  const entry = draft.subagents[name] ?? { offset: 0, anchor_start: 0, anchor_sha256: this.sha(Buffer.alloc(0)) };
3378
3375
  const pinned = await this.preflightSubagent(entry, size, device, inode);
@@ -3400,10 +3397,10 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3400
3397
  let consumed = 0;
3401
3398
  for (const line of chunk.toString("utf8").split("\n").filter((value) => value.length > 0)) {
3402
3399
  consumed += 1;
3403
- const record6 = parseTranscriptLine(line);
3404
- if (record6 === null) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript line is not valid JSON.", "malformed_line");
3405
- if (isAssistantUsageLine(record6)) this.accumulateUsage(draft, toUsage(record6.uuid, record6.timestamp, record6.message.usage));
3406
- else if (isAssistantLine(record6) && record6.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript usage is malformed.");
3400
+ const record4 = parseTranscriptLine(line);
3401
+ if (record4 === null) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript line is not valid JSON.", "malformed_line");
3402
+ if (isAssistantUsageLine(record4)) this.accumulateUsage(draft, toUsage(record4.uuid, record4.timestamp, record4.message.usage));
3403
+ else if (isAssistantLine(record4) && record4.message.usage !== void 0) throw new ClaudeHostError("payload_rejected", "Claude subagent transcript usage is malformed.");
3407
3404
  }
3408
3405
  return consumed;
3409
3406
  }
@@ -3439,12 +3436,10 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3439
3436
  schema_version: "ceal.claude_observation_outbox.v1",
3440
3437
  identity,
3441
3438
  host_input_ref: turn.host_input_ref,
3442
- input_kind: "host_automatic",
3443
3439
  prompt_ref: turn.prompt_ref,
3444
3440
  observations: [
3445
- { observation_ref: observationRef("transcript_terminal", turn.turn_key, digest2), observation_kind: "turn", observed_at: observedAt, value: { status: "stopped", host_session_id_sha256: this.sha(registration.claude_session_id), host_turn_ref: turn.turn_key } },
3446
- { observation_ref: observationRef("transcript_token", turn.turn_key, digest2), observation_kind: "token", observed_at: observedAt, value: tokenValue },
3447
- { observation_ref: observationRef("transcript_cost", turn.turn_key, digest2), observation_kind: "cost", observed_at: observedAt, value: hostObservationNotRecorded("provider_cost_not_observed") }
3441
+ { observation_ref: observationRef(registration.host_source_ref, "transcript_terminal", turn.turn_key, digest2), observation_kind: "turn", observed_at: observedAt, value: { status: "stopped", host_session_id_sha256: this.sha(registration.claude_session_id), host_turn_ref: turn.turn_key } },
3442
+ { observation_ref: observationRef(registration.host_source_ref, "transcript_token", turn.turn_key, digest2), observation_kind: "token", observed_at: observedAt, value: tokenValue }
3448
3443
  ],
3449
3444
  origin_input: false,
3450
3445
  submission_ref: submissionRef("terminal", turn.turn_key, digest2, turn.prompt_ref),
@@ -3472,30 +3467,6 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3472
3467
  updated_at: this.stamp()
3473
3468
  });
3474
3469
  }
3475
- async registerPrompt(sessionRef, turnRef, promptRef, prompt, expiresAt) {
3476
- if (this.producerInstallationRef === void 0 || this.registerCurrentPrompt === void 0) return false;
3477
- const register = this.registerCurrentPrompt;
3478
- const installationRef = this.producerInstallationRef;
3479
- return registerHostCurrentPromptDiagnosed(
3480
- () => register({
3481
- producer_id: "claude",
3482
- producer_installation_ref: installationRef,
3483
- local_session_ref: sessionRef,
3484
- local_turn_ref: turnRef,
3485
- prompt_ref: promptRef,
3486
- current_prompt: prompt,
3487
- context_expires_at: expiresAt
3488
- }),
3489
- (event) => this.diagnostic(event)
3490
- );
3491
- }
3492
- async releaseStoredPromptBestEffort(sessionRef, cursor) {
3493
- await this.releaseOpenTurnBestEffort(sessionRef, cursor?.open_turn);
3494
- }
3495
- async releaseOpenTurnBestEffort(sessionRef, turn) {
3496
- if (turn === void 0) return;
3497
- await this.releasePrompt(sessionRef, turn).catch(() => void 0);
3498
- }
3499
3470
  async activateContext(identity, promptRef, turnGenerationRef, expiresAt) {
3500
3471
  try {
3501
3472
  await this.store.activateInvocationContext(identity, promptRef, expiresAt, void 0, turnGenerationRef);
@@ -3506,7 +3477,6 @@ var ClaudeTranscriptWatcher = class extends HostTranscriptWatcher {
3506
3477
  }
3507
3478
  }
3508
3479
  async quarantineStored(cursor, device, inode, reason) {
3509
- await this.releaseStoredPromptBestEffort(cursor.host_session_ref, cursor);
3510
3480
  await this.store.writeTranscriptCursor({ ...cursor, device, inode, status: "quarantined", reason, updated_at: this.stamp() });
3511
3481
  return null;
3512
3482
  }
@@ -3610,47 +3580,43 @@ function parseTranscriptLine(line) {
3610
3580
  return null;
3611
3581
  }
3612
3582
  }
3613
- function lineIdentity(record6) {
3614
- if (typeof record6.uuid !== "string" || record6.uuid.length === 0 || record6.uuid.length > 256) return null;
3615
- if (typeof record6.timestamp !== "string" || !Number.isFinite(Date.parse(record6.timestamp))) return null;
3616
- return { uuid: record6.uuid, timestamp: record6.timestamp };
3583
+ function lineIdentity(record4) {
3584
+ if (typeof record4.uuid !== "string" || record4.uuid.length === 0 || record4.uuid.length > 256) return null;
3585
+ if (typeof record4.timestamp !== "string" || !Number.isFinite(Date.parse(record4.timestamp))) return null;
3586
+ return { uuid: record4.uuid, timestamp: record4.timestamp };
3617
3587
  }
3618
- function isUserTurnLine(record6) {
3619
- if (!isTranscriptRecord(record6) || record6.type !== "user") return false;
3620
- if (!isTranscriptRecord(record6.message) || typeof record6.message.content !== "string") return false;
3621
- if (record6.isMeta === true && (!isTranscriptRecord(record6.origin) || typeof record6.origin.kind !== "string")) return false;
3622
- const identity = lineIdentity(record6);
3588
+ function isUserTurnLine(record4) {
3589
+ if (!isTranscriptRecord(record4) || record4.type !== "user") return false;
3590
+ if (!isTranscriptRecord(record4.message) || typeof record4.message.content !== "string") return false;
3591
+ if (record4.isMeta === true && (!isTranscriptRecord(record4.origin) || typeof record4.origin.kind !== "string")) return false;
3592
+ const identity = lineIdentity(record4);
3623
3593
  if (identity === null) throw new ClaudeHostError("payload_rejected", "Claude transcript user line is malformed.");
3624
3594
  return true;
3625
3595
  }
3626
- function isAssistantLine(record6) {
3627
- return isTranscriptRecord(record6) && record6.type === "assistant" && isTranscriptRecord(record6.message);
3596
+ function isAssistantLine(record4) {
3597
+ return isTranscriptRecord(record4) && record4.type === "assistant" && isTranscriptRecord(record4.message);
3628
3598
  }
3629
- function isAssistantTerminalLine(record6) {
3630
- return isAssistantLine(record6) && (record6.message.stop_reason === "end_turn" || record6.message.stop_reason === "stop_sequence");
3631
- }
3632
- function isAssistantUsageLine(record6) {
3633
- if (!isTranscriptRecord(record6)) return false;
3634
- if (!isAssistantLine(record6) || record6.message.usage === void 0) return false;
3635
- const identity = lineIdentity(record6);
3636
- if (identity === null || !isTranscriptRecord(record6.message.usage)) throw new ClaudeHostError("payload_rejected", "Claude transcript usage line is malformed.");
3599
+ function isAssistantUsageLine(record4) {
3600
+ if (!isTranscriptRecord(record4)) return false;
3601
+ if (!isAssistantLine(record4) || record4.message.usage === void 0) return false;
3602
+ const identity = lineIdentity(record4);
3603
+ if (identity === null || !isTranscriptRecord(record4.message.usage)) throw new ClaudeHostError("payload_rejected", "Claude transcript usage line is malformed.");
3637
3604
  return true;
3638
3605
  }
3639
3606
  function subagentsDirectory(transcriptPath) {
3640
3607
  const stem = transcriptPath.endsWith(".jsonl") ? transcriptPath.slice(0, -".jsonl".length) : transcriptPath;
3641
- return join4(stem, "subagents");
3608
+ return join3(stem, "subagents");
3642
3609
  }
3643
- function observationRef(event, identity, value) {
3644
- return cealHostObservationRef("claude", transcriptObjectSha256({ event, identity, value }));
3610
+ function observationRef(hostSourceRef, event, identity, value) {
3611
+ return cealHostObservationRef("claude", transcriptObjectSha256({ host_source_ref: hostSourceRef, event, identity, value }));
3645
3612
  }
3646
3613
  function submissionRef(kind, turn, digest2, prompt) {
3647
3614
  return `claude-submission:${transcriptObjectSha256({ kind, turn, digest: digest2, prompt })}`;
3648
3615
  }
3649
3616
  async function ownedClaudeTranscriptFile(handle) {
3650
3617
  const stats = await handle.stat();
3651
- const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
3652
3618
  if (!stats.isFile()) throw new ClaudeHostError("payload_rejected", "Claude transcript source is not a regular file.");
3653
- if (stats.uid !== uid) throw new ClaudeHostError("payload_rejected", "Claude transcript source is owned by another user.");
3619
+ if (process.platform !== "win32" && !isSelfOwned(stats.uid, process.platform, handle)) throw new ClaudeHostError("payload_rejected", "Claude transcript source is owned by another user.");
3654
3620
  return { size: stats.size, device: String(stats.dev), inode: String(stats.ino) };
3655
3621
  }
3656
3622
  function isNotDirectory(error) {
@@ -3676,23 +3642,41 @@ function isSymlinkTranscript(error) {
3676
3642
  }
3677
3643
 
3678
3644
  // host/claude/src/claude-adapter.ts
3679
- import { createHash as createHash4, randomUUID } from "node:crypto";
3680
- import { O_NOFOLLOW as O_NOFOLLOW3, O_RDONLY as O_RDONLY4 } from "node:constants";
3681
- import { open as open4, readdir as readdir3 } from "node:fs/promises";
3645
+ import { createHash as createHash5, randomUUID } from "node:crypto";
3646
+ import { readdir as readdir3 } from "node:fs/promises";
3647
+ import { constants as constants2 } from "node:fs";
3682
3648
  import { homedir } from "node:os";
3683
3649
  import { isAbsolute as isAbsolute2, join as join5, relative, resolve } from "node:path";
3650
+
3651
+ // host/claude/src/claude-runtime-paths.ts
3652
+ import { join as join4 } from "node:path";
3653
+ var CLAUDE_CONFIG_DIRECTORY_ENVIRONMENT = "CLAUDE_CONFIG_DIR";
3654
+ var CLAUDE_HOME_DEFAULT_DIRECTORY = ".claude";
3655
+ var CLAUDE_PROJECTS_DIRECTORY = "projects";
3656
+ function resolveClaudeConfigHome(environment, home) {
3657
+ return environment[CLAUDE_CONFIG_DIRECTORY_ENVIRONMENT] || join4(home, CLAUDE_HOME_DEFAULT_DIRECTORY);
3658
+ }
3659
+ function resolveClaudeProjectsRoot(environment, home) {
3660
+ return join4(resolveClaudeConfigHome(environment, home), CLAUDE_PROJECTS_DIRECTORY);
3661
+ }
3662
+
3663
+ // host/claude/src/claude-adapter.ts
3684
3664
  var ClaudeHookAdapter = class {
3685
3665
  store;
3686
3666
  now;
3687
3667
  optionsSourceRef;
3688
3668
  transcriptsRoot;
3689
3669
  randomUuid;
3670
+ platform;
3671
+ custody;
3690
3672
  constructor(options) {
3691
3673
  this.store = new ClaudeOutboxStore(options.outbox_dir);
3692
3674
  this.now = options.now ?? (() => /* @__PURE__ */ new Date());
3693
3675
  this.optionsSourceRef = options.host_source_ref ?? "claude-host:local";
3694
3676
  this.transcriptsRoot = options.claude_transcripts_root;
3695
3677
  this.randomUuid = options.random_uuid ?? randomUUID;
3678
+ this.platform = options.platform ?? process.platform;
3679
+ this.custody = options.custody ?? {};
3696
3680
  }
3697
3681
  async handle(event, raw) {
3698
3682
  let input;
@@ -3708,10 +3692,10 @@ var ClaudeHookAdapter = class {
3708
3692
  }
3709
3693
  }
3710
3694
  async registerSession(input) {
3711
- const defaultRoot = join5(homedir(), ".claude", "projects");
3712
- const transcriptPath = sourceBoundTranscriptPath(input.transcript_path, this.transcriptsRoot ?? defaultRoot);
3695
+ const defaultRoot = resolveClaudeProjectsRoot(process.env, homedir());
3696
+ const transcriptPath = sourceBoundTranscriptPath(input.transcript_path, this.transcriptsRoot ?? defaultRoot, this.platform);
3713
3697
  const hostSessionRef = claudeLocalSessionRef(input.session_id);
3714
- const observedAt = timestamp4(this.now);
3698
+ const observedAt = timestamp3(this.now);
3715
3699
  const expiresAt = new Date(Date.parse(observedAt) + CLAUDE_DEFAULT_RETENTION_MS).toISOString();
3716
3700
  const registration = {
3717
3701
  schema_version: "ceal.claude_transcript_registration.v1",
@@ -3725,7 +3709,7 @@ var ClaudeHookAdapter = class {
3725
3709
  };
3726
3710
  const existing = (await this.store.listTranscriptRegistrations()).find((candidate) => candidate.host_source_ref === registration.host_source_ref && candidate.host_session_ref === registration.host_session_ref);
3727
3711
  if (existing === void 0) {
3728
- await this.store.writeTranscriptCursor((await captureClaudeTranscriptBaseline(registration, this.now)).cursor);
3712
+ await this.store.writeTranscriptCursor((await captureClaudeTranscriptBaseline(registration, this.now, void 0, this.platform, this.custody)).cursor);
3729
3713
  }
3730
3714
  await this.store.registerTranscript(registration);
3731
3715
  const snapshot = {
@@ -3740,9 +3724,9 @@ var ClaudeHookAdapter = class {
3740
3724
  return { event: input.event, disposition: "ignored", reason: "transcript_registered", local_session_ref: hostSessionRef };
3741
3725
  }
3742
3726
  async registerCurrentTurn(input) {
3743
- const transcriptPath = sourceBoundTranscriptPath(input.transcript_path, this.transcriptsRoot ?? join5(homedir(), ".claude", "projects"));
3727
+ const transcriptPath = sourceBoundTranscriptPath(input.transcript_path, this.transcriptsRoot ?? resolveClaudeProjectsRoot(process.env, homedir()), this.platform);
3744
3728
  const hostSessionRef = claudeLocalSessionRef(input.session_id);
3745
- const observedAt = timestamp4(this.now);
3729
+ const observedAt = timestamp3(this.now);
3746
3730
  const expiresAt = new Date(Date.parse(observedAt) + CLAUDE_DEFAULT_RETENTION_MS).toISOString();
3747
3731
  const registration = {
3748
3732
  schema_version: "ceal.claude_transcript_registration.v1",
@@ -3754,13 +3738,13 @@ var ClaudeHookAdapter = class {
3754
3738
  registered_at: observedAt,
3755
3739
  expires_at: expiresAt
3756
3740
  };
3757
- const promptRef = hostProducerPromptRef(createHash4("sha256").update(input.prompt, "utf8").digest("hex"));
3741
+ const promptRef = hostProducerPromptRef(createHash5("sha256").update(input.prompt, "utf8").digest("hex"));
3758
3742
  const turnGenerationRef = this.randomUuid();
3759
- const selection = { schema_version: "ceal.host_invocation_turn_selection.v1", host_source_ref: this.optionsSourceRef, host_session_ref: hostSessionRef, prompt_ref: promptRef, turn_generation_ref: turnGenerationRef };
3760
- await this.store.writeInvocationTurnSelection(selection);
3743
+ const selection = { schema_version: HOST_INVOCATION_TURN_SELECTION_SCHEMA, host_source_ref: this.optionsSourceRef, host_session_ref: hostSessionRef, prompt_ref: promptRef, turn_generation_ref: turnGenerationRef };
3761
3744
  const existing = (await this.store.listTranscriptRegistrations()).find((candidate) => candidate.host_source_ref === registration.host_source_ref && candidate.host_session_ref === registration.host_session_ref);
3762
- const baseline = await captureClaudeTranscriptBaseline(registration, this.now, input.prompt);
3745
+ const baseline = await captureClaudeTranscriptBaseline(registration, this.now, input.prompt, this.platform, this.custody);
3763
3746
  const transcriptTurnRef = baseline.current_turn_ref;
3747
+ await this.store.writeInvocationTurnSelection(selection);
3764
3748
  if (existing === void 0) await this.store.writeTranscriptCursor(baseline.cursor);
3765
3749
  else await refreshCurrentTurnCursor(this.store, registration, baseline, promptRef, turnGenerationRef, transcriptTurnRef);
3766
3750
  await this.store.registerTranscript(registration);
@@ -3770,12 +3754,12 @@ var ClaudeHookAdapter = class {
3770
3754
  function sessionObservationValue(input) {
3771
3755
  return { status: "started", host_kind: "claude", source: input.source, model: input.model ?? "unavailable", decoder_evidence_version: "claude-code-2.1.258" };
3772
3756
  }
3773
- function sourceBoundTranscriptPath(path, transcriptsRoot) {
3757
+ function sourceBoundTranscriptPath(path, transcriptsRoot, platform = process.platform) {
3774
3758
  if (!isAbsolute2(path) || !isAbsolute2(transcriptsRoot)) throw new ClaudeHostError("configuration", "Claude transcript registration paths must be absolute.");
3775
3759
  const transcript = resolve(path);
3776
3760
  const root = resolve(transcriptsRoot);
3777
3761
  const child = relative(root, transcript);
3778
- if (child === "" || child === ".." || child.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute2(child)) {
3762
+ if (child === "" || child === ".." || child.startsWith(`..${platform === "win32" ? "\\" : "/"}`) || isAbsolute2(child)) {
3779
3763
  throw new ClaudeHostError("invalid_input", "Claude transcript path is outside the configured transcripts root.");
3780
3764
  }
3781
3765
  return transcript;
@@ -3793,19 +3777,19 @@ async function refreshCurrentTurnCursor(store, registration, baseline, promptRef
3793
3777
  host_turn_ref: transcriptTurnRef
3794
3778
  }, promptRef, turnGenerationRef);
3795
3779
  }
3796
- async function captureClaudeTranscriptBaseline(registration, now, currentPrompt) {
3780
+ async function captureClaudeTranscriptBaseline(registration, now, currentPrompt, platform = process.platform, custody = {}) {
3797
3781
  let handle;
3798
3782
  try {
3799
- handle = await open4(registration.transcript_path, O_RDONLY4 | O_NOFOLLOW3);
3783
+ handle = await openNoFollow(registration.transcript_path, constants2.O_RDONLY, void 0, platform, custody);
3800
3784
  } catch (error) {
3801
3785
  if (isNotFound(error)) return { cursor: initialCursor(registration, now) };
3802
3786
  throw error;
3803
3787
  }
3804
3788
  try {
3805
- const file = await inspectClaudeTranscript(handle);
3789
+ const file = await inspectClaudeTranscript(handle, platform);
3806
3790
  const current = currentPrompt === void 0 ? void 0 : await currentClaudeUserLine(handle, file.size, registration.claude_session_id, currentPrompt);
3807
3791
  const offset = current?.offset ?? file.size;
3808
- const cursor = await makeClaudeBaselineCursor(handle, registration, now, file, offset);
3792
+ const cursor = await makeClaudeBaselineCursor(handle, registration, now, file, offset, platform, custody);
3809
3793
  return {
3810
3794
  cursor,
3811
3795
  ...current?.user === void 0 ? {} : {
@@ -3818,21 +3802,55 @@ async function captureClaudeTranscriptBaseline(registration, now, currentPrompt)
3818
3802
  await handle?.close();
3819
3803
  }
3820
3804
  }
3821
- async function inspectClaudeTranscript(handle) {
3805
+ async function inspectClaudeTranscript(handle, platform = process.platform) {
3822
3806
  const stats = await handle.stat();
3823
- const uid = typeof process.geteuid === "function" ? process.geteuid() : stats.uid;
3824
3807
  if (!stats.isFile()) throw new ClaudeHostError("storage", "Claude transcript source is not a regular file.");
3825
- if (stats.uid !== uid) throw new ClaudeHostError("storage", "Claude transcript source is owned by another user.");
3808
+ if (platform !== "win32" && !isSelfOwned(stats.uid, platform, handle)) throw new ClaudeHostError("storage", "Claude transcript source is owned by another user.");
3826
3809
  return { size: stats.size, device: String(stats.dev), inode: String(stats.ino) };
3827
3810
  }
3828
3811
  async function currentClaudeUserLine(handle, size, sessionId, prompt) {
3829
- const lastLine = await readLastClaudeTranscriptLine(handle, size);
3830
- if (lastLine === null) return void 0;
3831
- const user = parseClaudeUserLine(lastLine.bytes.toString("utf8"), sessionId);
3832
- if (user?.content === prompt) return { offset: lastLine.start, user };
3833
- return { offset: lastLine.complete ? size : lastLine.start };
3812
+ const recent = await readRecentClaudeTranscriptLines(handle, size);
3813
+ if (recent === null) return void 0;
3814
+ for (let index = recent.length - 1; index >= 0; index -= 1) {
3815
+ const line = recent[index];
3816
+ if (line === void 0) continue;
3817
+ const user = parseClaudeUserLine(line.text, sessionId);
3818
+ if (user?.content === prompt) return { offset: line.start, user };
3819
+ const queued = decodeClaudeQueuedPromptRow(parseClaudeJsonLine(line.text), sessionId);
3820
+ if (queued?.content === prompt) return { offset: line.start, user: { uuid: queued.turnId } };
3821
+ }
3822
+ const last = recent[recent.length - 1];
3823
+ if (last === void 0) return { offset: size };
3824
+ return { offset: last.complete ? size : last.start };
3825
+ }
3826
+ var CLAUDE_BASELINE_SCAN_BYTES = 512 * 1024;
3827
+ var CLAUDE_BASELINE_SCAN_LINES = 128;
3828
+ async function readRecentClaudeTranscriptLines(handle, size) {
3829
+ if (size === 0) return null;
3830
+ const start = Math.max(0, size - CLAUDE_BASELINE_SCAN_BYTES);
3831
+ const buffer = Buffer.alloc(size - start);
3832
+ await handle.read(buffer, 0, buffer.length, start);
3833
+ const lines = [];
3834
+ let lineStart = 0;
3835
+ for (let index = 0; index < buffer.length; index += 1) {
3836
+ if (buffer[index] !== 10) continue;
3837
+ lines.push({ start: start + lineStart, text: buffer.subarray(lineStart, index).toString("utf8"), complete: true });
3838
+ lineStart = index + 1;
3839
+ }
3840
+ if (lineStart < buffer.length) lines.push({ start: start + lineStart, text: buffer.subarray(lineStart).toString("utf8"), complete: false });
3841
+ if (lines.length === 0) return null;
3842
+ const whole = start > 0 ? lines.slice(1) : lines;
3843
+ if (whole.length === 0) return null;
3844
+ return whole.slice(-CLAUDE_BASELINE_SCAN_LINES);
3845
+ }
3846
+ function parseClaudeJsonLine(line) {
3847
+ try {
3848
+ return JSON.parse(line);
3849
+ } catch {
3850
+ return null;
3851
+ }
3834
3852
  }
3835
- async function makeClaudeBaselineCursor(handle, registration, now, file, offset) {
3853
+ async function makeClaudeBaselineCursor(handle, registration, now, file, offset, platform, custody) {
3836
3854
  const anchorStart = Math.max(0, offset - CLAUDE_TRANSCRIPT_ANCHOR_BYTES);
3837
3855
  const anchor = Buffer.alloc(offset - anchorStart);
3838
3856
  if (anchor.length > 0) await handle.read(anchor, 0, anchor.length, anchorStart);
@@ -3845,87 +3863,67 @@ async function makeClaudeBaselineCursor(handle, registration, now, file, offset)
3845
3863
  inode: file.inode,
3846
3864
  offset,
3847
3865
  anchor_start: anchorStart,
3848
- anchor_sha256: createHash4("sha256").update(anchor).digest("hex"),
3849
- subagents: await readClaudeSubagentBaseline(registration.transcript_path),
3866
+ anchor_sha256: createHash5("sha256").update(anchor).digest("hex"),
3867
+ subagents: await readClaudeSubagentBaseline(registration.transcript_path, platform, custody),
3850
3868
  status: "active",
3851
- updated_at: timestamp4(now)
3869
+ updated_at: timestamp3(now)
3852
3870
  };
3853
3871
  }
3854
- async function readClaudeSubagentBaseline(transcriptPath) {
3872
+ async function readClaudeSubagentBaseline(transcriptPath, platform, custody) {
3855
3873
  const directory = subagentsDirectory(transcriptPath);
3856
3874
  let names;
3857
3875
  try {
3858
3876
  names = (await readdir3(directory)).filter((name) => name.endsWith(".jsonl") && !name.endsWith(".meta.json")).sort();
3859
- } catch {
3860
- return {};
3877
+ } catch (error) {
3878
+ if (isNotFound(error)) return {};
3879
+ throw new ClaudeHostError("storage", "subagent_baseline_unavailable");
3861
3880
  }
3862
3881
  const cursors = {};
3863
3882
  for (const name of names) {
3864
- const seed = await readClaudeSubagentSeed(directory, name).catch(() => void 0);
3865
- if (seed !== void 0) cursors[name] = seed;
3883
+ try {
3884
+ cursors[name] = await readClaudeSubagentSeed(directory, name, platform, custody);
3885
+ } catch {
3886
+ throw new ClaudeHostError("storage", "subagent_baseline_unavailable");
3887
+ }
3866
3888
  }
3867
3889
  return cursors;
3868
3890
  }
3869
- async function readClaudeSubagentSeed(directory, name) {
3891
+ async function readClaudeSubagentSeed(directory, name, platform, custody) {
3870
3892
  let handle;
3871
3893
  try {
3872
- handle = await open4(join5(directory, name), O_RDONLY4 | O_NOFOLLOW3);
3894
+ handle = await openNoFollow(join5(directory, name), constants2.O_RDONLY, void 0, platform, custody);
3873
3895
  const stats = await handle.stat();
3874
3896
  if (!stats.isFile()) throw new ClaudeHostError("storage", "Claude subagent baseline source is not a regular file.");
3897
+ if (platform !== "win32" && !isSelfOwned(stats.uid, platform, handle)) throw new ClaudeHostError("storage", "Claude subagent baseline source is owned by another user.");
3875
3898
  return {
3876
3899
  device: String(stats.dev),
3877
3900
  inode: String(stats.ino),
3878
3901
  offset: stats.size,
3879
3902
  anchor_start: 0,
3880
- anchor_sha256: createHash4("sha256").update(Buffer.alloc(0)).digest("hex")
3903
+ anchor_sha256: createHash5("sha256").update(Buffer.alloc(0)).digest("hex")
3881
3904
  };
3882
3905
  } finally {
3883
3906
  await handle?.close();
3884
3907
  }
3885
3908
  }
3886
- async function readLastClaudeTranscriptLine(handle, size) {
3887
- if (size === 0) return null;
3888
- const last = Buffer.alloc(1);
3889
- await handle.read(last, 0, 1, size - 1);
3890
- const complete = last[0] === 10;
3891
- let end = complete ? size - 1 : size;
3892
- if (end === 0) return null;
3893
- let position = end;
3894
- const chunks = [];
3895
- let total = 0;
3896
- const maximumBytes = 512 * 1024;
3897
- while (position > 0 && total <= maximumBytes) {
3898
- const start = Math.max(0, position - 64 * 1024);
3899
- const buffer = Buffer.alloc(position - start);
3900
- const read = await handle.read(buffer, 0, buffer.length, start);
3901
- const chunk = buffer.subarray(0, read.bytesRead);
3902
- const newline = chunk.lastIndexOf(10);
3903
- if (newline >= 0) return { start: start + newline + 1, bytes: Buffer.concat([chunk.subarray(newline + 1), ...chunks]), complete };
3904
- chunks.unshift(chunk);
3905
- total += chunk.length;
3906
- position = start;
3907
- }
3908
- if (position === 0) return { start: 0, bytes: Buffer.concat(chunks), complete };
3909
- return null;
3910
- }
3911
3909
  function parseClaudeUserLine(line, sessionId) {
3912
3910
  const row = parseClaudeJsonObject(line);
3913
- if (row === null || row.type !== "user" || row.sessionId !== sessionId || typeof row.uuid !== "string" || row.uuid.length === 0 || !isRecord2(row.message)) return null;
3911
+ if (row === null || row.type !== "user" || row.sessionId !== sessionId || typeof row.uuid !== "string" || row.uuid.length === 0 || !isRecord3(row.message)) return null;
3914
3912
  return typeof row.message.content === "string" ? { uuid: row.uuid, content: row.message.content } : null;
3915
3913
  }
3916
3914
  function parseClaudeJsonObject(line) {
3917
3915
  try {
3918
3916
  const value = JSON.parse(line);
3919
- return isRecord2(value) ? value : null;
3917
+ return isRecord3(value) ? value : null;
3920
3918
  } catch {
3921
3919
  return null;
3922
3920
  }
3923
3921
  }
3924
- function isRecord2(value) {
3922
+ function isRecord3(value) {
3925
3923
  return value !== null && typeof value === "object" && !Array.isArray(value);
3926
3924
  }
3927
3925
  function isNotFound(error) {
3928
- return isRecord2(error) && error.code === "ENOENT";
3926
+ return isRecord3(error) && error.code === "ENOENT";
3929
3927
  }
3930
3928
  function initialCursor(registration, now) {
3931
3929
  return {
@@ -3935,13 +3933,13 @@ function initialCursor(registration, now) {
3935
3933
  transcript_path: registration.transcript_path,
3936
3934
  offset: 0,
3937
3935
  anchor_start: 0,
3938
- anchor_sha256: createHash4("sha256").update(Buffer.alloc(0)).digest("hex"),
3936
+ anchor_sha256: createHash5("sha256").update(Buffer.alloc(0)).digest("hex"),
3939
3937
  subagents: {},
3940
3938
  status: "active",
3941
- updated_at: timestamp4(now)
3939
+ updated_at: timestamp3(now)
3942
3940
  };
3943
3941
  }
3944
- function timestamp4(now) {
3942
+ function timestamp3(now) {
3945
3943
  const value = now();
3946
3944
  if (!(value instanceof Date) || !Number.isFinite(value.getTime())) throw new TypeError("Claude Host clock is invalid.");
3947
3945
  return value.toISOString();
@@ -3949,131 +3947,78 @@ function timestamp4(now) {
3949
3947
 
3950
3948
  // host/claude/src/claude-producer.ts
3951
3949
  var ClaudeProducer = class {
3952
- producer_id = "claude";
3953
- producer_label = "Claude";
3954
- outbox_directory;
3955
3950
  store;
3956
3951
  core;
3957
3952
  constructor(options) {
3958
3953
  this.store = new ClaudeOutboxStore(options.outbox_dir);
3959
- this.outbox_directory = this.store.directory;
3960
3954
  this.core = new HostProducerDrainCore(this, options);
3961
3955
  }
3962
3956
  drainOutbox() {
3963
3957
  return this.core.drainOutbox();
3964
3958
  }
3965
- fail(code2, message) {
3966
- return new ClaudeHostError(code2, message);
3967
- }
3968
3959
  listEnvelopes() {
3969
3960
  return this.store.listEnvelopes();
3970
3961
  }
3971
3962
  removeEnvelope(envelope) {
3972
3963
  return this.store.removeEnvelope(envelope);
3973
3964
  }
3974
- submissionRefOf(envelope) {
3975
- return envelope.submission_ref;
3976
- }
3977
3965
  expiresAtOf(envelope) {
3978
3966
  return envelope.expires_at;
3979
3967
  }
3980
- createdAtOf(envelope) {
3981
- return envelope.created_at;
3982
- }
3983
- async mapEnvelope(envelope) {
3984
- const prompt = observation(envelope, "prompt");
3985
- if (prompt !== void 0) return this.mapInput(envelope, prompt);
3986
- return [mapTerminal(envelope)];
3987
- }
3988
- async mapInput(envelope, prompt) {
3989
- const drafts = [];
3990
- const session = observation(envelope, "session");
3991
- if (session !== void 0) {
3992
- const registrations = await this.store.listTranscriptRegistrations();
3993
- const registration = registrations.find((item) => item.host_source_ref === envelope.identity.host_source_ref && item.host_session_ref === envelope.identity.host_session_ref);
3994
- const directoryIdentity = registration?.cwd ?? envelope.identity.host_session_ref;
3995
- drafts.push({
3996
- frame_kind: "session_started",
3997
- payload: compact({
3998
- local_session_ref: envelope.identity.host_session_ref,
3999
- working_directory_ref: claudeWorkspaceRef(directoryIdentity),
4000
- model: recordText(session.value, "model"),
4001
- observations: [session]
4002
- })
4003
- });
4004
- }
4005
- const turn = observation(envelope, "turn");
4006
- if (turn !== void 0) drafts.push({
4007
- frame_kind: "turn_started",
4008
- payload: compact({
4009
- local_session_ref: envelope.identity.host_session_ref,
4010
- local_turn_ref: localTurnRef(envelope),
4011
- observations: [turn]
4012
- })
4013
- });
4014
- if (!isCealHostPromptObservationValue(prompt.value)) throw new ClaudeHostError("storage", "Claude prompt observation is malformed.");
4015
- drafts.push({
4016
- frame_kind: "user_input",
4017
- payload: compact({
4018
- local_session_ref: envelope.identity.host_session_ref,
4019
- local_turn_ref: localTurnRef(envelope),
4020
- local_input_ref: envelope.host_input_ref,
4021
- input_kind: envelope.input_kind,
4022
- origin_input: envelope.origin_input,
4023
- observations: [prompt],
4024
- context_expires_at: envelope.expires_at
4025
- })
4026
- });
4027
- return drafts;
4028
- }
4029
3968
  };
4030
- function mapTerminal(envelope) {
4031
- const usage = envelope.observations.filter((item) => ["token", "cost"].includes(item.observation_kind));
4032
- return {
4033
- frame_kind: "turn_terminal",
4034
- payload: compact({
4035
- local_session_ref: envelope.identity.host_session_ref,
4036
- local_turn_ref: localTurnRef(envelope),
4037
- terminal_state: "completed",
4038
- usage: usage.length === 0 ? void 0 : Object.fromEntries(usage.map((item) => [item.observation_kind, item.value])),
4039
- observations: envelope.observations
4040
- })
4041
- };
4042
- }
4043
- function observation(envelope, kind) {
4044
- return envelope.observations.find((item) => item.observation_kind === kind);
4045
- }
4046
- function localTurnRef(envelope) {
4047
- return envelope.identity.host_turn_ref;
3969
+
3970
+ // host/service-log.ts
3971
+ import { copyFile, mkdir as mkdir3, stat, truncate, appendFile } from "node:fs/promises";
3972
+ import { dirname as dirname3, isAbsolute as isAbsolute3, posix, win32 as win322 } from "node:path";
3973
+ var HOST_SERVICE_LOG_ENVIRONMENT = "CEAL_HOST_SERVICE_LOG";
3974
+ function writeHostServiceDiagnostic(producer_id, event, failure_kind, cause, sink) {
3975
+ const line = `${JSON.stringify({ schema_version: CEAL_HOST_SERVICE_DIAGNOSTIC_SCHEMA, producer_id, event, ...event === "watcher_scan_failed" && failure_kind !== void 0 ? { failure_kind } : {}, ...event === "watcher_scan_failed" && cause !== void 0 ? { cause } : {} })}
3976
+ `;
3977
+ try {
3978
+ (sink ?? defaultHostServiceDiagnosticSink)(line);
3979
+ } catch {
3980
+ }
4048
3981
  }
4049
- function compact(value) {
4050
- return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== void 0));
3982
+ function defaultHostServiceDiagnosticSink(line) {
3983
+ writeHostServiceLogLine(line);
3984
+ process.stderr.write(line);
4051
3985
  }
4052
- function asRecord(value) {
4053
- return isRecord3(value) ? value : void 0;
3986
+ var HOST_SERVICE_LOG_MAX_BYTES = 1024 * 1024;
3987
+ function writeHostServiceLogLine(line, environment = process.env) {
3988
+ if (process.platform !== "win32") return;
3989
+ const path = environment[HOST_SERVICE_LOG_ENVIRONMENT];
3990
+ if (path === void 0 || !win322.isAbsolute(path) || win322.normalize(path) !== path) return;
3991
+ void appendHostServiceLogLine(path, line).catch(() => void 0);
4054
3992
  }
4055
- function recordText(value, key) {
4056
- const record6 = asRecord(value);
4057
- return typeof record6?.[key] === "string" ? record6[key] : void 0;
3993
+ async function appendHostServiceLogLine(path, line) {
3994
+ if (!isAbsolute3(path)) return;
3995
+ await ensureHostServiceLogDirectory(path);
3996
+ await appendFile(path, line);
4058
3997
  }
4059
- function isRecord3(value) {
4060
- return value !== null && typeof value === "object" && !Array.isArray(value);
3998
+ var HOST_SERVICE_LOG_TAIL_BYTES = 64 * 1024;
3999
+ var hostServiceLogDirectories = /* @__PURE__ */ new Map();
4000
+ function ensureHostServiceLogDirectory(path) {
4001
+ const directory = dirname3(path);
4002
+ const existing = hostServiceLogDirectories.get(directory);
4003
+ if (existing !== void 0) return existing;
4004
+ const pending = mkdir3(directory, { recursive: true, mode: 448 }).then(() => void 0).catch((error) => {
4005
+ hostServiceLogDirectories.delete(directory);
4006
+ throw error;
4007
+ });
4008
+ hostServiceLogDirectories.set(directory, pending);
4009
+ return pending;
4061
4010
  }
4062
4011
 
4063
4012
  // host/claude/src/claude-producer-runtime.ts
4064
4013
  async function drainClaudeProducerOnce(options) {
4065
- const promptPort = new HostRelayCurrentPromptPort(options.relay_endpoint, options.timeout_ms);
4066
4014
  const scan = await new ClaudeTranscriptWatcher({
4067
4015
  outbox_dir: options.outbox_dir,
4068
- producer_installation_ref: options.producer_installation_ref,
4069
- register_current_prompt: (input) => promptPort.register(input),
4070
- release_current_prompt: (identity) => promptPort.release(identity),
4071
4016
  diagnostic: (event, failure_kind, cause) => writeHostServiceDiagnostic("claude", event, failure_kind, cause)
4072
4017
  }).scanOnce().catch((error) => {
4073
4018
  writeHostServiceDiagnostic("claude", "watcher_scan_failed", claudeTranscriptScanFailureKind(error), claudeTranscriptScanCause(void 0, error));
4074
4019
  throw error;
4075
4020
  });
4076
- const producer = new ClaudeProducer({ outbox_dir: options.outbox_dir, producer_installation_ref: options.producer_installation_ref, relay: new HostRelayIpcAdmissionPort(options.relay_endpoint, options.timeout_ms), ...options.producer_epoch === void 0 ? {} : { producer_epoch: options.producer_epoch }, ...options.now === void 0 ? {} : { now: options.now } });
4021
+ const producer = new ClaudeProducer({ outbox_dir: options.outbox_dir, ...options.now === void 0 ? {} : { now: options.now } });
4077
4022
  return { scanned: scan.enqueued, ...await producer.drainOutbox() };
4078
4023
  }
4079
4024
 
@@ -4082,16 +4027,16 @@ import { homedir as homedir2 } from "node:os";
4082
4027
  import { join as join6 } from "node:path";
4083
4028
 
4084
4029
  // host/producer-service.ts
4085
- import { posix as posix3, win32 as win323 } from "node:path";
4030
+ import { posix as posix4, win32 as win325 } from "node:path";
4086
4031
 
4087
4032
  // host/runtime-source-paths.ts
4088
- import { createHash as createHash5 } from "node:crypto";
4089
- import { posix as posix2, win32 as win322 } from "node:path";
4033
+ import { createHash as createHash6 } from "node:crypto";
4034
+ import { posix as posix3, win32 as win324 } from "node:path";
4090
4035
 
4091
4036
  // host/absolute-path.ts
4092
- import { posix, win32 } from "node:path";
4037
+ import { posix as posix2, win32 as win323 } from "node:path";
4093
4038
  function isAbsoluteNormalizedNonRootPath(value, flavor = process.platform, allowRoot = false) {
4094
- const pathApi = flavor === "win32" || flavor === "windows" ? win32 : posix;
4039
+ const pathApi = flavor === "win32" || flavor === "windows" ? win323 : posix2;
4095
4040
  return typeof value === "string" && value.length <= 4096 && !value.includes("\0") && pathApi.isAbsolute(value) && pathApi.normalize(value) === value && (allowRoot || pathApi.parse(value).root !== value);
4096
4041
  }
4097
4042
 
@@ -4103,28 +4048,23 @@ function hostSourceUuid(sourceRef) {
4103
4048
  function hostRuntimeSourcePaths(stateRoot, sourceRef, platform = process.platform) {
4104
4049
  const sourceId = hostSourceUuid(sourceRef);
4105
4050
  if (!isAbsoluteNormalizedNonRootPath(stateRoot, platform) || sourceId === null) throw new TypeError("Host runtime source identity is invalid.");
4106
- const path = platform === "win32" ? win322 : posix2;
4051
+ const path = platform === "win32" ? win324 : posix3;
4107
4052
  const sourcesRoot = path.join(stateRoot, "sources");
4108
4053
  const sourceRoot = path.join(sourcesRoot, sourceId);
4109
4054
  const runtimeRoot = path.join(stateRoot, "run");
4110
- const endpoint = platform === "win32" ? `\\\\.\\pipe\\ceal-host-${createHash5("sha256").update(`${stateRoot}\0${sourceRef}`).digest("hex").slice(0, 24)}` : path.join(runtimeRoot, `${sourceId}.sock`);
4111
- assertHostRuntimeIpcLength(endpoint, platform);
4055
+ const endpoint = platform === "win32" ? `\\\\.\\pipe\\ceal-host-${createHash6("sha256").update(`${stateRoot}\0${sourceRef}`).digest("hex").slice(0, 24)}` : path.join(runtimeRoot, `${sourceId}.sock`);
4112
4056
  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 };
4113
4057
  }
4114
- function assertHostRuntimeIpcLength(endpoint, platform) {
4115
- if (platform === "win32") return;
4116
- const maximum = platform === "linux" ? 107 : 103;
4117
- 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" });
4118
- }
4119
4058
  function assertHostProducerSourcePaths(stateRoot, config, producer, platform = process.platform) {
4120
4059
  const paths = hostRuntimeSourcePaths(stateRoot, config.host_source_ref, platform);
4121
- if (config.outbox_dir !== (producer === "codex" ? paths.codexOutbox : paths.claudeOutbox) || config.relay_endpoint !== paths.endpoint) throw new TypeError("Host producer outbox and IPC must belong to its configured source.");
4060
+ if (config.outbox_dir !== (producer === "codex" ? paths.codexOutbox : paths.claudeOutbox) || platform !== "win32" && config.relay_endpoint !== paths.endpoint) throw new TypeError("Host producer outbox and IPC must belong to its configured source.");
4061
+ return paths.endpoint;
4122
4062
  }
4123
4063
 
4124
4064
  // host/producer-service.ts
4125
- import { constants } from "node:fs";
4126
- import { lstat as lstat2, open as open5 } from "node:fs/promises";
4127
- var SAFE_REF4 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
4065
+ import { constants as constants3 } from "node:fs";
4066
+ import { lstat as lstat3 } from "node:fs/promises";
4067
+ var SAFE_REF3 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
4128
4068
  var HOST_PRODUCER_CONFIG_FLAG = "--ceal-host-config";
4129
4069
  var HostProducerServiceError = class extends Error {
4130
4070
  code;
@@ -4135,18 +4075,19 @@ var HostProducerServiceError = class extends Error {
4135
4075
  }
4136
4076
  };
4137
4077
  var HOST_PRODUCER_CONFIG_KEYS = ["schema_version", "host_source_ref", "producer_installation_ref", "producer_epoch", "outbox_dir", "relay_endpoint"];
4078
+ var HOST_PRODUCER_CONFIG_ENDPOINT_KEY = "relay_endpoint";
4138
4079
  var HOST_PRODUCER_TIMING_DEFAULTS = Object.freeze({ reconcile_interval_ms: 250, retry_backoff_ms: 1e3, ipc_timeout_ms: 5e3 });
4139
4080
  function hostProducerExtraPath(extras, key) {
4140
4081
  const value = extras[key];
4141
- if (value === void 0) throw hostProducerError("invalid_config");
4082
+ if (value === void 0) throw hostProducerError("invalid_config", refusalFact(key, "missing", "an absolute path"));
4142
4083
  return value;
4143
4084
  }
4144
4085
  function parseHostProducerConfigFlag(args, platform = process.platform) {
4145
4086
  return parseProducerHostConfigFlag(args, HOST_PRODUCER_CONFIG_FLAG, platform, hostProducerError);
4146
4087
  }
4147
- async function readHostProducerConfig(descriptor, configPath, platform = process.platform) {
4148
- const config = decodeHostProducerConfigText(descriptor, await readProducerConfigText(configPath, platform, hostProducerError), platform);
4149
- const path = platform === "win32" ? win323 : posix3;
4088
+ async function readHostProducerConfig(descriptor, configPath, platform = process.platform, custody = {}) {
4089
+ const config = decodeHostProducerConfigText(descriptor, await readProducerConfigText(configPath, platform, hostProducerError, custody), platform);
4090
+ const path = platform === "win32" ? win325 : posix4;
4150
4091
  const root = path.resolve(config.outbox_dir, "../../../../..");
4151
4092
  try {
4152
4093
  assertHostProducerSourcePaths(root, config, descriptor.producer_id, platform);
@@ -4172,15 +4113,31 @@ function parseProducerHostConfigFlag(args, flag, platform, error) {
4172
4113
  assertProducerAbsolutePath(value, platform, "config", error);
4173
4114
  return value;
4174
4115
  }
4175
- async function readProducerConfigText(configPath, platform, error) {
4176
- assertProducerAbsolutePath(configPath, platform, "config", error);
4116
+ async function readProducerConfigText(configPath, platform, error, custody) {
4117
+ assertProducerAbsolutePath(configPath, process.platform, "config", error);
4118
+ if (platform === "win32") {
4119
+ let bytes;
4120
+ try {
4121
+ bytes = await readPrivateFileWithSecurefs(configPath, CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES, custody);
4122
+ } catch (cause) {
4123
+ if (cause instanceof SecureFileCustodyError && cause.reason === "not_private") throw error("config_not_private");
4124
+ if (cause instanceof SecureFileCustodyError && cause.reason === "too_large") throw error("invalid_config", refusalFact("config_bytes", "over_limit", `at most ${CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES}`));
4125
+ if (cause instanceof SecureFileCustodyError && cause.reason === "reparse_point") throw error("invalid_config");
4126
+ throw error("config_unavailable");
4127
+ }
4128
+ if (bytes.byteLength === 0) throw error("invalid_config", refusalFact("config_bytes", 0, `between 1 and ${CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES}`));
4129
+ try {
4130
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
4131
+ } catch {
4132
+ throw error("invalid_config");
4133
+ }
4134
+ }
4177
4135
  const pathStat = await configPathStat(configPath, error);
4178
4136
  validateConfigFile(pathStat, platform, error);
4179
- const noFollow = typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
4180
- const handle = await openConfigFile(configPath, constants.O_RDONLY | noFollow, error);
4137
+ const handle = await openConfigFile(configPath, platform, error, custody);
4181
4138
  try {
4182
4139
  const opened = await handle.stat();
4183
- validateConfigFile(opened, platform, error);
4140
+ validateConfigFile(opened, platform, error, handle);
4184
4141
  validateOpenedFile(opened, pathStat, platform, error);
4185
4142
  return await readBoundedConfig(handle, opened.size, error);
4186
4143
  } finally {
@@ -4188,8 +4145,9 @@ async function readProducerConfigText(configPath, platform, error) {
4188
4145
  }
4189
4146
  }
4190
4147
  function decodeProducerConfigCore(text, options) {
4148
+ const ownership = producerConfigKeyOwnership(options.keys, options.platform);
4191
4149
  const value = parseCanonicalJson(text, options.error);
4192
- if (!isRecord4(value) || !hasConfigKeys(value, options.keys) || value.schema_version !== options.schema) throw options.error("invalid_config");
4150
+ if (!isRecord4(value) || !hasConfigKeys(value, ownership.required, ownership.tolerated) || value.schema_version !== options.schema) throw options.error("invalid_config", refusalFacts(producerShapeFacts(value, { schema: options.schema, keys: ownership.required, tolerated: ownership.tolerated })));
4193
4151
  const hostSourceRef = typeof value.host_source_ref === "string" ? value.host_source_ref : "";
4194
4152
  const installationRef = typeof value.producer_installation_ref === "string" ? value.producer_installation_ref : "";
4195
4153
  const producerEpoch = validateIdentity3(hostSourceRef, installationRef, value.producer_epoch, options.error);
@@ -4207,14 +4165,21 @@ function decodeProducerConfigCore(text, options) {
4207
4165
  producer_installation_ref: installationRef,
4208
4166
  producer_epoch: producerEpoch,
4209
4167
  outbox_dir: value.outbox_dir,
4210
- relay_endpoint: value.relay_endpoint,
4168
+ relay_endpoint: producerConfigEndpoint(value),
4211
4169
  reconcile_interval_ms: intervals[0],
4212
4170
  retry_backoff_ms: intervals[1],
4213
4171
  ipc_timeout_ms: intervals[2]
4214
4172
  };
4215
4173
  }
4174
+ function producerConfigKeyOwnership(keys, platform) {
4175
+ if (platform !== "win32") return { required: keys, tolerated: [] };
4176
+ return { required: keys.filter((key) => key !== HOST_PRODUCER_CONFIG_ENDPOINT_KEY), tolerated: [HOST_PRODUCER_CONFIG_ENDPOINT_KEY] };
4177
+ }
4178
+ function producerConfigEndpoint(value) {
4179
+ return typeof value.relay_endpoint === "string" ? value.relay_endpoint : "";
4180
+ }
4216
4181
  function assertProducerRelayEndpoint(value, platform, error) {
4217
- if (platform === "win32" && typeof value === "string" && /^\\\\\.\\pipe\\[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value)) return;
4182
+ if (platform === "win32") return;
4218
4183
  assertProducerAbsolutePath(value, platform, "runtime", error);
4219
4184
  }
4220
4185
  function assertProducerAbsolutePath(value, platform, kind, error) {
@@ -4222,26 +4187,27 @@ function assertProducerAbsolutePath(value, platform, kind, error) {
4222
4187
  }
4223
4188
  async function configPathStat(configPath, error) {
4224
4189
  try {
4225
- return await lstat2(configPath);
4190
+ return await lstat3(configPath);
4226
4191
  } catch {
4227
4192
  throw error("config_unavailable");
4228
4193
  }
4229
4194
  }
4230
- function validateConfigFile(value, platform, error) {
4195
+ function validateConfigFile(value, platform, error, handle) {
4231
4196
  if (!value.isFile() || value.isSymbolicLink()) throw error("invalid_config", refusalFacts([{ name: "config_is_file", value: value.isFile(), expected: "true" }, { name: "config_is_symlink", value: value.isSymbolicLink(), expected: "false" }]));
4232
4197
  if (value.size <= 0 || value.size > CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES) throw error("invalid_config", refusalFact("config_bytes", value.size, `between 1 and ${CEAL_ENCODED_JSON_DOCUMENT_MAX_BYTES}`));
4233
- if (platform !== "win32" && (value.uid !== process.getuid?.() || (value.mode & 63) !== 0)) throw error("config_not_private", refusalFacts([{ name: "platform", value: platform, expected: "win32 or owner-private" }, { name: "config_uid", value: value.uid, expected: "the current user" }, { name: "config_mode", value: value.mode, expected: "owner-private" }]));
4198
+ if (!isSelfOwned(value.uid, platform, handle) || !isPrivateFileMode(value.mode, platform, handle)) throw error("config_not_private", refusalFacts([{ name: "platform", value: platform, expected: "a platform with verifiable owner and private-mode facts" }, { name: "config_uid", value: value.uid, expected: "the current user" }, { name: "config_mode", value: value.mode, expected: "private to its owner" }]));
4234
4199
  }
4235
- async function openConfigFile(configPath, flags, error) {
4200
+ async function openConfigFile(configPath, platform, error, custody) {
4236
4201
  try {
4237
- return await open5(configPath, flags);
4238
- } catch {
4202
+ return await openNoFollow(configPath, constants3.O_RDONLY, void 0, platform, custody);
4203
+ } catch (error_) {
4204
+ if (error_ instanceof SecureFileCustodyError) throw error(error_.reason === "not_private" ? "config_not_private" : "invalid_config");
4239
4205
  throw error("config_unavailable");
4240
4206
  }
4241
4207
  }
4242
4208
  function validateOpenedFile(opened, pathStat, platform, error) {
4243
4209
  if (!opened.isFile()) throw error("config_unavailable", refusalFacts([{ name: "opened_is_file", value: opened.isFile(), expected: "true" }, { name: "opened_mode", value: opened.mode.toString(8), expected: "a regular file mode" }]));
4244
- if (platform !== "win32" && (opened.dev !== pathStat.dev || opened.ino !== pathStat.ino)) throw error("config_unavailable", refusalFacts([{ name: "platform", value: platform, expected: "win32, or the same file the path named" }, { name: "opened_device", value: opened.dev, expected: String(pathStat.dev) }, { name: "opened_inode", value: opened.ino, expected: String(pathStat.ino) }]));
4210
+ if (opened.dev !== pathStat.dev || opened.ino !== pathStat.ino) throw error("config_unavailable", refusalFacts([{ name: "platform", value: platform, expected: "the same file the path named" }, { name: "opened_device", value: opened.dev, expected: String(pathStat.dev) }, { name: "opened_inode", value: opened.ino, expected: String(pathStat.ino) }]));
4245
4211
  }
4246
4212
  async function readBoundedConfig(handle, expectedBytes, error) {
4247
4213
  const buffer = Buffer.allocUnsafe(expectedBytes + 1);
@@ -4273,25 +4239,50 @@ function firstDifferenceIndex(left, right) {
4273
4239
  for (let index = 0; index < shared; index += 1) if (left[index] !== right[index]) return index;
4274
4240
  return shared;
4275
4241
  }
4242
+ function producerShapeFacts(value, options) {
4243
+ if (!isRecord4(value)) return [{ name: "config", value: "wrong_type", expected: "a record" }];
4244
+ const tolerated = options.tolerated ?? [];
4245
+ const allowed = [...options.keys, ...tolerated, ...Object.keys(HOST_PRODUCER_TIMING_DEFAULTS)];
4246
+ const facts = [
4247
+ ...Object.keys(value).filter((key) => !allowed.includes(key)).map((key) => ({ name: key, value: "unknown", expected: `one of ${[...options.keys, ...tolerated].join(", ")}` })),
4248
+ ...options.keys.filter((key) => !Object.hasOwn(value, key)).map((key) => ({ name: key, value: "missing", expected: "present" }))
4249
+ ];
4250
+ if (Object.hasOwn(value, "schema_version") && value.schema_version !== options.schema) facts.push({ name: "schema_version", value: "wrong_type", expected: options.schema });
4251
+ return facts;
4252
+ }
4276
4253
  function validateIdentity3(sourceRef, installationRef, epoch, error) {
4277
- if (!SAFE_REF4.test(sourceRef) || !SAFE_REF4.test(installationRef) || !positive2(epoch, 2147483647)) throw error("invalid_config");
4254
+ if (!SAFE_REF3.test(sourceRef) || !SAFE_REF3.test(installationRef) || !positive(epoch, 2147483647)) throw error("invalid_config", refusalFacts(producerIdentityFacts(sourceRef, installationRef, epoch)));
4278
4255
  return epoch;
4279
4256
  }
4257
+ function producerIdentityFacts(sourceRef, installationRef, epoch) {
4258
+ const facts = [];
4259
+ if (!SAFE_REF3.test(sourceRef)) facts.push({ name: "host_source_ref", value: "wrong_type", expected: "a safe reference" });
4260
+ if (!SAFE_REF3.test(installationRef)) facts.push({ name: "producer_installation_ref", value: "wrong_type", expected: "a safe reference" });
4261
+ if (!positive(epoch, 2147483647)) facts.push({ name: "producer_epoch", value: "wrong_type", expected: "a positive integer" });
4262
+ return facts;
4263
+ }
4280
4264
  function validateIntervals(reconcile, retry, timeout, error) {
4281
- if (!boundedMilliseconds(reconcile) || !boundedMilliseconds(retry) || !boundedMilliseconds(timeout)) throw error("invalid_config");
4265
+ if (!boundedMilliseconds(reconcile) || !boundedMilliseconds(retry) || !boundedMilliseconds(timeout)) throw error("invalid_config", refusalFacts(producerIntervalFacts(reconcile, retry, timeout)));
4282
4266
  return [reconcile, retry, timeout];
4283
4267
  }
4284
- function hasConfigKeys(value, required) {
4285
- return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => required.includes(key) || Object.hasOwn(HOST_PRODUCER_TIMING_DEFAULTS, key));
4268
+ function producerIntervalFacts(reconcile, retry, timeout) {
4269
+ const facts = [];
4270
+ if (!boundedMilliseconds(reconcile)) facts.push({ name: "reconcile_interval_ms", value: "wrong_type", expected: "25..60000 milliseconds" });
4271
+ if (!boundedMilliseconds(retry)) facts.push({ name: "retry_backoff_ms", value: "wrong_type", expected: "25..60000 milliseconds" });
4272
+ if (!boundedMilliseconds(timeout)) facts.push({ name: "ipc_timeout_ms", value: "wrong_type", expected: "25..60000 milliseconds" });
4273
+ return facts;
4274
+ }
4275
+ function hasConfigKeys(value, required, tolerated = []) {
4276
+ return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => required.includes(key) || tolerated.includes(key) || Object.hasOwn(HOST_PRODUCER_TIMING_DEFAULTS, key));
4286
4277
  }
4287
4278
  function isRecord4(value) {
4288
4279
  return value !== null && typeof value === "object" && !Array.isArray(value);
4289
4280
  }
4290
- function positive2(value, maximum) {
4281
+ function positive(value, maximum) {
4291
4282
  return Number.isSafeInteger(value) && Number(value) > 0 && Number(value) <= maximum;
4292
4283
  }
4293
4284
  function boundedMilliseconds(value) {
4294
- return positive2(value, 6e4) && Number(value) >= 25;
4285
+ return positive(value, 6e4) && Number(value) >= 25;
4295
4286
  }
4296
4287
 
4297
4288
  // host/claude/src/claude-producer-service.ts
@@ -4303,11 +4294,7 @@ var CLAUDE_PRODUCER_DESCRIPTOR = {
4303
4294
  extra_paths: [CLAUDE_TRANSCRIPTS_ROOT],
4304
4295
  config: (shared, extras) => ({ ...shared, claude_transcripts_root: hostProducerExtraPath(extras, CLAUDE_TRANSCRIPTS_ROOT) }),
4305
4296
  runtime_options: (config) => Object.freeze({
4306
- outbox_dir: config.outbox_dir,
4307
- producer_installation_ref: config.producer_installation_ref,
4308
- producer_epoch: config.producer_epoch,
4309
- relay_endpoint: config.relay_endpoint,
4310
- timeout_ms: config.ipc_timeout_ms
4297
+ outbox_dir: config.outbox_dir
4311
4298
  }),
4312
4299
  drain: drainClaudeProducerOnce
4313
4300
  };
@@ -4351,8 +4338,9 @@ async function installedClaudeHookRunnerOptions(args) {
4351
4338
  Object.defineProperty(options, "host_source_ref", { value: config.host_source_ref, enumerable: false });
4352
4339
  return options;
4353
4340
  }
4354
- function standaloneClaudeHookRunnerOptions() {
4355
- return { outbox_dir: process.env.CEAL_CLAUDE_OUTBOX_DIR ?? join6(homedir2(), ".claude", ".ceal", "claude-observation-outbox"), claude_transcripts_root: process.env.CLAUDE_CONFIG_DIR !== void 0 ? join6(process.env.CLAUDE_CONFIG_DIR, "projects") : join6(homedir2(), ".claude", "projects") };
4341
+ function standaloneClaudeHookRunnerOptions(overrides) {
4342
+ const claudeHome = resolveClaudeConfigHome(process.env, homedir2());
4343
+ return { outbox_dir: overrides?.outbox_dir ?? join6(claudeHome, ".ceal", "claude-observation-outbox"), claude_transcripts_root: resolveClaudeProjectsRoot(process.env, homedir2()) };
4356
4344
  }
4357
4345
  function hasHostConfigArgument(args) {
4358
4346
  return args.some((argument) => argument === HOST_PRODUCER_CONFIG_FLAG || argument.startsWith(`${HOST_PRODUCER_CONFIG_FLAG}=`));
@@ -4369,8 +4357,11 @@ async function readStdin(stdin) {
4369
4357
  return Buffer.concat(chunks).toString("utf8");
4370
4358
  }
4371
4359
 
4360
+ // host/local-capability-descriptor.ts
4361
+ var HOST_LOCAL_CAPABILITY_AUTHENTICATION_SUFFIX = Object.freeze(["authenticate", "--client-nonce"]);
4362
+
4372
4363
  // host/client-doctor.ts
4373
- import { execFile } from "node:child_process";
4364
+ import { execFile as execFile2 } from "node:child_process";
4374
4365
  import { promisify } from "node:util";
4375
4366
 
4376
4367
  // host/invocation-authentication-client-command.ts
@@ -4381,7 +4372,7 @@ var DOCTOR_FLAGS = Object.freeze({
4381
4372
  });
4382
4373
 
4383
4374
  // host/client-doctor.ts
4384
- var execFileAsync = promisify(execFile);
4375
+ var execFileAsync = promisify(execFile2);
4385
4376
 
4386
4377
  // host/claude/src/claude-installer.ts
4387
4378
  var ACTIVE_CLAUDE_HOOK_EVENTS = new Set(CLAUDE_HOOK_EVENTS);