@corca-org/ceal-host-linux-arm64 1.0.17 → 1.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/ceal-host +0 -0
- package/libexec/ceal-host/claude-hook.mjs +24 -8
- package/libexec/ceal-host/claude-invocation-authentication.mjs +28 -8
- package/libexec/ceal-host/claude-producer.mjs +28 -8
- package/libexec/ceal-host/codex-hook.mjs +29 -368
- package/libexec/ceal-host/codex-invocation-authentication.mjs +29 -366
- package/libexec/ceal-host/codex-producer.mjs +29 -366
- package/libexec/ceal-host/control.mjs +37 -371
- package/libexec/ceal-host/relay.mjs +369 -49
- package/package.json +1 -1
- package/share/ceal-host/bundle-manifest.json +12 -12
- package/share/ceal-host/bundle-manifest.sigstore.json +1 -1
|
@@ -200,6 +200,9 @@ var CEAL_COMMAND_RESPONSE_DISCOVERY_MANIFEST_DOCUMENT_FIELDS = Object.freeze([..
|
|
|
200
200
|
function vocabulary(words) {
|
|
201
201
|
return Object.freeze(words);
|
|
202
202
|
}
|
|
203
|
+
function isVocabularyWord(words, value) {
|
|
204
|
+
return words.some((word) => word === value);
|
|
205
|
+
}
|
|
203
206
|
|
|
204
207
|
// packages/ceal-client-protocol/src/gateway-response-types.ts
|
|
205
208
|
var CEAL_OPERATION_READINESS_VALUES = vocabulary(["ready", "degraded", "unavailable", "unknown"]);
|
|
@@ -950,7 +953,7 @@ var OPTIONAL_FIELDS = CEAL_CLIENT_WIRE_CONTRACT.command_response.bare_document_o
|
|
|
950
953
|
// packages/ceal-client-protocol/src/resource-row.ts
|
|
951
954
|
var CEAL_RESOURCE_SEARCH_ORDERS = vocabulary(["relevance", "modified", "viewed", "created"]);
|
|
952
955
|
var CEAL_RESOURCE_SEARCH_DIRECTIONS = vocabulary(["ascending", "descending"]);
|
|
953
|
-
var CEAL_RESOURCE_SEARCH_FILTERS = vocabulary(["query", "modified_since", "since", "until", "authored_by_me", "include_replies", "kinds", "container_ref", "container_refs", "container_names", "addressed_to_me", "unread"]);
|
|
956
|
+
var CEAL_RESOURCE_SEARCH_FILTERS = vocabulary(["query", "modified_since", "since", "until", "authored_by_me", "authored_by", "include_replies", "kinds", "container_ref", "container_refs", "container_names", "addressed_to_me", "unread"]);
|
|
954
957
|
var CEAL_RESOURCE_TIME_BASES = vocabulary(["created", "modified", "viewed"]);
|
|
955
958
|
var CEAL_REPOSITORY_VISIBILITIES = vocabulary(["public", "private", "internal"]);
|
|
956
959
|
var CEAL_ACTIVITY_V3_KINDS = vocabulary(["authored", "edited", "messaged", "responded"]);
|
|
@@ -1029,6 +1032,7 @@ var RESOURCE_ROW_KEY_RULES = Object.freeze({
|
|
|
1029
1032
|
ref: { required: true, timestamp: false },
|
|
1030
1033
|
next: { required: true, timestamp: false },
|
|
1031
1034
|
kind: { required: true, timestamp: false },
|
|
1035
|
+
matched_kinds: { required: false, timestamp: false },
|
|
1032
1036
|
title: { required: true, timestamp: false },
|
|
1033
1037
|
container_label: { required: true, timestamp: false },
|
|
1034
1038
|
container_ref: { required: false, timestamp: false },
|
|
@@ -1419,6 +1423,16 @@ var CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_MAX_BYTES = new TextEncoder().enc
|
|
|
1419
1423
|
expires_at: (/* @__PURE__ */ new Date("+010000-01-01T00:00:00.000Z")).toISOString()
|
|
1420
1424
|
})}
|
|
1421
1425
|
`).byteLength;
|
|
1426
|
+
var CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS = vocabulary([
|
|
1427
|
+
"request_authority_unavailable",
|
|
1428
|
+
"request_missing",
|
|
1429
|
+
"authority_mismatch",
|
|
1430
|
+
"host_source_mismatch",
|
|
1431
|
+
"host_session_mismatch",
|
|
1432
|
+
"host_turn_mismatch",
|
|
1433
|
+
"host_input_mismatch",
|
|
1434
|
+
"host_prompt_mismatch"
|
|
1435
|
+
]);
|
|
1422
1436
|
function decodeCealHostInvocationAuthenticationResult(value, now = /* @__PURE__ */ new Date()) {
|
|
1423
1437
|
if (!isCealJsonRecord(value) || !exactKeys(value, ["schema_version", "context_token", "expires_at"]) || value.schema_version !== CEAL_HOST_INVOCATION_AUTHENTICATION_RESULT_SCHEMA || typeof value.context_token !== "string" || !CEAL_HOST_CONTEXT_TOKEN_PATTERN.test(value.context_token) || typeof value.expires_at !== "string" || !validFutureTimestamp(value.expires_at, now)) {
|
|
1424
1438
|
throw new TypeError("The Host invocation authentication result is invalid.");
|
|
@@ -1437,8 +1451,8 @@ function exactKeys(value, expected) {
|
|
|
1437
1451
|
// packages/ceal-client-protocol/src/client-result-materialization.ts
|
|
1438
1452
|
var CEAL_UNREAD_REASONS = vocabulary(["blocked", "unavailable", "too_large", "unsupported", "download_failed", "permission_denied"]);
|
|
1439
1453
|
var CEAL_MODEL_VISIBLE_COMMAND_OUTPUT_MAX_BYTES = 32 * 1024;
|
|
1440
|
-
var REQUIRED_FIELDS2 = Object.freeze(["schema_version", "operation_id"]);
|
|
1441
|
-
var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "
|
|
1454
|
+
var REQUIRED_FIELDS2 = Object.freeze(["schema_version", "operation_id", "read_argv"]);
|
|
1455
|
+
var FIELDS = /* @__PURE__ */ new Set([...REQUIRED_FIELDS2, "local_result_root", "continue_argv", "model_disclosure"]);
|
|
1442
1456
|
|
|
1443
1457
|
// packages/ceal-client-protocol/src/gateway-cache-origin-validation.ts
|
|
1444
1458
|
var CEAL_MAX_CACHE_ORIGIN_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
@@ -1649,7 +1663,7 @@ function assertHostRelaySourcePaths(stateRoot, config, platform = process.platfo
|
|
|
1649
1663
|
// host/relay/src/host-relay-runtime.ts
|
|
1650
1664
|
import { O_NOFOLLOW as O_NOFOLLOW3, O_RDONLY as O_RDONLY4 } from "node:constants";
|
|
1651
1665
|
import { lstat as lstat4, open as open5 } from "node:fs/promises";
|
|
1652
|
-
import { dirname as
|
|
1666
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
1653
1667
|
|
|
1654
1668
|
// host/abortable-wait.ts
|
|
1655
1669
|
function waitForAbort(signal, milliseconds) {
|
|
@@ -1753,6 +1767,9 @@ var HostProducerFrameError = class extends Error {
|
|
|
1753
1767
|
this.code = code;
|
|
1754
1768
|
}
|
|
1755
1769
|
};
|
|
1770
|
+
function assertHostProducerFrame(value) {
|
|
1771
|
+
validateFrame(value);
|
|
1772
|
+
}
|
|
1756
1773
|
function encodeHostProducerFrame(frame) {
|
|
1757
1774
|
validateFrame(frame);
|
|
1758
1775
|
const json = Buffer.from(JSON.stringify(frame), "utf8");
|
|
@@ -2433,31 +2450,53 @@ var RelayAdmissionStore = class {
|
|
|
2433
2450
|
for (const producerId of PRODUCER_ORDER) {
|
|
2434
2451
|
const head = state.entries.find((entry2) => entry2.producer_id === producerId);
|
|
2435
2452
|
if (!head || head.retry_at_ms > options.now_ms) continue;
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
const exhausted = retryExhausted(outcome, head);
|
|
2439
|
-
if (outcome.status === "retryable" && !exhausted) {
|
|
2440
|
-
scheduleRetry(head, options.now_ms, outcome.reason);
|
|
2441
|
-
retained.push(input.submission_ref);
|
|
2442
|
-
await this.write(state);
|
|
2443
|
-
continue;
|
|
2444
|
-
}
|
|
2445
|
-
const installation2 = state.installations[installationKey(head.frame)];
|
|
2446
|
-
if (!installation2) throw new TypeError("Host Relay delivery installation is unavailable.");
|
|
2447
|
-
if (outcome.status === "refused" || exhausted) {
|
|
2448
|
-
await this.quarantine.retainFrame(head.frame, "gateway_refused");
|
|
2449
|
-
installation2.last_refusal = { sequence: head.sequence, submission_ref: input.submission_ref, ...refusalCause(outcome, head), refused_at_ms: options.now_ms };
|
|
2450
|
-
refused.push(input.submission_ref);
|
|
2451
|
-
} else {
|
|
2452
|
-
await options.after_gateway_accept_before_delivery_fsync?.({ fault_point: "after_gateway_accept_before_delivery_fsync", delivery: structuredClone(input) });
|
|
2453
|
-
delivered.push(input.submission_ref);
|
|
2454
|
-
}
|
|
2455
|
-
state.entries.splice(state.entries.indexOf(head), 1);
|
|
2456
|
-
installation2.delivered_through_sequence = Math.max(installation2.delivered_through_sequence, head.sequence);
|
|
2457
|
-
await this.write(state);
|
|
2453
|
+
if (await this.refusePoisonHead(state, head, options, refused)) continue;
|
|
2454
|
+
await this.deliverHead(state, head, deliver, options, delivered, retained, refused);
|
|
2458
2455
|
}
|
|
2459
2456
|
return { delivered, retained, refused };
|
|
2460
2457
|
}
|
|
2458
|
+
async deliverHead(state, head, deliver, options, delivered, retained, refused) {
|
|
2459
|
+
const input = { frame: structuredClone(head.frame), host_source_ref: options.host_source_ref, submission_ref: hostProducerSubmissionRef(options.host_source_ref, head.frame) };
|
|
2460
|
+
const outcome = await deliver(input);
|
|
2461
|
+
const exhausted = retryExhausted(outcome, head);
|
|
2462
|
+
if (outcome.status === "retryable" && !exhausted) {
|
|
2463
|
+
scheduleRetry(head, options.now_ms, outcome.reason);
|
|
2464
|
+
retained.push(input.submission_ref);
|
|
2465
|
+
await this.write(state);
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
const installation2 = state.installations[installationKey(head.frame)];
|
|
2469
|
+
if (!installation2) throw new TypeError("Host Relay delivery installation is unavailable.");
|
|
2470
|
+
if (outcome.status === "refused" || exhausted) {
|
|
2471
|
+
await this.quarantine.retainFrame(head.frame, "gateway_refused");
|
|
2472
|
+
installation2.last_refusal = { sequence: head.sequence, submission_ref: input.submission_ref, ...refusalCause(outcome, head), refused_at_ms: options.now_ms };
|
|
2473
|
+
refused.push(input.submission_ref);
|
|
2474
|
+
} else {
|
|
2475
|
+
await options.after_gateway_accept_before_delivery_fsync?.({ fault_point: "after_gateway_accept_before_delivery_fsync", delivery: structuredClone(input) });
|
|
2476
|
+
delivered.push(input.submission_ref);
|
|
2477
|
+
}
|
|
2478
|
+
state.entries.splice(state.entries.indexOf(head), 1);
|
|
2479
|
+
installation2.delivered_through_sequence = Math.max(installation2.delivered_through_sequence, head.sequence);
|
|
2480
|
+
await this.write(state);
|
|
2481
|
+
}
|
|
2482
|
+
async refusePoisonHead(state, head, options, refused) {
|
|
2483
|
+
const poison = poisonFrameError(head.frame);
|
|
2484
|
+
if (poison === null) return false;
|
|
2485
|
+
const installation2 = state.installations[installationKey(head)];
|
|
2486
|
+
if (!hasIntactAdmissionEnvelope(head, installation2)) throw new TypeError("Host Relay admission state is invalid.");
|
|
2487
|
+
const json = Buffer.from(JSON.stringify(head.frame), "utf8");
|
|
2488
|
+
const raw = Buffer.allocUnsafe(json.byteLength + 4);
|
|
2489
|
+
raw.writeUInt32BE(json.byteLength, 0);
|
|
2490
|
+
json.copy(raw, 4);
|
|
2491
|
+
await this.quarantine.retainMalformed(raw, poison.code);
|
|
2492
|
+
const submissionRef = hostProducerSubmissionRef(options.host_source_ref, { producer_id: head.producer_id, producer_installation_ref: head.producer_installation_ref, sequence: head.sequence });
|
|
2493
|
+
installation2.last_refusal = { sequence: head.sequence, submission_ref: submissionRef, code: poison.code, message: `Host Relay persisted frame is invalid: ${poison.code}.`, refused_at_ms: options.now_ms };
|
|
2494
|
+
refused.push(submissionRef);
|
|
2495
|
+
state.entries.splice(state.entries.indexOf(head), 1);
|
|
2496
|
+
installation2.delivered_through_sequence = Math.max(installation2.delivered_through_sequence, head.sequence);
|
|
2497
|
+
await this.write(state);
|
|
2498
|
+
return true;
|
|
2499
|
+
}
|
|
2461
2500
|
async read() {
|
|
2462
2501
|
try {
|
|
2463
2502
|
return decodeState3(JSON.parse(await readFile2(join4(this.root, "admission.json"), "utf8")));
|
|
@@ -2523,6 +2562,8 @@ function decodeState3(value) {
|
|
|
2523
2562
|
const entries = [];
|
|
2524
2563
|
for (const item of value.entries) {
|
|
2525
2564
|
if (!entry(item)) throw new TypeError("Host Relay entry state is invalid.");
|
|
2565
|
+
poisonFrameError(item.frame);
|
|
2566
|
+
if (!hasIntactAdmissionEnvelope(item, installations[installationKey(item)])) throw new TypeError("Host Relay admission state is invalid.");
|
|
2526
2567
|
entries.push(item);
|
|
2527
2568
|
}
|
|
2528
2569
|
return { schema_version: "ceal.host_relay_admission_state.v1", installations, entries };
|
|
@@ -2545,6 +2586,60 @@ function producer2(value) {
|
|
|
2545
2586
|
function object(value) {
|
|
2546
2587
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2547
2588
|
}
|
|
2589
|
+
function poisonFrameError(frame) {
|
|
2590
|
+
try {
|
|
2591
|
+
assertHostProducerFrame(frame);
|
|
2592
|
+
return null;
|
|
2593
|
+
} catch (error) {
|
|
2594
|
+
if (error instanceof HostProducerFrameError) return error;
|
|
2595
|
+
throw error;
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
function hasIntactAdmissionEnvelope(entryValue, installation2) {
|
|
2599
|
+
if (installation2 === void 0) return false;
|
|
2600
|
+
return poisonIdentityValid(entryValue) && poisonEnvelopeMatches(entryValue, installation2) && poisonIntegrityMatches(entryValue) && poisonSequenceInRange(entryValue, installation2);
|
|
2601
|
+
}
|
|
2602
|
+
function poisonIdentityValid(entryValue) {
|
|
2603
|
+
if (entryValue.producer_id !== "codex" && entryValue.producer_id !== "claude") return false;
|
|
2604
|
+
if (!opaqueHostSourceRef(entryValue.producer_installation_ref)) return false;
|
|
2605
|
+
return Number.isSafeInteger(entryValue.sequence) && entryValue.sequence > 0;
|
|
2606
|
+
}
|
|
2607
|
+
function poisonEnvelopeMatches(entryValue, installation2) {
|
|
2608
|
+
const frame = entryValue.frame;
|
|
2609
|
+
if (!object(frame)) return false;
|
|
2610
|
+
return poisonEnvelopeSchema(frame) && poisonEnvelopeProducer(frame, entryValue) && poisonEnvelopeInstallation(frame, entryValue) && poisonEnvelopeEpoch(frame, installation2) && poisonEnvelopeSequence(frame, entryValue) && poisonEnvelopeKind(frame) && poisonEnvelopeTimestamp(frame);
|
|
2611
|
+
}
|
|
2612
|
+
function poisonEnvelopeSchema(frame) {
|
|
2613
|
+
return frame.schema_version === "ceal.host_producer_frame.v1";
|
|
2614
|
+
}
|
|
2615
|
+
function poisonEnvelopeProducer(frame, entryValue) {
|
|
2616
|
+
return producer2(frame.producer_id) && frame.producer_id === entryValue.producer_id;
|
|
2617
|
+
}
|
|
2618
|
+
function poisonEnvelopeInstallation(frame, entryValue) {
|
|
2619
|
+
return opaqueHostSourceRef(frame.producer_installation_ref) && frame.producer_installation_ref === entryValue.producer_installation_ref;
|
|
2620
|
+
}
|
|
2621
|
+
function poisonEnvelopeEpoch(frame, installation2) {
|
|
2622
|
+
return Number.isSafeInteger(frame.producer_epoch) && typeof frame.producer_epoch === "number" && frame.producer_epoch > 0 && frame.producer_epoch <= installation2.epoch;
|
|
2623
|
+
}
|
|
2624
|
+
function poisonEnvelopeSequence(frame, entryValue) {
|
|
2625
|
+
return Number.isSafeInteger(frame.sequence) && frame.sequence === entryValue.sequence;
|
|
2626
|
+
}
|
|
2627
|
+
function poisonEnvelopeKind(frame) {
|
|
2628
|
+
return isHostProducerFrameKind(frame.frame_kind);
|
|
2629
|
+
}
|
|
2630
|
+
function poisonEnvelopeTimestamp(frame) {
|
|
2631
|
+
return typeof frame.occurred_at === "string" && Number.isFinite(Date.parse(frame.occurred_at));
|
|
2632
|
+
}
|
|
2633
|
+
function poisonIntegrityMatches(entryValue) {
|
|
2634
|
+
const frame = entryValue.frame;
|
|
2635
|
+
if (!object(frame)) return false;
|
|
2636
|
+
const json = JSON.stringify(frame);
|
|
2637
|
+
return entryValue.charged_bytes === 4 + Buffer.byteLength(json, "utf8") && entryValue.hash === hostProducerFrameHash(entryValue.frame);
|
|
2638
|
+
}
|
|
2639
|
+
function poisonSequenceInRange(entryValue, installation2) {
|
|
2640
|
+
if (entryValue.sequence <= installation2.delivered_through_sequence) return false;
|
|
2641
|
+
return entryValue.sequence <= installation2.admitted_through_sequence;
|
|
2642
|
+
}
|
|
2548
2643
|
|
|
2549
2644
|
// host/relay/src/relay-gateway-bridge.ts
|
|
2550
2645
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
@@ -2889,20 +2984,29 @@ var HostRelayGatewayError = class extends Error {
|
|
|
2889
2984
|
status;
|
|
2890
2985
|
/** The Gateway's own refusal word, when it named one; a bounded token, never its message. */
|
|
2891
2986
|
error_code;
|
|
2892
|
-
|
|
2987
|
+
/** The Gateway mint refusal's bounded continuity reason, when it named one; a fixed token, never its message. */
|
|
2988
|
+
refusal_reason;
|
|
2989
|
+
constructor(code, message, status, errorCode9, refusalReason) {
|
|
2893
2990
|
super(message);
|
|
2894
2991
|
this.code = code;
|
|
2895
2992
|
if (status !== void 0) this.status = status;
|
|
2896
2993
|
if (errorCode9 !== void 0) this.error_code = errorCode9;
|
|
2994
|
+
if (refusalReason !== void 0) this.refusal_reason = refusalReason;
|
|
2897
2995
|
}
|
|
2898
2996
|
};
|
|
2899
2997
|
var GATEWAY_REFUSAL_CODE_PATTERN = /^[a-z][a-z0-9_]{0,63}$/u;
|
|
2900
|
-
async function
|
|
2998
|
+
async function gatewayRefusal(response2) {
|
|
2901
2999
|
try {
|
|
2902
3000
|
const body = await response2.json();
|
|
2903
|
-
|
|
3001
|
+
if (!isCealJsonRecord(body)) return {};
|
|
3002
|
+
const code = typeof body.error_code === "string" && GATEWAY_REFUSAL_CODE_PATTERN.test(body.error_code) ? body.error_code : void 0;
|
|
3003
|
+
const reason = code === "request_context_refused" && typeof body.reason === "string" && GATEWAY_REFUSAL_CODE_PATTERN.test(body.reason) && isVocabularyWord(CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS, body.reason) ? body.reason : void 0;
|
|
3004
|
+
return {
|
|
3005
|
+
...code === void 0 ? {} : { code },
|
|
3006
|
+
...reason === void 0 ? {} : { reason }
|
|
3007
|
+
};
|
|
2904
3008
|
} catch {
|
|
2905
|
-
return
|
|
3009
|
+
return {};
|
|
2906
3010
|
}
|
|
2907
3011
|
}
|
|
2908
3012
|
function refusalMessage(status, body) {
|
|
@@ -2950,7 +3054,10 @@ function createHostRelayGatewayHttpPort(options) {
|
|
|
2950
3054
|
} catch {
|
|
2951
3055
|
throw new HostRelayGatewayError("gateway_unavailable", "Gateway Host invocation context is unavailable.");
|
|
2952
3056
|
}
|
|
2953
|
-
if (!response2.ok)
|
|
3057
|
+
if (!response2.ok) {
|
|
3058
|
+
const refusal = await gatewayRefusal(response2);
|
|
3059
|
+
throw new HostRelayGatewayError(response2.status === 401 ? "credential" : "gateway_refused", `Gateway Host invocation context was refused (${response2.status}).`, response2.status, refusal.code, refusal.reason);
|
|
3060
|
+
}
|
|
2954
3061
|
let value;
|
|
2955
3062
|
try {
|
|
2956
3063
|
value = await response2.json();
|
|
@@ -3071,8 +3178,9 @@ function acknowledgement(value) {
|
|
|
3071
3178
|
}
|
|
3072
3179
|
|
|
3073
3180
|
// host/relay/src/relay-ipc.ts
|
|
3074
|
-
import { chmod, lstat as lstat3, unlink as unlink2 } from "node:fs/promises";
|
|
3181
|
+
import { chmod, lstat as lstat3, rename as rename2, unlink as unlink2 } from "node:fs/promises";
|
|
3075
3182
|
import { createConnection, createServer } from "node:net";
|
|
3183
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
3076
3184
|
|
|
3077
3185
|
// host/relay/src/current-prompt-custody.ts
|
|
3078
3186
|
import { createHash as createHash5 } from "node:crypto";
|
|
@@ -3153,7 +3261,15 @@ var HOST_RELAY_INVOCATION_MINT_REFUSAL_REASONS = vocabulary([
|
|
|
3153
3261
|
"deadline",
|
|
3154
3262
|
"cancelled",
|
|
3155
3263
|
"mint_failed",
|
|
3156
|
-
"relay_failed"
|
|
3264
|
+
"relay_failed",
|
|
3265
|
+
"request_authority_unavailable",
|
|
3266
|
+
"request_missing",
|
|
3267
|
+
"authority_mismatch",
|
|
3268
|
+
"host_source_mismatch",
|
|
3269
|
+
"host_session_mismatch",
|
|
3270
|
+
"host_turn_mismatch",
|
|
3271
|
+
"host_input_mismatch",
|
|
3272
|
+
"host_prompt_mismatch"
|
|
3157
3273
|
]);
|
|
3158
3274
|
var HostRelayInvocationMintRefusal = class extends Error {
|
|
3159
3275
|
reason;
|
|
@@ -3185,15 +3301,42 @@ var HostRelayIpcError = class extends Error {
|
|
|
3185
3301
|
}
|
|
3186
3302
|
};
|
|
3187
3303
|
async function startHostRelayIpcServer(options) {
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3304
|
+
return withRelayEndpointLock(options.endpoint, async () => {
|
|
3305
|
+
if (process.platform === "win32") {
|
|
3306
|
+
await assertEndpointVacant(options.endpoint);
|
|
3307
|
+
} else {
|
|
3308
|
+
await assertTrustedEndpointParent(options.endpoint);
|
|
3309
|
+
await removeStaleRelayEndpoint(options.endpoint, options.testHooks);
|
|
3310
|
+
}
|
|
3311
|
+
const active = /* @__PURE__ */ new Map();
|
|
3312
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
3313
|
+
const quarantine = options.quarantine ?? options.admission.quarantine;
|
|
3314
|
+
const currentPrompts = options.currentPrompts ?? new HostCurrentPromptCustody();
|
|
3315
|
+
const server = createServer((socket) => serveSocket(socket, options, currentPrompts, quarantine, active, sockets));
|
|
3316
|
+
if (process.platform === "win32") {
|
|
3317
|
+
try {
|
|
3318
|
+
await listen(server, options.endpoint);
|
|
3319
|
+
} catch (error) {
|
|
3320
|
+
await abandonRelayServer(server, sockets);
|
|
3321
|
+
throw error;
|
|
3322
|
+
}
|
|
3323
|
+
return { endpoint: options.endpoint, close: () => closeServer(server, sockets, options.endpoint) };
|
|
3324
|
+
}
|
|
3325
|
+
const staging = stagingRelayEndpoint(options.endpoint);
|
|
3326
|
+
await listenStagingRelayEndpoint(server, sockets, staging);
|
|
3327
|
+
let owned;
|
|
3328
|
+
try {
|
|
3329
|
+
await options.testHooks?.beforeCaptureOwnedEndpoint?.(options.endpoint);
|
|
3330
|
+
owned = await readStagingRelayEndpointIdentity(staging);
|
|
3331
|
+
await chmod(staging, 384);
|
|
3332
|
+
const identity = await publishStagingRelayEndpoint(staging, owned, options.endpoint);
|
|
3333
|
+
return { endpoint: options.endpoint, close: () => closeRelayServer(server, sockets, options.endpoint, identity, options.testHooks) };
|
|
3334
|
+
} catch (error) {
|
|
3335
|
+
if (owned !== void 0) await releaseOwnedRelayEndpoint(server, sockets, options.endpoint, owned);
|
|
3336
|
+
else await abandonRelayServer(server, sockets);
|
|
3337
|
+
throw error;
|
|
3338
|
+
}
|
|
3339
|
+
});
|
|
3197
3340
|
}
|
|
3198
3341
|
function encodeHostRelayIpcResponse(response2) {
|
|
3199
3342
|
const body = Buffer.from(JSON.stringify(response2), "utf8");
|
|
@@ -3355,8 +3498,9 @@ function writeCurrentPromptResponse(socket, status) {
|
|
|
3355
3498
|
async function resolveInvocationContext(request2, admission, bindings, currentPrompts, mint, signal) {
|
|
3356
3499
|
if (mint === void 0) throw new HostRelayIpcError("relay_unavailable");
|
|
3357
3500
|
const query = bindingQueryFromRequest(request2);
|
|
3501
|
+
const expectedSequences = pendingAdmissionSequences(await admission.snapshot(), query);
|
|
3358
3502
|
const binding = await waitForRequestBinding(query, admission, bindings, signal);
|
|
3359
|
-
if (binding.status !== "ready") return { schema_version: HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA, status: "no_binding" };
|
|
3503
|
+
if (binding.status !== "ready") return unboundInvocationContextRefusal(await admission.snapshot(), query, expectedSequences) ?? { schema_version: HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA, status: "no_binding" };
|
|
3360
3504
|
const currentPrompt = currentPrompts.take({ producer_id: request2.producer_id, producer_installation_ref: request2.producer_installation_ref, local_session_ref: request2.local_session_ref, local_turn_ref: request2.local_turn_ref, prompt_ref: binding.binding.prompt_ref });
|
|
3361
3505
|
let result2;
|
|
3362
3506
|
try {
|
|
@@ -3366,6 +3510,24 @@ async function resolveInvocationContext(request2, admission, bindings, currentPr
|
|
|
3366
3510
|
}
|
|
3367
3511
|
return { schema_version: HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA, status: "ready", result: result2 };
|
|
3368
3512
|
}
|
|
3513
|
+
function unboundInvocationContextRefusal(snapshot, query, expectedSequences) {
|
|
3514
|
+
if (expectedSequences.length === 0) return null;
|
|
3515
|
+
const refusal = snapshot.installations[`${query.producer_id}\0${query.producer_installation_ref}`]?.last_refusal;
|
|
3516
|
+
if (refusal === void 0 || !expectedSequences.includes(refusal.sequence) || refusal.code !== "gateway_refused") return null;
|
|
3517
|
+
return { schema_version: HOST_RELAY_INVOCATION_CONTEXT_RESULT_SCHEMA, status: "refused", reason: "gateway_refused" };
|
|
3518
|
+
}
|
|
3519
|
+
function pendingAdmissionSequences(snapshot, query) {
|
|
3520
|
+
const sequences = [];
|
|
3521
|
+
for (const entry2 of snapshot.entries) {
|
|
3522
|
+
if (entry2.frame.frame_kind !== "user_input") continue;
|
|
3523
|
+
const payload = entry2.frame.payload;
|
|
3524
|
+
if (typeof payload.local_session_ref !== "string" || typeof payload.local_turn_ref !== "string") continue;
|
|
3525
|
+
const aliases = Array.isArray(payload.local_turn_aliases) ? payload.local_turn_aliases.filter((value) => typeof value === "string") : [];
|
|
3526
|
+
if (!relayRequestBindingMatchesQuery(query, { producer_id: entry2.frame.producer_id, producer_installation_ref: entry2.frame.producer_installation_ref, local_session_ref: payload.local_session_ref, local_turn_ref: payload.local_turn_ref, local_turn_aliases: aliases })) continue;
|
|
3527
|
+
sequences.push(entry2.sequence);
|
|
3528
|
+
}
|
|
3529
|
+
return sequences;
|
|
3530
|
+
}
|
|
3369
3531
|
async function waitForRequestBinding(query, admission, bindings, signal) {
|
|
3370
3532
|
let binding = await bindings.query(query);
|
|
3371
3533
|
if (binding.status === "ready") return binding;
|
|
@@ -3541,6 +3703,160 @@ async function closeServer(server, sockets, endpoint) {
|
|
|
3541
3703
|
await new Promise((resolve) => server.close(() => resolve()));
|
|
3542
3704
|
if (process.platform !== "win32") await unlink2(endpoint).catch(() => void 0);
|
|
3543
3705
|
}
|
|
3706
|
+
var relayEndpointLocks = /* @__PURE__ */ new Map();
|
|
3707
|
+
function withRelayEndpointLock(endpoint, run) {
|
|
3708
|
+
const prior = relayEndpointLocks.get(endpoint) ?? Promise.resolve();
|
|
3709
|
+
let release;
|
|
3710
|
+
const gate = new Promise((resolve) => {
|
|
3711
|
+
release = resolve;
|
|
3712
|
+
});
|
|
3713
|
+
const tail = prior.then(() => gate);
|
|
3714
|
+
relayEndpointLocks.set(endpoint, tail);
|
|
3715
|
+
return prior.then(run).finally(() => {
|
|
3716
|
+
release?.();
|
|
3717
|
+
if (relayEndpointLocks.get(endpoint) === tail) relayEndpointLocks.delete(endpoint);
|
|
3718
|
+
});
|
|
3719
|
+
}
|
|
3720
|
+
function currentUid(fallback) {
|
|
3721
|
+
return typeof process.geteuid === "function" ? process.geteuid() : fallback;
|
|
3722
|
+
}
|
|
3723
|
+
async function assertTrustedEndpointParent(endpoint) {
|
|
3724
|
+
const parent = dirname3(endpoint);
|
|
3725
|
+
let stats;
|
|
3726
|
+
try {
|
|
3727
|
+
stats = await lstat3(parent);
|
|
3728
|
+
} catch {
|
|
3729
|
+
throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC endpoint parent is unavailable; leaving it untouched.");
|
|
3730
|
+
}
|
|
3731
|
+
if (!stats.isDirectory() || stats.uid !== currentUid(stats.uid) || (stats.mode & 63) !== 0) throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC endpoint parent is not an owner-private directory; leaving it untouched.");
|
|
3732
|
+
}
|
|
3733
|
+
var HOST_RELAY_ENDPOINT_PROBE_TIMEOUT_MS = 1e3;
|
|
3734
|
+
function probeRelayEndpoint(endpoint) {
|
|
3735
|
+
return new Promise((resolve) => {
|
|
3736
|
+
let settled = false;
|
|
3737
|
+
const socket = createConnection(endpoint);
|
|
3738
|
+
const finish = (outcome) => {
|
|
3739
|
+
if (settled) return;
|
|
3740
|
+
settled = true;
|
|
3741
|
+
clearTimeout(timer);
|
|
3742
|
+
socket.destroy();
|
|
3743
|
+
resolve(outcome);
|
|
3744
|
+
};
|
|
3745
|
+
const timer = setTimeout(() => finish("ambiguous"), HOST_RELAY_ENDPOINT_PROBE_TIMEOUT_MS);
|
|
3746
|
+
timer.unref?.();
|
|
3747
|
+
socket.once("connect", () => finish("live"));
|
|
3748
|
+
socket.once("error", (error) => {
|
|
3749
|
+
const code = errorCode8(error);
|
|
3750
|
+
if (code === "ECONNREFUSED") finish("stale");
|
|
3751
|
+
else if (code === "ENOENT") finish("missing");
|
|
3752
|
+
else finish("ambiguous");
|
|
3753
|
+
});
|
|
3754
|
+
});
|
|
3755
|
+
}
|
|
3756
|
+
async function removeStaleRelayEndpoint(endpoint, testHooks) {
|
|
3757
|
+
const observed = await readEndpointStats(endpoint);
|
|
3758
|
+
if (observed === null) return;
|
|
3759
|
+
if (!observed.isSocket()) throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC endpoint is already present and is not a socket; leaving it untouched.");
|
|
3760
|
+
if (observed.uid !== currentUid(observed.uid)) throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC endpoint is not owned by the current uid; leaving it untouched.");
|
|
3761
|
+
const captured = { uid: observed.uid, dev: observed.dev, ino: observed.ino };
|
|
3762
|
+
await requireStaleEndpoint(endpoint);
|
|
3763
|
+
await testHooks?.beforeRemoveStaleEndpoint?.(endpoint);
|
|
3764
|
+
const current = await readEndpointStats(endpoint);
|
|
3765
|
+
if (current === null) return;
|
|
3766
|
+
requireSameEndpointIdentity(current, captured, "Host Relay IPC endpoint changed before cleanup; leaving it untouched.");
|
|
3767
|
+
await unlinkRelayEndpoint(endpoint);
|
|
3768
|
+
}
|
|
3769
|
+
async function requireStaleEndpoint(endpoint) {
|
|
3770
|
+
switch (await probeRelayEndpoint(endpoint)) {
|
|
3771
|
+
case "missing":
|
|
3772
|
+
case "stale":
|
|
3773
|
+
return;
|
|
3774
|
+
case "live":
|
|
3775
|
+
throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC endpoint is already served by a live Relay; leaving it untouched.");
|
|
3776
|
+
case "ambiguous":
|
|
3777
|
+
throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC endpoint liveness is ambiguous; leaving it untouched.");
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
async function readEndpointStats(endpoint) {
|
|
3781
|
+
try {
|
|
3782
|
+
return await lstat3(endpoint);
|
|
3783
|
+
} catch (error) {
|
|
3784
|
+
if (errorCode8(error) === "ENOENT") return null;
|
|
3785
|
+
throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC endpoint identity is ambiguous; leaving it untouched.");
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
function requireSameEndpointIdentity(current, owned, message) {
|
|
3789
|
+
if (!current.isSocket() || current.uid !== owned.uid || current.dev !== owned.dev || current.ino !== owned.ino) throw new HostRelayIpcError("relay_unavailable", message);
|
|
3790
|
+
}
|
|
3791
|
+
async function unlinkRelayEndpoint(endpoint) {
|
|
3792
|
+
try {
|
|
3793
|
+
await unlink2(endpoint);
|
|
3794
|
+
} catch (error) {
|
|
3795
|
+
if (errorCode8(error) !== "ENOENT") throw error;
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
var relayStagingSequence = 0;
|
|
3799
|
+
function stagingRelayEndpoint(canonical) {
|
|
3800
|
+
const parent = dirname3(canonical);
|
|
3801
|
+
const pid = process.pid.toString(36);
|
|
3802
|
+
const sequence = (relayStagingSequence++ % (36 * 36)).toString(36);
|
|
3803
|
+
return join6(parent, `.r${pid}${sequence}`);
|
|
3804
|
+
}
|
|
3805
|
+
async function readStagingRelayEndpointIdentity(staging) {
|
|
3806
|
+
let created;
|
|
3807
|
+
try {
|
|
3808
|
+
created = await lstat3(staging);
|
|
3809
|
+
} catch {
|
|
3810
|
+
throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC endpoint is unavailable after listen; leaving it untouched.");
|
|
3811
|
+
}
|
|
3812
|
+
if (!created.isSocket()) throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC endpoint was replaced before this server listened; leaving it untouched.");
|
|
3813
|
+
if (created.uid !== currentUid(created.uid)) throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC endpoint is not owned by the current uid after listen; leaving it untouched.");
|
|
3814
|
+
return { uid: created.uid, dev: created.dev, ino: created.ino };
|
|
3815
|
+
}
|
|
3816
|
+
async function listenStagingRelayEndpoint(server, sockets, staging) {
|
|
3817
|
+
try {
|
|
3818
|
+
await listen(server, staging);
|
|
3819
|
+
} catch (error) {
|
|
3820
|
+
await abandonRelayServer(server, sockets);
|
|
3821
|
+
throw error;
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
async function publishStagingRelayEndpoint(staging, owned, canonical) {
|
|
3825
|
+
const incumbent = await readEndpointStats(canonical);
|
|
3826
|
+
if (incumbent !== null) throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC endpoint was replaced before this server listened; leaving it untouched.");
|
|
3827
|
+
await rename2(staging, canonical);
|
|
3828
|
+
const published = await readEndpointStats(canonical);
|
|
3829
|
+
if (published === null) throw new HostRelayIpcError("relay_unavailable", "Host Relay IPC endpoint is unavailable after listen; leaving it untouched.");
|
|
3830
|
+
requireSameEndpointIdentity(published, owned, "Host Relay IPC endpoint was replaced before this server listened; leaving it untouched.");
|
|
3831
|
+
return { uid: published.uid, dev: published.dev, ino: published.ino };
|
|
3832
|
+
}
|
|
3833
|
+
async function releaseOwnedRelayEndpoint(server, sockets, endpoint, owned) {
|
|
3834
|
+
await abandonRelayServer(server, sockets);
|
|
3835
|
+
try {
|
|
3836
|
+
const current = await readEndpointStats(endpoint);
|
|
3837
|
+
if (current === null) return;
|
|
3838
|
+
requireSameEndpointIdentity(current, owned, "Host Relay IPC endpoint changed while releasing a failed startup; leaving it untouched.");
|
|
3839
|
+
await unlinkRelayEndpoint(endpoint);
|
|
3840
|
+
} catch {
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
async function closeRelayServer(server, sockets, endpoint, owned, testHooks) {
|
|
3844
|
+
await withRelayEndpointLock(endpoint, async () => {
|
|
3845
|
+
for (const socket of sockets) socket.destroy();
|
|
3846
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
3847
|
+
await testHooks?.beforeRemoveOwnedEndpoint?.(endpoint);
|
|
3848
|
+
const current = await readEndpointStats(endpoint);
|
|
3849
|
+
if (current === null) return;
|
|
3850
|
+
requireSameEndpointIdentity(current, owned, "Host Relay IPC endpoint was replaced after this server listened; leaving it untouched.");
|
|
3851
|
+
await unlinkRelayEndpoint(endpoint);
|
|
3852
|
+
});
|
|
3853
|
+
}
|
|
3854
|
+
async function abandonRelayServer(server, sockets) {
|
|
3855
|
+
for (const socket of sockets) socket.destroy();
|
|
3856
|
+
await new Promise((resolve) => {
|
|
3857
|
+
server.close(() => resolve());
|
|
3858
|
+
});
|
|
3859
|
+
}
|
|
3544
3860
|
function errorCode8(error) {
|
|
3545
3861
|
return record4(error) && typeof error.code === "string" ? error.code : null;
|
|
3546
3862
|
}
|
|
@@ -3562,7 +3878,7 @@ async function readHostRelayConfig(path) {
|
|
|
3562
3878
|
if (!isAbsoluteNormalizedNonRootPath(path, process.platform, true)) throw new HostRelayConfigError("invalid_config_path", `invalid_config_path: Host Relay config path is invalid: ${refusalFact("config_path", path, "absolute, normalized, and non-root")}.`);
|
|
3563
3879
|
let handle;
|
|
3564
3880
|
try {
|
|
3565
|
-
await assertOwnerPrivateDirectory2(
|
|
3881
|
+
await assertOwnerPrivateDirectory2(dirname4(path));
|
|
3566
3882
|
handle = await open5(path, O_RDONLY4 | O_NOFOLLOW3);
|
|
3567
3883
|
const stats = await handle.stat();
|
|
3568
3884
|
if (!stats.isFile() || !ownerPrivate4(stats)) throw new HostRelayConfigError("config_not_private", "Host Relay config file is not owner-private.");
|
|
@@ -3587,7 +3903,7 @@ async function readHostRelayConfig(path) {
|
|
|
3587
3903
|
}
|
|
3588
3904
|
function validateHostRelaySourcePaths(config) {
|
|
3589
3905
|
try {
|
|
3590
|
-
assertHostRelaySourcePaths(
|
|
3906
|
+
assertHostRelaySourcePaths(dirname4(config.registration_state_file), config);
|
|
3591
3907
|
} catch (error) {
|
|
3592
3908
|
throw new HostRelayConfigError("invalid_config", error instanceof Error ? error.message : "Host Relay source tuple is invalid.");
|
|
3593
3909
|
}
|
|
@@ -3617,10 +3933,10 @@ function validConfigRuntime(value) {
|
|
|
3617
3933
|
var HOST_RELAY_INVOCATION_MINT_LOG_TAG = "[host-relay-invocation-mint]";
|
|
3618
3934
|
var HOST_RELAY_INVOCATION_MINT_LINE = new RegExp(`^${HOST_RELAY_INVOCATION_MINT_LOG_TAG.replace(/[[\]]/gu, "\\$&")} outcome=([a-z_]+)(?: reason=([a-z][a-z0-9_]{0,63}))? elapsed_ms=[0-9]+$`, "u");
|
|
3619
3935
|
async function serveHostRelay(config, signal, dependencies = {}) {
|
|
3620
|
-
const admission = new RelayAdmissionStore(
|
|
3621
|
-
const bindings = new RelayRequestBindingStore(
|
|
3936
|
+
const admission = new RelayAdmissionStore(join7(config.state_root, "admission"));
|
|
3937
|
+
const bindings = new RelayRequestBindingStore(join7(config.state_root, "bindings"));
|
|
3622
3938
|
const gateway = (dependencies.createGateway ?? createHostRelayGatewayHttpPort)({ registration_state_file: config.registration_state_file, host_source_ref: config.host_source_ref });
|
|
3623
|
-
const bridge = new RelayGatewayBridge({ state_root:
|
|
3939
|
+
const bridge = new RelayGatewayBridge({ state_root: join7(config.state_root, "gateway"), host_source_ref: config.host_source_ref, gateway, timeout_ms: config.gateway_timeout_ms, on_request_bound: async (binding) => {
|
|
3624
3940
|
await bindings.bind(binding);
|
|
3625
3941
|
} });
|
|
3626
3942
|
let server;
|
|
@@ -3661,7 +3977,11 @@ function invocationMintRefusalReason(error, cancellation) {
|
|
|
3661
3977
|
if (error.code === "gateway_unavailable") return "gateway_unavailable";
|
|
3662
3978
|
if (error.code === "invalid_response") return "gateway_response_invalid";
|
|
3663
3979
|
if (error.code !== "gateway_refused") return "mint_failed";
|
|
3664
|
-
return (error
|
|
3980
|
+
return gatewayMintRefusalReason(error);
|
|
3981
|
+
}
|
|
3982
|
+
function gatewayMintRefusalReason(error) {
|
|
3983
|
+
if (error.error_code === "request_context_refused" && isVocabularyWord(CEAL_HOST_MINT_REQUEST_CONTEXT_REFUSAL_REASONS, error.refusal_reason)) return error.refusal_reason;
|
|
3984
|
+
return GATEWAY_MINT_REFUSAL_REASONS.get(error.error_code ?? "") ?? "gateway_refused";
|
|
3665
3985
|
}
|
|
3666
3986
|
var GATEWAY_MINT_REFUSAL_REASONS = /* @__PURE__ */ new Map([
|
|
3667
3987
|
["request_context_refused", "context_stale"],
|