diff --git a/.gitignore b/.gitignore index c7b91ce..d5d2d4f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,4 @@ dist/* !dist/*.mjs *.log .DS_Store -package-lock.json progress.md diff --git a/broker/authorization.test.ts b/broker/authorization.test.ts index 880dd5b..b9470c3 100644 --- a/broker/authorization.test.ts +++ b/broker/authorization.test.ts @@ -2,6 +2,15 @@ import assert from "node:assert/strict"; import test from "node:test"; import { authorizeSessionAction, visibleSessions } from "./authorization.ts"; import type { SessionInfo } from "../types.ts"; +import { + BOSS_CAPABILITY_FEATURE_DIGEST, + BOSS_PARTICIPANT_BINDING_VERSION, + BOSS_POLICY_PRINCIPAL_VERSION, + BOSS_POLICY_SEMANTICS_HASH, + BOSS_RUN_FEATURE_CONTRACT, + type BossPolicyRole, +} from "@dataforxyz/agent-intercom-core/boss"; +import { brokerGeneration, participantBindingEpoch } from "@dataforxyz/agent-intercom-core/canonical"; function local(id: string): SessionInfo { return { id, name: id, cwd: "/tmp", model: "test", pid: 1, startedAt: 1, lastActivity: 1, origin: "local" }; @@ -32,6 +41,70 @@ const sessions = [ remote("child-b", "manager"), ]; +function bossSession( + id: string, + role: BossPolicyRole, + bossRunId: string, + options: { managerId?: string; assignedIds?: string[] } = {}, +): SessionInfo { + const bindingEpoch = participantBindingEpoch(1); + const participantId = `participant-${id}`; + const assignedManagerParticipantId = options.managerId === undefined ? undefined : `participant-${options.managerId}`; + const assignedParticipantIds = options.assignedIds?.map((assignedId) => `participant-${assignedId}`); + const principal = { + version: BOSS_POLICY_PRINCIPAL_VERSION, + principalId: id, + principalClass: "boss-private" as const, + state: "active" as const, + bossRunId, + participantId, + role, + bindingEpoch, + ...(assignedManagerParticipantId === undefined ? {} : { assignedManagerParticipantId }), + ...(assignedParticipantIds === undefined ? {} : { assignedParticipantIds }), + }; + const binding = role === "controller" ? undefined : { + version: BOSS_PARTICIPANT_BINDING_VERSION, + bossRunId, + participantId, + role, + communicationProfile: role, + bindingEpoch, + sessionId: id, + brokerGeneration: brokerGeneration(1), + brokerBootInstance: "boot-1", + state: "active" as const, + ...(assignedManagerParticipantId === undefined ? {} : { assignedManagerParticipantId }), + authorityTransitionId: `transition-${id}`, + }; + return { + id, + name: id, + cwd: "/tmp", + model: "test", + pid: 3, + startedAt: 1, + lastActivity: 1, + origin: "local", + boss: { + registration: { + principalId: id, + principalClass: "boss-bound", + state: "active", + bossRunId, + participantId, + bindingEpoch, + featureContract: BOSS_RUN_FEATURE_CONTRACT, + policySemanticsHash: BOSS_POLICY_SEMANTICS_HASH, + capabilityDigest: BOSS_CAPABILITY_FEATURE_DIGEST, + brokerIdentityVerified: true, + }, + principal, + ...(binding === undefined ? {} : { binding }), + }, + }; +} + test("phase one discovery and communication use the same ancestor-chain policy", () => { assert.equal(authorizeSessionAction(sessions, "root", "send", "manager").allowed, true); assert.equal(authorizeSessionAction(sessions, "manager", "ask", "root").allowed, true); @@ -47,3 +120,58 @@ test("visibility hides unauthorized sessions rather than revealing denial detail assert.deepEqual(visibleSessions(sessions, "root").map((session) => session.id).sort(), ["child-a", "child-b", "manager", "root", "unrelated"]); assert.deepEqual(visibleSessions(sessions, "unrelated").map((session) => session.id).sort(), ["root", "unrelated"]); }); + +test("Boss discovery is run-scoped and never downgrades into ordinary local-public routing", () => { + const mixed = [ + local("ordinary"), + bossSession("manager-a", "manager", "run-a", { assignedIds: ["worker-a"] }), + bossSession("worker-a", "worker", "run-a", { managerId: "manager-a" }), + bossSession("manager-b", "manager", "run-b", { assignedIds: [] }), + ]; + + assert.deepEqual(authorizeSessionAction(mixed, "manager-a", "discover", "worker-a"), { + allowed: true, + reason: "communication-profile", + }); + assert.deepEqual(authorizeSessionAction(mixed, "manager-a", "send", "manager-b"), { + allowed: false, + code: "CROSS_RUN_DENIED", + }); + assert.deepEqual(authorizeSessionAction(mixed, "ordinary", "send", "manager-a"), { + allowed: false, + code: "FEATURE_CLASS_DENIED", + }); + assert.deepEqual(visibleSessions(mixed, "manager-a").map((session) => session.id).sort(), ["manager-a", "worker-a"]); +}); + +test("Boss typed control uses the directional Core matrix and exact binding epochs", () => { + const run = [ + bossSession("manager", "manager", "run-a", { assignedIds: ["worker"] }), + bossSession("worker", "worker", "run-a", { managerId: "manager" }), + ]; + assert.deepEqual(authorizeSessionAction(run, "manager", "control", "worker", { + actorBindingEpoch: participantBindingEpoch(1), + targetBindingEpoch: participantBindingEpoch(1), + controlKind: "assignment_request", + correlated: true, + }), { allowed: true, reason: "structured-control" }); + assert.deepEqual(authorizeSessionAction(run, "manager", "control", "worker", { + actorBindingEpoch: participantBindingEpoch(1), + targetBindingEpoch: participantBindingEpoch(1), + controlKind: "decision", + correlated: true, + }), { allowed: false, code: "CONTROL_KIND_DENIED" }); + assert.deepEqual(authorizeSessionAction(run, "manager", "control", "worker", { + actorBindingEpoch: participantBindingEpoch(1), + targetBindingEpoch: participantBindingEpoch(1), + controlKind: "assignment_request", + correlated: false, + }), { allowed: false, code: "CONTROL_REQUIRES_CORRELATION" }); +}); + +test("invalid Boss metadata fails closed instead of becoming an ordinary session", () => { + const corrupt = bossSession("worker", "worker", "run-a", { managerId: "manager" }); + corrupt.boss!.registration.brokerIdentityVerified = false; + const manager = bossSession("manager", "manager", "run-a", { assignedIds: ["worker"] }); + assert.equal(authorizeSessionAction([manager, corrupt], "manager", "discover", "worker").allowed, false); +}); diff --git a/broker/authorization.ts b/broker/authorization.ts index 225b986..2ad57c0 100644 --- a/broker/authorization.ts +++ b/broker/authorization.ts @@ -1,4 +1,12 @@ -import { authorize, type AuthorizationDecision, type PolicyAction, type PolicyPrincipal, type PolicyState } from "@dataforxyz/agent-intercom-core"; +import { + authorizeFeatureAware, + type BossAuthorizationContext, + type BossPolicyAction, + type FeatureAwareAuthorizationDecision, + type FeatureAwarePolicyState, +} from "@dataforxyz/agent-intercom-core/boss"; +import type { PolicyAction, PolicyPrincipal, PolicyState } from "@dataforxyz/agent-intercom-core/policy"; +import { validatedBossMetadata } from "./boss.ts"; import type { SessionInfo } from "../types.ts"; export function policyPrincipalForSession(session: SessionInfo): PolicyPrincipal { @@ -28,23 +36,66 @@ export function policyPrincipalForSession(session: SessionInfo): PolicyPrincipal export function policyStateForSessions(sessions: Iterable): PolicyState { const principals: Record = {}; - for (const session of sessions) principals[session.id] = policyPrincipalForSession(session); + for (const session of sessions) { + if (session.boss === undefined) principals[session.id] = policyPrincipalForSession(session); + } return { principals }; } +export function featurePolicyStateForSessions(sessions: Iterable): FeatureAwarePolicyState { + const values = Array.from(sessions); + const legacy = policyStateForSessions(values); + const registrations: FeatureAwarePolicyState["registrations"] = {}; + const boss: FeatureAwarePolicyState["boss"] = { principals: {} }; + + for (const session of values) { + let metadata; + try { + metadata = validatedBossMetadata(session); + } catch { + // Boss-marked metadata is broker-owned. Corruption must stay in the + // Boss namespace and fail closed rather than downgrade to ordinary. + registrations[session.id] = {} as FeatureAwarePolicyState["registrations"][string]; + continue; + } + if (metadata) { + registrations[session.id] = metadata.registration; + boss.principals[session.id] = metadata.principal; + } else { + registrations[session.id] = { + principalId: session.id, + principalClass: "ordinary", + state: "active", + }; + } + } + return { legacy, boss, registrations }; +} + export function authorizeSessionAction( sessions: Iterable, actorId: string, - action: PolicyAction, + action: PolicyAction | BossPolicyAction, targetId: string, -): AuthorizationDecision { - const state = policyStateForSessions(sessions); - const actor = state.principals[actorId]; - const target = state.principals[targetId]; - return authorize(state, actorId, action, targetId, { - actorGeneration: actor?.generation, - targetGeneration: target?.generation, - }); + bossContext?: BossAuthorizationContext, +): FeatureAwareAuthorizationDecision { + const state = featurePolicyStateForSessions(sessions); + const actorRegistration = state.registrations[actorId]; + const targetRegistration = state.registrations[targetId]; + const request = { + actorId, + action, + targetId, + ...(actorRegistration?.principalClass === "boss-bound" || targetRegistration?.principalClass === "boss-bound" + ? { bossContext } + : { + legacyContext: { + actorGeneration: state.legacy.principals[actorId]?.generation, + targetGeneration: state.legacy.principals[targetId]?.generation, + }, + }), + }; + return authorizeFeatureAware(state, request); } export function visibleSessions(sessions: Iterable, actorId: string): SessionInfo[] { diff --git a/broker/boss-control-store.test.ts b/broker/boss-control-store.test.ts new file mode 100644 index 0000000..4674e38 --- /dev/null +++ b/broker/boss-control-store.test.ts @@ -0,0 +1,528 @@ +import assert from "node:assert/strict"; +import { existsSync, rmSync, writeFileSync } from "node:fs"; +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { participantBindingEpoch } from "@dataforxyz/agent-intercom-core/canonical"; +import { + DURABLE_JSON_FILE_OPERATIONS, + writeDurableJson, + type DurableJsonFileOperations, +} from "../durable-json.ts"; +import { + bossControlFingerprint, + bossControlReplayFrames, + DurableBossControlStore, + type DurableBossControlRecord, +} from "./boss-control-store.ts"; + +const envelope = { + type: "boss.assignment.submitted" as const, + version: 1 as const, + messageId: "control-message-a", + bossRunId: "run-a", + participantId: "worker-a", + bindingEpoch: participantBindingEpoch(2), + causationId: "assignment-a", + replyTo: "control-message-parent", + idempotencyKey: "assignment-a:submission:1", + payload: { assignmentId: "assignment-a", outcome: "complete" }, +}; + +function accepted(overrides: Partial = {}): DurableBossControlRecord { + return { + senderSessionId: "worker-session-a", + bossRunId: envelope.bossRunId, + participantId: envelope.participantId, + senderBindingEpoch: envelope.bindingEpoch, + idempotencyKey: envelope.idempotencyKey, + targetSessionId: "manager-session-a", + targetBindingEpoch: participantBindingEpoch(4), + controlKind: "assignment_response", + envelope, + fingerprint: bossControlFingerprint("manager-session-a", envelope), + deliveryId: "delivery-a", + state: "accepted", + acceptedAt: "2026-07-28T12:00:00.000Z", + ...overrides, + }; +} + +type DurableFaultStage = "write" | "fsync" | "rename" | "restrict" | "directory-fsync"; + +function faultingPersist(stage: DurableFaultStage): (path: string, state: unknown) => void { + return (path, state) => { + let fsyncCalls = 0; + const operations: DurableJsonFileOperations = { + ...DURABLE_JSON_FILE_OPERATIONS, + writeFile(filePath, contents, options) { + if (stage === "write") throw new Error("injected durable write fault"); + DURABLE_JSON_FILE_OPERATIONS.writeFile(filePath, contents, options); + }, + fsync(fileDescriptor) { + fsyncCalls += 1; + if (stage === "fsync" && fsyncCalls === 1) throw new Error("injected durable fsync fault"); + if (stage === "directory-fsync" && fsyncCalls === 2) { + throw new Error("injected durable directory-fsync fault"); + } + DURABLE_JSON_FILE_OPERATIONS.fsync(fileDescriptor); + }, + rename(from, to) { + if (stage === "rename") throw new Error("injected durable rename fault"); + DURABLE_JSON_FILE_OPERATIONS.rename(from, to); + }, + restrict(filePath) { + if (stage === "restrict") throw new Error("injected durable restrict fault"); + DURABLE_JSON_FILE_OPERATIONS.restrict(filePath); + }, + }; + writeDurableJson(path, state, operations); + }; +} + +test("Boss control acceptance and delivery survive broker-process restarts with one delivery ID", async () => { + const root = await mkdtemp(join(tmpdir(), "boss-control-store-")); + try { + const path = join(root, "controls.json"); + const first = new DurableBossControlStore(path); + assert.equal(first.reserve(accepted()).created, true); + assert.equal(new DurableBossControlStore(path).get(accepted())?.state, "accepted"); + + const delivered = new DurableBossControlStore(path).markDelivered(accepted(), "delivery-a", "2026-07-28T12:00:01.000Z"); + assert.equal(delivered.deliveryId, "delivery-a"); + assert.equal(delivered.state, "delivered"); + assert.deepEqual(new DurableBossControlStore(path).get(accepted()), delivered); + assert.deepEqual(bossControlReplayFrames(delivered, "control-message-retry"), [ + { + type: "boss_control_accepted", + messageId: "control-message-retry", + deliveryId: "delivery-a", + }, + { + type: "boss_control_delivered", + messageId: "control-message-retry", + deliveryId: "delivery-a", + }, + ]); + assert.throws( + () => new DurableBossControlStore(path).markRejected(accepted(), "delivery-a", "DELIVERY_TIMEOUT", "late timeout"), + /delivered Boss control cannot become rejected/, + ); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("Boss control idempotency excludes transport messageId and replays to the new request", async () => { + const root = await mkdtemp(join(tmpdir(), "boss-control-idempotency-")); + try { + const path = join(root, "controls.json"); + const store = new DurableBossControlStore(path); + store.reserve(accepted()); + assert.equal(store.reserve({ ...accepted(), deliveryId: "ignored-retry-id" }).record.deliveryId, "delivery-a"); + + const substitutedEnvelope = { ...envelope, messageId: "substituted-message" }; + const replay = store.reserve(accepted({ + envelope: substitutedEnvelope, + fingerprint: bossControlFingerprint("manager-session-a", substitutedEnvelope), + deliveryId: "ignored-new-request-delivery", + })); + assert.equal(replay.created, false); + assert.equal(replay.record.deliveryId, "delivery-a"); + assert.equal(replay.record.envelope.messageId, envelope.messageId); + assert.equal( + bossControlFingerprint("manager-session-a", envelope), + bossControlFingerprint("manager-session-a", substitutedEnvelope), + ); + + const reorderedEnvelope = { + payload: envelope.payload, + idempotencyKey: envelope.idempotencyKey, + replyTo: envelope.replyTo, + causationId: envelope.causationId, + bindingEpoch: envelope.bindingEpoch, + participantId: envelope.participantId, + bossRunId: envelope.bossRunId, + messageId: "property-order-replay", + version: envelope.version, + type: envelope.type, + }; + assert.equal( + bossControlFingerprint("manager-session-a", envelope), + bossControlFingerprint("manager-session-a", reorderedEnvelope), + ); + + const conflictingEnvelope = { + ...substitutedEnvelope, + payload: { assignmentId: "assignment-a", outcome: "different" }, + }; + assert.throws(() => store.reserve(accepted({ + envelope: conflictingEnvelope, + fingerprint: bossControlFingerprint("manager-session-a", conflictingEnvelope), + })), /Conflicting Boss control idempotency replay/); + + const reboundEnvelope = { ...envelope, bindingEpoch: participantBindingEpoch(3) }; + const nextBinding = accepted({ + senderBindingEpoch: participantBindingEpoch(3), + envelope: reboundEnvelope, + fingerprint: bossControlFingerprint("manager-session-a", reboundEnvelope), + deliveryId: "delivery-new-binding", + }); + assert.equal(store.reserve(nextBinding).created, true); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("Boss control rejections persist and corrupt cross-record identity fails closed", async () => { + const root = await mkdtemp(join(tmpdir(), "boss-control-failure-")); + try { + const path = join(root, "controls.json"); + const store = new DurableBossControlStore(path); + store.reserve(accepted()); + const failed = store.markRejected(accepted(), "delivery-a", "DELIVERY_TIMEOUT", "recipient timeout", "2026-07-28T12:00:08.000Z"); + assert.equal(failed.state, "rejected"); + assert.equal(new DurableBossControlStore(path).get(accepted())?.failureCode, "DELIVERY_TIMEOUT"); + + assert.deepEqual(bossControlReplayFrames(failed, "new-request-message"), [ + { + type: "boss_control_accepted", + messageId: "new-request-message", + deliveryId: "delivery-a", + }, + { + type: "boss_control_failed", + messageId: "new-request-message", + deliveryId: "delivery-a", + accepted: true, + code: "DELIVERY_TIMEOUT", + reason: "recipient timeout", + }, + ]); + + const state = JSON.parse(await readFile(path, "utf8")); + const [scope] = Object.keys(state.records); + state.records[scope].participantId = "substituted-worker"; + await writeFile(path, JSON.stringify(state)); + assert.throws(() => new DurableBossControlStore(path), /identity does not match its envelope/); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("Boss control durable records require exact plain data boundaries", async () => { + const root = await mkdtemp(join(tmpdir(), "boss-control-boundaries-")); + try { + const store = new DurableBossControlStore(join(root, "controls.json")); + let getterCalls = 0; + const accessor = { ...accepted() } as Record; + Object.defineProperty(accessor, "targetSessionId", { + enumerable: true, + get() { + getterCalls += 1; + return "manager-session-a"; + }, + }); + assert.throws(() => store.reserve(accessor as unknown as DurableBossControlRecord), /enumerable data property/); + assert.equal(getterCalls, 0); + assert.throws(() => store.reserve({ ...accepted(), unsupported: true } as never), /not supported/); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("Boss control reserve publishes no live or replayable acceptance after write, fsync, or rename faults", async () => { + const root = await mkdtemp(join(tmpdir(), "boss-control-reserve-faults-")); + try { + for (const stage of ["write", "fsync", "rename"] as const) { + const path = join(root, `${stage}.json`); + const store = new DurableBossControlStore(path, faultingPersist(stage)); + assert.throws(() => store.reserve(accepted()), new RegExp(`injected durable ${stage} fault`)); + assert.throws( + () => store.get(accepted()), + /Durable Boss control store is unavailable after commit reconciliation failed/, + `${stage} fault with a missing exact target must poison the live store`, + ); + assert.equal( + new DurableBossControlStore(path).get(accepted()), + undefined, + `${stage} fault must not leave replayable acceptance`, + ); + assert.equal(new DurableBossControlStore(path).reserve(accepted()).created, true); + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("Boss control terminal mutations stay accepted after write, fsync, or rename faults", async () => { + const root = await mkdtemp(join(tmpdir(), "boss-control-terminal-faults-")); + try { + for (const terminal of ["delivered", "rejected"] as const) { + for (const stage of ["write", "fsync", "rename"] as const) { + const path = join(root, `${terminal}-${stage}.json`); + new DurableBossControlStore(path).reserve(accepted()); + const store = new DurableBossControlStore(path, faultingPersist(stage)); + const mutate = terminal === "delivered" + ? () => store.markDelivered(accepted(), "delivery-a", "2026-07-28T12:00:01.000Z") + : () => store.markRejected( + accepted(), + "delivery-a", + "DELIVERY_TIMEOUT", + "recipient timeout", + "2026-07-28T12:00:08.000Z", + ); + assert.throws(mutate, new RegExp(`injected durable ${stage} fault`)); + + const live = store.get(accepted()); + const replayed = new DurableBossControlStore(path).get(accepted()); + assert.equal(live?.state, "accepted", `${terminal}/${stage} fault must preserve live accepted state`); + assert.equal(replayed?.state, "accepted", `${terminal}/${stage} fault must preserve durable accepted state`); + assert.deepEqual(bossControlReplayFrames(replayed!, "replay-after-fault"), [{ + type: "boss_control_accepted", + messageId: "replay-after-fault", + deliveryId: "delivery-a", + }]); + } + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("Boss control reserve reconciles the committed target after restrict or directory fsync faults", async () => { + const root = await mkdtemp(join(tmpdir(), "boss-control-reserve-post-rename-faults-")); + try { + for (const stage of ["restrict", "directory-fsync"] as const) { + const path = join(root, `${stage}.json`); + const store = new DurableBossControlStore(path, faultingPersist(stage)); + assert.throws(() => store.reserve(accepted()), new RegExp(`injected durable ${stage} fault`)); + + const live = store.get(accepted()); + assert.equal(live?.deliveryId, "delivery-a", `${stage} fault must reconcile the renamed acceptance`); + assert.equal(live?.state, "accepted"); + + const retry = store.reserve(accepted({ deliveryId: "conflicting-delivery-id" })); + assert.equal(retry.created, false, `${stage} retry must not create a second reservation`); + assert.equal(retry.record.deliveryId, "delivery-a"); + + const conflictingEnvelope = { + ...envelope, + payload: { assignmentId: "assignment-a", outcome: "conflicting" }, + }; + assert.throws(() => store.reserve(accepted({ + envelope: conflictingEnvelope, + fingerprint: bossControlFingerprint("manager-session-a", conflictingEnvelope), + })), /Conflicting Boss control idempotency replay/); + + assert.deepEqual(new DurableBossControlStore(path).get(accepted()), live); + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("Boss control terminal mutations reconcile without stale replay after restrict or directory fsync faults", async () => { + const root = await mkdtemp(join(tmpdir(), "boss-control-terminal-post-rename-faults-")); + try { + for (const terminal of ["delivered", "rejected"] as const) { + for (const stage of ["restrict", "directory-fsync"] as const) { + const path = join(root, `${terminal}-${stage}.json`); + new DurableBossControlStore(path).reserve(accepted()); + const store = new DurableBossControlStore(path, faultingPersist(stage)); + const mutate = terminal === "delivered" + ? () => store.markDelivered(accepted(), "delivery-a", "2026-07-28T12:00:01.000Z") + : () => store.markRejected( + accepted(), + "delivery-a", + "DELIVERY_TIMEOUT", + "recipient timeout", + "2026-07-28T12:00:08.000Z", + ); + assert.throws(mutate, new RegExp(`injected durable ${stage} fault`)); + + const live = store.get(accepted()); + assert.equal(live?.state, terminal, `${terminal}/${stage} fault must reconcile the renamed terminal state`); + assert.deepEqual(bossControlReplayFrames(live!, "same-process-replay"), terminal === "delivered" + ? [ + { type: "boss_control_accepted", messageId: "same-process-replay", deliveryId: "delivery-a" }, + { type: "boss_control_delivered", messageId: "same-process-replay", deliveryId: "delivery-a" }, + ] + : [ + { type: "boss_control_accepted", messageId: "same-process-replay", deliveryId: "delivery-a" }, + { + type: "boss_control_failed", + messageId: "same-process-replay", + deliveryId: "delivery-a", + accepted: true, + code: "DELIVERY_TIMEOUT", + reason: "recipient timeout", + }, + ]); + if (terminal === "delivered") { + assert.throws( + () => store.markRejected(accepted(), "delivery-a", "DELIVERY_TIMEOUT", "stale timeout"), + /delivered Boss control cannot become rejected/, + ); + } else { + assert.throws( + () => store.markDelivered(accepted(), "delivery-a"), + /rejected Boss control cannot become delivered/, + ); + } + + const replayed = new DurableBossControlStore(path).get(accepted()); + assert.deepEqual(replayed, live, `${terminal}/${stage} restart must load the valid committed state`); + assert.deepEqual( + bossControlReplayFrames(replayed!, "restart-replay"), + bossControlReplayFrames(live!, "restart-replay"), + ); + } + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("Boss control store never publishes the mutable state exposed to its persister", async () => { + const root = await mkdtemp(join(tmpdir(), "boss-control-persist-alias-")); + try { + const path = join(root, "controls.json"); + let retained: { records: Record } | undefined; + const store = new DurableBossControlStore(path, (target, state) => { + writeDurableJson(target, state); + retained = state; + state.records = {}; + }); + + store.reserve(accepted()); + assert.equal(store.get(accepted())?.state, "accepted"); + assert.equal(new DurableBossControlStore(path).get(accepted())?.state, "accepted"); + + retained!.records = {}; + assert.equal(store.get(accepted())?.state, "accepted"); + assert.equal(new DurableBossControlStore(path).get(accepted())?.state, "accepted"); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("Boss control store reconciles exceptions against pre-callback prior and staged snapshots", async () => { + const root = await mkdtemp(join(tmpdir(), "boss-control-persist-snapshots-")); + try { + for (const durableTarget of ["prior", "staged"] as const) { + const path = join(root, `${durableTarget}.json`); + let call = 0; + let retainedPrior: { records: Record } | undefined; + let retainedStaged: { records: Record } | undefined; + const store = new DurableBossControlStore(path, (target, state) => { + call += 1; + if (call === 1) { + writeDurableJson(target, state); + retainedPrior = state; + return; + } + if (durableTarget === "staged") writeDurableJson(target, state); + retainedStaged = state; + retainedPrior!.records = {}; + state.records = {}; + throw new Error(`injected ${durableTarget} exception after mutation`); + }); + + store.reserve(accepted()); + assert.throws( + () => store.markDelivered(accepted(), "delivery-a", "2026-07-28T12:00:01.000Z"), + new RegExp(`injected ${durableTarget} exception after mutation`), + ); + const expectedState = durableTarget === "staged" ? "delivered" : "accepted"; + assert.equal(store.get(accepted())?.state, expectedState); + assert.equal(new DurableBossControlStore(path).get(accepted())?.state, expectedState); + + retainedPrior!.records = {}; + retainedStaged!.records = {}; + assert.equal(store.get(accepted())?.state, expectedState); + assert.equal(new DurableBossControlStore(path).get(accepted())?.state, expectedState); + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("Boss control store poisons after persist exceptions reload missing, corrupt, or foreign targets", async () => { + const root = await mkdtemp(join(tmpdir(), "boss-control-poison-fallbacks-")); + try { + for (const fallback of ["missing", "corrupt", "foreign"] as const) { + const path = join(root, `${fallback}.json`); + new DurableBossControlStore(path).reserve(accepted()); + let persistCalls = 0; + const store = new DurableBossControlStore(path, (target, staged) => { + persistCalls += 1; + if (fallback === "missing") { + rmSync(target, { force: true }); + } else if (fallback === "corrupt") { + writeFileSync(target, "{corrupt", "utf8"); + } else { + const foreign = structuredClone(staged) as { + records: Record; + }; + const [record] = Object.values(foreign.records); + assert(record); + record.deliveredAt = "2026-07-28T12:00:02.000Z"; + writeDurableJson(target, foreign); + } + throw new Error(`injected ${fallback} target persist fault`); + }); + + assert.throws( + () => store.markDelivered(accepted(), "delivery-a", "2026-07-28T12:00:01.000Z"), + new RegExp(`injected ${fallback} target persist fault`), + ); + const targetAfterPoison = existsSync(path) ? await readFile(path, "utf8") : undefined; + const poisoned = /Durable Boss control store is unavailable after commit reconciliation failed/; + assert.throws(() => store.get(accepted()), poisoned, `${fallback} target must not replay through get`); + assert.throws( + () => store.reserve(accepted({ deliveryId: "replay-after-poison" })), + poisoned, + `${fallback} target must not replay through reserve`, + ); + + const nextEnvelope = { ...envelope, bindingEpoch: participantBindingEpoch(3) }; + assert.throws( + () => store.reserve(accepted({ + senderBindingEpoch: nextEnvelope.bindingEpoch, + envelope: nextEnvelope, + fingerprint: bossControlFingerprint("manager-session-a", nextEnvelope), + deliveryId: "new-reservation-after-poison", + })), + poisoned, + `${fallback} target must not accept a new reservation`, + ); + assert.throws( + () => store.markDelivered(accepted(), "delivery-a", "2026-07-28T12:00:03.000Z"), + poisoned, + `${fallback} target must not record delivery`, + ); + assert.throws( + () => store.markRejected( + accepted(), + "delivery-a", + "DELIVERY_TIMEOUT", + "recipient timeout", + "2026-07-28T12:00:04.000Z", + ), + poisoned, + `${fallback} target must not record rejection`, + ); + + assert.equal(persistCalls, 1, `${fallback} target must not be persisted again after poison`); + assert.equal( + existsSync(path) ? await readFile(path, "utf8") : undefined, + targetAfterPoison, + `${fallback} target must remain unchanged after poison`, + ); + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/broker/boss-control-store.ts b/broker/boss-control-store.ts new file mode 100644 index 0000000..d1300c8 --- /dev/null +++ b/broker/boss-control-store.ts @@ -0,0 +1,430 @@ +import { existsSync, readFileSync } from "node:fs"; +import { dirname } from "node:path"; +import { + parseBossControlEnvelope, + type BossControlEnvelope, + type BossControlKind, +} from "@dataforxyz/agent-intercom-core/boss"; +import { + canonicalJson, + assertExactKeys, + assertRecord, + participantBindingEpoch, + type ParticipantBindingEpoch, +} from "@dataforxyz/agent-intercom-core/canonical"; +import { writeDurableJson } from "../durable-json.ts"; +import { ensureIntercomRuntimeDir } from "./paths.ts"; +import { bossControlKind } from "./boss.ts"; +import type { DeliveryFailureCode } from "../types.ts"; + +const STATE_VERSION = 1; + +export type BossControlTerminalFailureCode = Extract< + DeliveryFailureCode, + "BOSS_CONTROL_DENIED" | "RECIPIENT_DISCONNECTED" | "SENDER_DISCONNECTED" | "DELIVERY_TIMEOUT" +>; + +export interface BossControlIdempotencyIdentity { + senderSessionId: string; + bossRunId: string; + participantId: string; + senderBindingEpoch: ParticipantBindingEpoch; + idempotencyKey: string; +} + +export interface DurableBossControlRecord extends BossControlIdempotencyIdentity { + targetSessionId: string; + targetBindingEpoch: ParticipantBindingEpoch; + controlKind: BossControlKind; + envelope: BossControlEnvelope; + fingerprint: string; + deliveryId: string; + state: "accepted" | "delivered" | "rejected"; + acceptedAt: string; + deliveredAt?: string; + failureCode?: BossControlTerminalFailureCode; + failureReason?: string; + rejectedAt?: string; +} + +export type BossControlAcceptedFrame = { + type: "boss_control_accepted"; + messageId: string; + deliveryId: string; +}; + +export type BossControlTerminalFrame = + | { + type: "boss_control_delivered"; + messageId: string; + deliveryId: string; + } + | { + type: "boss_control_failed"; + messageId: string; + deliveryId: string; + accepted: true; + code: BossControlTerminalFailureCode; + reason: string; + }; + +interface BossControlState { + version: typeof STATE_VERSION; + records: Record; +} + +const CONTROL_KINDS: readonly BossControlKind[] = [ + "assignment_request", + "assignment_response", + "health", + "staffing", + "review_request", + "review_result", + "proof", + "lifecycle", + "decision", +]; + +const TERMINAL_FAILURE_CODES: readonly BossControlTerminalFailureCode[] = [ + "BOSS_CONTROL_DENIED", + "RECIPIENT_DISCONNECTED", + "SENDER_DISCONNECTED", + "DELIVERY_TIMEOUT", +]; + +function recordValue(value: unknown, path: string): Record { + assertRecord(value, path); + return value; +} + +function exactKeys(value: Record, required: readonly string[], optional: readonly string[], path: string): void { + assertExactKeys(value, required, optional, path); +} + +function stringValue(value: unknown, path: string): string { + if (typeof value !== "string" || value.length === 0) throw new Error(`${path} must be a non-empty string`); + return value; +} + +function timestampValue(value: unknown, path: string): string { + const timestamp = stringValue(value, path); + if (!Number.isFinite(Date.parse(timestamp))) throw new Error(`${path} must be a timestamp`); + return timestamp; +} + +export function bossControlIdempotencyScope(identity: BossControlIdempotencyIdentity): string { + return canonicalJson({ + senderSessionId: identity.senderSessionId, + bossRunId: identity.bossRunId, + participantId: identity.participantId, + senderBindingEpoch: identity.senderBindingEpoch, + idempotencyKey: identity.idempotencyKey, + }); +} + +export function bossControlFingerprint(targetSessionId: string, envelope: BossControlEnvelope): string { + return canonicalJson({ + targetSessionId, + envelope: { + type: envelope.type, + version: envelope.version, + bossRunId: envelope.bossRunId, + participantId: envelope.participantId, + bindingEpoch: envelope.bindingEpoch, + ...(envelope.causationId === undefined ? {} : { causationId: envelope.causationId }), + ...(envelope.replyTo === undefined ? {} : { replyTo: envelope.replyTo }), + idempotencyKey: envelope.idempotencyKey, + payload: envelope.payload, + }, + }); +} + +export function bossControlAcceptedFrame( + record: DurableBossControlRecord, + requestMessageId: string, +): BossControlAcceptedFrame { + return { + type: "boss_control_accepted", + messageId: stringValue(requestMessageId, "$requestMessageId"), + deliveryId: record.deliveryId, + }; +} + +export function bossControlTerminalFrame( + record: DurableBossControlRecord, + requestMessageId: string, +): BossControlTerminalFrame { + const messageId = stringValue(requestMessageId, "$requestMessageId"); + if (record.state === "delivered") { + return { type: "boss_control_delivered", messageId, deliveryId: record.deliveryId }; + } + if (record.state === "rejected") { + return { + type: "boss_control_failed", + messageId, + deliveryId: record.deliveryId, + accepted: true, + code: record.failureCode!, + reason: record.failureReason!, + }; + } + throw new Error("Accepted Boss control has no terminal result"); +} + +export function bossControlReplayFrames( + record: DurableBossControlRecord, + requestMessageId: string, +): [BossControlAcceptedFrame] | [BossControlAcceptedFrame, BossControlTerminalFrame] { + const accepted = bossControlAcceptedFrame(record, requestMessageId); + return record.state === "accepted" + ? [accepted] + : [accepted, bossControlTerminalFrame(record, requestMessageId)]; +} + +function parseRecord(value: unknown, path: string): DurableBossControlRecord { + const record = recordValue(value, path); + exactKeys(record, [ + "senderSessionId", + "bossRunId", + "participantId", + "senderBindingEpoch", + "idempotencyKey", + "targetSessionId", + "targetBindingEpoch", + "controlKind", + "envelope", + "fingerprint", + "deliveryId", + "state", + "acceptedAt", + ], ["deliveredAt", "failureCode", "failureReason", "rejectedAt"], path); + + const envelope = parseBossControlEnvelope(record.envelope); + const senderSessionId = stringValue(record.senderSessionId, `${path}.senderSessionId`); + const bossRunId = stringValue(record.bossRunId, `${path}.bossRunId`); + const participantId = stringValue(record.participantId, `${path}.participantId`); + const senderBindingEpoch = participantBindingEpoch(record.senderBindingEpoch, `${path}.senderBindingEpoch`); + const idempotencyKey = stringValue(record.idempotencyKey, `${path}.idempotencyKey`); + const targetSessionId = stringValue(record.targetSessionId, `${path}.targetSessionId`); + const targetBindingEpoch = participantBindingEpoch(record.targetBindingEpoch, `${path}.targetBindingEpoch`); + const controlKind = record.controlKind; + if (!CONTROL_KINDS.includes(controlKind as BossControlKind)) throw new Error(`${path}.controlKind is invalid`); + const fingerprint = stringValue(record.fingerprint, `${path}.fingerprint`); + const deliveryId = stringValue(record.deliveryId, `${path}.deliveryId`); + const acceptedAt = timestampValue(record.acceptedAt, `${path}.acceptedAt`); + if ( + envelope.bossRunId !== bossRunId + || envelope.participantId !== participantId + || envelope.bindingEpoch !== senderBindingEpoch + || envelope.idempotencyKey !== idempotencyKey + ) { + throw new Error(`${path} identity does not match its envelope`); + } + if (controlKind !== bossControlKind(envelope.type)) throw new Error(`${path}.controlKind does not match its envelope type`); + if (fingerprint !== bossControlFingerprint(targetSessionId, envelope)) { + throw new Error(`${path}.fingerprint does not match its canonical target and envelope`); + } + + const state = record.state; + if (state !== "accepted" && state !== "delivered" && state !== "rejected") throw new Error(`${path}.state is invalid`); + const deliveredAt = record.deliveredAt === undefined ? undefined : timestampValue(record.deliveredAt, `${path}.deliveredAt`); + const failureCode = record.failureCode as BossControlTerminalFailureCode | undefined; + const failureReason = record.failureReason === undefined ? undefined : stringValue(record.failureReason, `${path}.failureReason`); + const rejectedAt = record.rejectedAt === undefined ? undefined : timestampValue(record.rejectedAt, `${path}.rejectedAt`); + if (state === "accepted" && (deliveredAt !== undefined || failureCode !== undefined || failureReason !== undefined || rejectedAt !== undefined)) { + throw new Error(`${path} accepted record contains terminal evidence`); + } + if (state === "delivered" && (deliveredAt === undefined || failureCode !== undefined || failureReason !== undefined || rejectedAt !== undefined)) { + throw new Error(`${path} delivered record has invalid terminal evidence`); + } + if ( + state === "rejected" + && ( + failureCode === undefined + || !TERMINAL_FAILURE_CODES.includes(failureCode) + || failureReason === undefined + || rejectedAt === undefined + || deliveredAt !== undefined + ) + ) { + throw new Error(`${path} rejected record has invalid terminal evidence`); + } + if (deliveredAt !== undefined && Date.parse(deliveredAt) < Date.parse(acceptedAt)) { + throw new Error(`${path}.deliveredAt precedes acceptance`); + } + if (rejectedAt !== undefined && Date.parse(rejectedAt) < Date.parse(acceptedAt)) { + throw new Error(`${path}.rejectedAt precedes acceptance`); + } + + return { + senderSessionId, + bossRunId, + participantId, + senderBindingEpoch, + idempotencyKey, + targetSessionId, + targetBindingEpoch, + controlKind: controlKind as BossControlKind, + envelope, + fingerprint, + deliveryId, + state, + acceptedAt, + ...(deliveredAt === undefined ? {} : { deliveredAt }), + ...(failureCode === undefined ? {} : { failureCode }), + ...(failureReason === undefined ? {} : { failureReason }), + ...(rejectedAt === undefined ? {} : { rejectedAt }), + }; +} + +function parseState(value: unknown): BossControlState { + const state = recordValue(value, "$bossControls"); + exactKeys(state, ["version", "records"], [], "$bossControls"); + if (state.version !== STATE_VERSION) throw new Error("Unsupported Boss control state version"); + const recordsValue = recordValue(state.records, "$bossControls.records"); + const records: Record = {}; + for (const [scope, value] of Object.entries(recordsValue)) { + const record = parseRecord(value, `$bossControls.records[${JSON.stringify(scope)}]`); + if (scope !== bossControlIdempotencyScope(record)) throw new Error("Boss control scope key does not match its record"); + records[scope] = record; + } + return { version: STATE_VERSION, records }; +} + +function clone(record: DurableBossControlRecord): DurableBossControlRecord { + return structuredClone(record); +} + +export class DurableBossControlStore { + private state: BossControlState; + private readonly persist: (path: string, state: BossControlState) => void; + private poisoned: Error | undefined; + + constructor( + readonly path: string, + persist: (path: string, state: BossControlState) => void = writeDurableJson, + ) { + ensureIntercomRuntimeDir(dirname(path)); + this.persist = persist; + this.state = this.load(); + } + + get(identity: BossControlIdempotencyIdentity): DurableBossControlRecord | undefined { + this.assertUsable(); + const record = this.state.records[bossControlIdempotencyScope(identity)]; + return record === undefined ? undefined : clone(record); + } + + reserve(recordValue: DurableBossControlRecord): { created: boolean; record: DurableBossControlRecord } { + this.assertUsable(); + const record = parseRecord(recordValue, "$record"); + if (record.state !== "accepted") throw new Error("A Boss control reservation must start accepted"); + const scope = bossControlIdempotencyScope(record); + const existing = this.state.records[scope]; + if (existing) { + if ( + existing.fingerprint !== record.fingerprint + || existing.targetBindingEpoch !== record.targetBindingEpoch + || existing.controlKind !== record.controlKind + ) { + throw new Error("Conflicting Boss control idempotency replay"); + } + return { created: false, record: clone(existing) }; + } + const next = structuredClone(this.state); + next.records[scope] = record; + this.commit(next); + return { created: true, record: clone(record) }; + } + + markDelivered(identity: BossControlIdempotencyIdentity, deliveryId: string, deliveredAt = new Date().toISOString()): DurableBossControlRecord { + this.assertUsable(); + const scope = bossControlIdempotencyScope(identity); + const record = this.state.records[scope]; + if (!record || record.deliveryId !== deliveryId) throw new Error("Boss control delivery does not match its durable acceptance"); + if (record.state === "rejected") throw new Error("A rejected Boss control cannot become delivered"); + if (record.state === "delivered") return clone(record); + const updated: DurableBossControlRecord = { + ...record, + state: "delivered", + deliveredAt: timestampValue(deliveredAt, "$deliveredAt"), + }; + parseRecord(updated, "$record"); + const next = structuredClone(this.state); + next.records[scope] = updated; + this.commit(next); + return clone(updated); + } + + markRejected( + identity: BossControlIdempotencyIdentity, + deliveryId: string, + failureCode: BossControlTerminalFailureCode, + failureReason: string, + rejectedAt = new Date().toISOString(), + ): DurableBossControlRecord { + this.assertUsable(); + const scope = bossControlIdempotencyScope(identity); + const record = this.state.records[scope]; + if (!record || record.deliveryId !== deliveryId) throw new Error("Boss control failure does not match its durable acceptance"); + if (record.state === "delivered") throw new Error("A delivered Boss control cannot become rejected"); + if (record.state === "rejected") { + if (record.failureCode !== failureCode || record.failureReason !== failureReason) { + throw new Error("Conflicting terminal Boss control failure"); + } + return clone(record); + } + const updated: DurableBossControlRecord = { + ...record, + state: "rejected", + failureCode, + failureReason: stringValue(failureReason, "$failureReason"), + rejectedAt: timestampValue(rejectedAt, "$rejectedAt"), + }; + parseRecord(updated, "$record"); + const next = structuredClone(this.state); + next.records[scope] = updated; + this.commit(next); + return clone(updated); + } + + private load(): BossControlState { + if (!existsSync(this.path)) return { version: STATE_VERSION, records: {} }; + return parseState(JSON.parse(readFileSync(this.path, "utf8"))); + } + + private loadExactTarget(): BossControlState { + if (!existsSync(this.path)) throw new Error("Durable Boss control target is missing"); + return parseState(JSON.parse(readFileSync(this.path, "utf8"))); + } + + private commit(next: BossControlState): void { + const stagedCanonical = canonicalJson(parseState(structuredClone(next))); + const priorCanonical = canonicalJson(this.state); + try { + this.persist(this.path, parseState(JSON.parse(stagedCanonical))); + } catch (persistError) { + try { + const recovered = this.loadExactTarget(); + const recoveredCanonical = canonicalJson(recovered); + if (recoveredCanonical === stagedCanonical) { + this.state = parseState(JSON.parse(stagedCanonical)); + } else if (recoveredCanonical === priorCanonical) { + this.state = parseState(JSON.parse(priorCanonical)); + } else { + throw new Error("Durable Boss control state does not match the prior or staged commit"); + } + } catch (reconcileError) { + this.poisoned = new Error("Durable Boss control store is unavailable after commit reconciliation failed", { + cause: reconcileError, + }); + } + throw persistError; + } + this.state = parseState(JSON.parse(stagedCanonical)); + } + + private assertUsable(): void { + if (this.poisoned) throw this.poisoned; + } +} diff --git a/broker/boss.test.ts b/broker/boss.test.ts new file mode 100644 index 0000000..d6d4183 --- /dev/null +++ b/broker/boss.test.ts @@ -0,0 +1,90 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { + BOSS_CAPABILITY_FEATURE_DIGEST, + BOSS_PARTICIPANT_BINDING_VERSION, + BOSS_POLICY_PRINCIPAL_VERSION, + BOSS_POLICY_SEMANTICS_HASH, + BOSS_RUN_FEATURE_CONTRACT, +} from "@dataforxyz/agent-intercom-core/boss"; +import { brokerGeneration, participantBindingEpoch } from "@dataforxyz/agent-intercom-core/canonical"; +import { bossControlKind, parseBoundBossControl, parseBossSessionMetadata } from "./boss.ts"; +import type { SessionInfo } from "../types.ts"; + +function worker(): SessionInfo { + const bindingEpoch = participantBindingEpoch(2); + return { + id: "worker-session", + cwd: "/repo", + model: "test", + pid: 1, + startedAt: 1, + lastActivity: 1, + boss: { + registration: { + principalId: "worker-session", + principalClass: "boss-bound", + state: "active", + bossRunId: "run-a", + participantId: "worker-a", + bindingEpoch, + featureContract: BOSS_RUN_FEATURE_CONTRACT, + policySemanticsHash: BOSS_POLICY_SEMANTICS_HASH, + capabilityDigest: BOSS_CAPABILITY_FEATURE_DIGEST, + brokerIdentityVerified: true, + }, + principal: { + version: BOSS_POLICY_PRINCIPAL_VERSION, + principalId: "worker-session", + principalClass: "boss-private", + state: "active", + bossRunId: "run-a", + participantId: "worker-a", + role: "worker", + bindingEpoch, + assignedManagerParticipantId: "manager-a", + }, + binding: { + version: BOSS_PARTICIPANT_BINDING_VERSION, + bossRunId: "run-a", + participantId: "worker-a", + role: "worker", + communicationProfile: "worker", + bindingEpoch, + sessionId: "worker-session", + brokerGeneration: brokerGeneration(3), + brokerBootInstance: "boot-a", + state: "active", + assignedManagerParticipantId: "manager-a", + authorityTransitionId: "transition-a", + }, + }, + }; +} + +test("broker-owned Boss participant and binding metadata must agree exactly", () => { + const session = worker(); + assert.deepEqual(parseBossSessionMetadata(session.boss, session.id), session.boss); + assert.throws( + () => parseBossSessionMetadata({ ...session.boss, registration: { ...session.boss!.registration, bossRunId: "run-b" } }, session.id), + /identity bindings must exactly match/, + ); +}); + +test("typed controls preserve stable envelope correlation and sender binding", () => { + const envelope = { + type: "boss.assignment.submitted" as const, + version: 1 as const, + messageId: "message-1", + bossRunId: "run-a", + participantId: "worker-a", + bindingEpoch: participantBindingEpoch(2), + causationId: "assignment-1", + replyTo: "message-0", + idempotencyKey: "assignment-1:submission:1", + payload: { assignmentId: "assignment-1", outcome: "complete" }, + }; + assert.deepEqual(parseBoundBossControl(envelope, worker()), envelope); + assert.equal(bossControlKind(envelope.type), "assignment_response"); + assert.throws(() => parseBoundBossControl({ ...envelope, bossRunId: "run-b" }, worker()), /must match the sender/); +}); diff --git a/broker/boss.ts b/broker/boss.ts new file mode 100644 index 0000000..c8a277f --- /dev/null +++ b/broker/boss.ts @@ -0,0 +1,104 @@ +import { + BOSS_CONTROL_TYPES, + parseBossControlEnvelope, + parseBossParticipantBinding, + parseBossPolicyPrincipal, + parseFeatureRegistration, + type BossControlEnvelope, + type BossControlKind, + type BossControlType, +} from "@dataforxyz/agent-intercom-core/boss"; +import { + ContractValidationError, + assertExactKeys, + assertRecord, +} from "@dataforxyz/agent-intercom-core/canonical"; +import type { BossSessionMetadata, SessionInfo } from "../types.ts"; + +const CONTROL_KIND_BY_TYPE = { + "boss.assignment.created": "assignment_request", + "boss.assignment.accepted": "assignment_response", + "boss.assignment.checkpoint": "assignment_response", + "boss.assignment.submitted": "assignment_response", + "boss.assignment.rejected": "assignment_response", + "boss.assignment.cancelled": "lifecycle", + "boss.staffing.requested": "staffing", + "boss.staffing.resolved": "staffing", + "boss.review.requested": "review_request", + "boss.review.submitted": "review_result", + "boss.council.requested": "review_request", + "boss.council.submitted": "review_result", + "boss.proof.submitted": "proof", + "boss.worker.health": "health", + "boss.worker.blocked": "health", + "boss.worker.failed": "health", + "boss.worker.notice": "lifecycle", + "boss.worker.notice_delivery_failed": "lifecycle", + "boss.decision.required": "decision", +} as const satisfies Record; + +if (Object.keys(CONTROL_KIND_BY_TYPE).length !== BOSS_CONTROL_TYPES.length) { + throw new Error("Boss control type mapping is incomplete"); +} + +export function bossControlKind(type: BossControlType): BossControlKind { + return CONTROL_KIND_BY_TYPE[type]; +} + +export function parseBossSessionMetadata(value: unknown, sessionId: string): BossSessionMetadata { + assertRecord(value, "$.boss"); + assertExactKeys(value, ["registration", "principal"], ["binding"], "$.boss"); + const metadata = value; + const registration = parseFeatureRegistration(metadata.registration); + const principal = parseBossPolicyPrincipal(metadata.principal); + if (registration.principalClass !== "boss-bound" || principal.principalClass !== "boss-private") { + throw new ContractValidationError("$.boss", "must contain Boss-bound registration and private principal metadata"); + } + if ( + registration.principalId !== sessionId + || principal.principalId !== sessionId + || registration.bossRunId !== principal.bossRunId + || registration.participantId !== principal.participantId + || registration.bindingEpoch !== principal.bindingEpoch + ) { + throw new ContractValidationError("$.boss", "registration and principal identity bindings must exactly match the session"); + } + + const binding = metadata.binding === undefined ? undefined : parseBossParticipantBinding(metadata.binding); + if (principal.role === "controller") { + if (binding !== undefined) throw new ContractValidationError("$.boss.binding", "is forbidden for Controller principals"); + } else { + if (binding === undefined) throw new ContractValidationError("$.boss.binding", "is required for Boss participants"); + if ( + binding.sessionId !== sessionId + || binding.bossRunId !== principal.bossRunId + || binding.participantId !== principal.participantId + || binding.role !== principal.role + || binding.bindingEpoch !== principal.bindingEpoch + || binding.state !== principal.state + || binding.assignedManagerParticipantId !== principal.assignedManagerParticipantId + ) { + throw new ContractValidationError("$.boss.binding", "must exactly match the authenticated session principal"); + } + } + return { registration, principal, ...(binding === undefined ? {} : { binding }) }; +} + +export function validatedBossMetadata(session: SessionInfo): BossSessionMetadata | undefined { + if (session.boss === undefined) return undefined; + return parseBossSessionMetadata(session.boss, session.id); +} + +export function parseBoundBossControl(value: unknown, sender: SessionInfo): BossControlEnvelope { + const envelope = parseBossControlEnvelope(value); + const boss = validatedBossMetadata(sender); + if (!boss) throw new ContractValidationError("$.envelope", "sender is not an authenticated Boss participant"); + if ( + envelope.bossRunId !== boss.principal.bossRunId + || envelope.participantId !== boss.principal.participantId + || envelope.bindingEpoch !== boss.principal.bindingEpoch + ) { + throw new ContractValidationError("$.envelope", "run, participant, and binding epoch must match the sender"); + } + return envelope; +} diff --git a/broker/broker-terminal.test.ts b/broker/broker-terminal.test.ts new file mode 100644 index 0000000..9da1a53 --- /dev/null +++ b/broker/broker-terminal.test.ts @@ -0,0 +1,232 @@ +import assert from "node:assert/strict"; +import { mkdtemp, rm } from "node:fs/promises"; +import type { Socket } from "node:net"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { participantBindingEpoch } from "@dataforxyz/agent-intercom-core/canonical"; +import { + DURABLE_JSON_FILE_OPERATIONS, + writeDurableJson, + type DurableJsonFileOperations, +} from "../durable-json.ts"; +import { + bossControlFingerprint, + bossControlIdempotencyScope, + DurableBossControlStore, + type BossControlTerminalFailureCode, + type DurableBossControlRecord, +} from "./boss-control-store.ts"; +import { IntercomBroker } from "./broker.ts"; + +const envelope = { + type: "boss.assignment.submitted" as const, + version: 1 as const, + messageId: "control-message-a", + bossRunId: "run-a", + participantId: "worker-a", + bindingEpoch: participantBindingEpoch(2), + causationId: "assignment-a", + replyTo: "control-message-parent", + idempotencyKey: "assignment-a:submission:1", + payload: { assignmentId: "assignment-a", outcome: "complete" }, +}; + +function accepted(): DurableBossControlRecord { + return { + senderSessionId: "worker-session-a", + bossRunId: envelope.bossRunId, + participantId: envelope.participantId, + senderBindingEpoch: envelope.bindingEpoch, + idempotencyKey: envelope.idempotencyKey, + targetSessionId: "manager-session-a", + targetBindingEpoch: participantBindingEpoch(4), + controlKind: "assignment_response", + envelope, + fingerprint: bossControlFingerprint("manager-session-a", envelope), + deliveryId: "delivery-a", + state: "accepted", + acceptedAt: "2026-07-28T12:00:00.000Z", + }; +} + +type PostRenameFaultStage = "restrict" | "directory-fsync"; + +function faultingPersist(stage: PostRenameFaultStage): (path: string, state: unknown) => void { + return (path, state) => { + let fsyncCalls = 0; + const operations: DurableJsonFileOperations = { + ...DURABLE_JSON_FILE_OPERATIONS, + fsync(fileDescriptor) { + fsyncCalls += 1; + if (stage === "directory-fsync" && fsyncCalls === 2) { + throw new Error("injected durable directory-fsync fault"); + } + DURABLE_JSON_FILE_OPERATIONS.fsync(fileDescriptor); + }, + restrict(filePath) { + if (stage === "restrict") throw new Error("injected durable restrict fault"); + DURABLE_JSON_FILE_OPERATIONS.restrict(filePath); + }, + }; + writeDurableJson(path, state, operations); + }; +} + +function capturingSocket(frames: unknown[]): Socket { + return { + write(data: Uint8Array) { + const frame = Buffer.from(data); + const length = frame.readUInt32BE(0); + frames.push(JSON.parse(frame.subarray(4, 4 + length).toString("utf8"))); + return true; + }, + } as unknown as Socket; +} + +interface PendingHarness { + id: string; + key: string; + fingerprint: string; + envelope: typeof envelope; + controlKind: "assignment_response"; + from: string; + to: string; + requesters: Map; + recipientSocket: Socket; + fromBindingEpoch: ReturnType; + toBindingEpoch: ReturnType; + timeout: NodeJS.Timeout; +} + +interface BrokerTerminalHarness { + pendingBossControls: Map; + pendingBossControlKeys: Map; + sessions: Map; + bossControlStoreInstance: DurableBossControlStore; + isBossControlAuthorized: () => boolean; + failDurableBossControl( + record: DurableBossControlRecord, + code: BossControlTerminalFailureCode, + reason: string, + senderSocket?: Socket, + requestMessageId?: string, + ): void; + acknowledgePendingBossControl(deliveryId: string, sessionId: string, socket: Socket): void; + failPendingBossControl(deliveryId: string, code: BossControlTerminalFailureCode, reason: string): void; + clearPendingBossControlsForSession(sessionId: string, socket: Socket): void; +} + +function brokerHarness(store: DurableBossControlStore, authorized: boolean): { + broker: BrokerTerminalHarness; + frames: unknown[]; + recipientSocket: Socket; +} { + const frames: unknown[] = []; + const senderSocket = capturingSocket(frames); + const recipientSocket = capturingSocket([]); + const record = accepted(); + const key = bossControlIdempotencyScope(record); + const timeout = setTimeout(() => {}, 60_000); + timeout.unref?.(); + const pending: PendingHarness = { + id: record.deliveryId, + key, + fingerprint: record.fingerprint, + envelope, + controlKind: record.controlKind, + from: record.senderSessionId, + to: record.targetSessionId, + requesters: new Map([[record.envelope.messageId, senderSocket]]), + recipientSocket, + fromBindingEpoch: record.senderBindingEpoch, + toBindingEpoch: record.targetBindingEpoch, + timeout, + }; + const broker = Object.create(IntercomBroker.prototype) as BrokerTerminalHarness; + broker.pendingBossControls = new Map([[record.deliveryId, pending]]); + broker.pendingBossControlKeys = new Map([[key, record.deliveryId]]); + broker.sessions = new Map([[record.senderSessionId, { socket: senderSocket }]]); + broker.bossControlStoreInstance = store; + broker.isBossControlAuthorized = () => authorized; + return { broker, frames, recipientSocket }; +} + +const TERMINAL_CASES = [ + { name: "ack", authorized: true, state: "delivered" as const }, + { name: "reject", authorized: true, state: "rejected" as const, code: "RECIPIENT_DISCONNECTED" as const }, + { name: "timeout", authorized: true, state: "rejected" as const, code: "DELIVERY_TIMEOUT" as const }, + { name: "close", authorized: true, state: "rejected" as const, code: "RECIPIENT_DISCONNECTED" as const }, +]; + +test("Boss broker contains post-rename terminal faults across ack, reject, timeout, and close callbacks", async () => { + const root = await mkdtemp(join(tmpdir(), "boss-broker-terminal-faults-")); + const originalConsoleError = console.error; + const containedErrors: unknown[][] = []; + console.error = (...args: unknown[]) => { + containedErrors.push(args); + }; + try { + for (const stage of ["restrict", "directory-fsync"] as const) { + for (const terminalCase of TERMINAL_CASES) { + const path = join(root, `${terminalCase.name}-${stage}.json`); + new DurableBossControlStore(path).reserve(accepted()); + const store = new DurableBossControlStore(path, faultingPersist(stage)); + const { broker, frames, recipientSocket } = brokerHarness(store, terminalCase.authorized); + + assert.doesNotThrow(() => { + if (terminalCase.name === "ack") { + broker.acknowledgePendingBossControl("delivery-a", "manager-session-a", recipientSocket); + } else if (terminalCase.name === "reject") { + clearTimeout(broker.pendingBossControls.get("delivery-a")!.timeout); + broker.pendingBossControls.clear(); + broker.pendingBossControlKeys.clear(); + broker.failDurableBossControl( + accepted(), + "RECIPIENT_DISCONNECTED", + "The exact accepted Boss control target is unavailable", + ); + } else if (terminalCase.name === "timeout") { + broker.failPendingBossControl("delivery-a", "DELIVERY_TIMEOUT", "Recipient acknowledgement timed out"); + } else { + broker.clearPendingBossControlsForSession("manager-session-a", recipientSocket); + } + }, `${terminalCase.name}/${stage} callback must contain the persist exception`); + + assert.equal(broker.pendingBossControls.size, 0, `${terminalCase.name}/${stage} must clear the exact pending control`); + assert.equal(broker.pendingBossControlKeys.size, 0, `${terminalCase.name}/${stage} must clear the exact pending key`); + const live = store.get(accepted()); + const replayed = new DurableBossControlStore(path).get(accepted()); + assert.equal(live?.state, terminalCase.state, `${terminalCase.name}/${stage} live state`); + assert.deepEqual(replayed, live, `${terminalCase.name}/${stage} restart replay`); + + const expectedFrame = terminalCase.state === "delivered" + ? { type: "boss_control_delivered", messageId: envelope.messageId, deliveryId: "delivery-a" } + : { + type: "boss_control_failed", + messageId: envelope.messageId, + deliveryId: "delivery-a", + accepted: true, + code: terminalCase.code, + reason: terminalCase.name === "reject" + ? "The exact accepted Boss control target is unavailable" + : terminalCase.name === "timeout" + ? "Recipient acknowledgement timed out" + : "Recipient disconnected before acknowledging the Boss control", + }; + assert.deepEqual(frames, [expectedFrame], `${terminalCase.name}/${stage} must publish the committed terminal`); + + assert.doesNotThrow(() => { + broker.failPendingBossControl("delivery-a", "DELIVERY_TIMEOUT", "stale timeout"); + broker.clearPendingBossControlsForSession("manager-session-a", recipientSocket); + }); + assert.deepEqual(frames, [expectedFrame], `${terminalCase.name}/${stage} stale callbacks must not contradict the terminal`); + assert.equal(new DurableBossControlStore(path).get(accepted())?.state, terminalCase.state); + } + } + assert.equal(containedErrors.length, TERMINAL_CASES.length * 2, "each injected persist exception must be contained and reported"); + } finally { + console.error = originalConsoleError; + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/broker/broker.ts b/broker/broker.ts index b2b3c03..1a74d9b 100644 --- a/broker/broker.ts +++ b/broker/broker.ts @@ -2,7 +2,16 @@ import net from "net"; import { existsSync, readFileSync, renameSync, writeFileSync, unlinkSync } from "fs"; import { join } from "path"; import { randomUUID } from "crypto"; -import { authorize, POLICY_SEMANTICS_HASH, POLICY_SEMANTICS_VERSION, type PolicyAction, type PolicyState } from "@dataforxyz/agent-intercom-core"; +import { pathToFileURL } from "node:url"; +import { + authorize, + POLICY_SEMANTICS_HASH, + POLICY_SEMANTICS_VERSION, + type PolicyAction, + type PolicyState, +} from "@dataforxyz/agent-intercom-core"; +import type { BossControlEnvelope, BossControlKind } from "@dataforxyz/agent-intercom-core/boss"; +import { assertExactKeys, type ParticipantBindingEpoch } from "@dataforxyz/agent-intercom-core/canonical"; import { writeMessage, createMessageReader } from "./framing.ts"; import { ensureIntercomRuntimeDir, @@ -10,6 +19,7 @@ import { getBrokerAdminCredentialFilePath, getBrokerAskStateFilePath, getBrokerAuditFilePath, + getBrokerBossControlStateFilePath, getBrokerListenTarget, getBrokerPortFilePath, getIntercomDirPath, @@ -25,6 +35,19 @@ import { writeDurableJson } from "../durable-json.ts"; import { acquireBrokerOwnership, hasBrokerOwnership, releaseBrokerOwnership } from "./ownership.ts"; import { RemoteAccessRegistry, type RemotePrincipalMetadata, type RemotePrincipalRecord } from "./access-registry.ts"; import { authorizeSessionAction, visibleSessions } from "./authorization.ts"; +import { bossControlKind, parseBoundBossControl, validatedBossMetadata } from "./boss.ts"; +import { + bossControlAcceptedFrame, + bossControlFingerprint, + bossControlIdempotencyScope, + bossControlReplayFrames, + bossControlTerminalFrame, + DurableBossControlStore, + type BossControlIdempotencyIdentity, + type BossControlTerminalFailureCode, + type DurableBossControlRecord, +} from "./boss-control-store.ts"; +import { negotiateBrokerCompatibility } from "./negotiation.ts"; import { BrokerAuditLog } from "./audit.ts"; import type { AskCancellationReason, @@ -48,6 +71,7 @@ const ASK_STATE_PATH = getBrokerAskStateFilePath(INTERCOM_DIR); const ACCESS_STATE_PATH = getBrokerAccessStateFilePath(INTERCOM_DIR); const ADMIN_CREDENTIAL_PATH = getBrokerAdminCredentialFilePath(INTERCOM_DIR); const AUDIT_PATH = getBrokerAuditFilePath(INTERCOM_DIR); +const BOSS_CONTROL_STATE_PATH = getBrokerBossControlStateFilePath(INTERCOM_DIR); const BROKER_STATE_ID = randomUUID(); const MAX_SESSIONS = 128; const MAX_UNREGISTERED_CONNECTIONS = 32; @@ -140,6 +164,21 @@ interface RecentDelivery { expiresAt: number; } +interface PendingBossControl { + id: string; + key: string; + fingerprint: string; + envelope: BossControlEnvelope; + controlKind: BossControlKind; + from: string; + to: string; + requesters: Map; + recipientSocket: net.Socket; + fromBindingEpoch: ParticipantBindingEpoch; + toBindingEpoch: ParticipantBindingEpoch; + timeout: NodeJS.Timeout; +} + function isAttachment(value: unknown): value is Attachment { if (typeof value !== "object" || value === null) { return false; @@ -223,6 +262,9 @@ function isSessionRegistration(value: unknown): value is SessionRegistration { const session = value as Record; + const allowedKeys = new Set(["name", "cwd", "model", "pid", "startedAt", "lastActivity", "status", "runtimeInstanceId"]); + if (Object.keys(session).some((key) => !allowedKeys.has(key))) return false; + if ( typeof session.cwd !== "string" || session.cwd.length === 0 @@ -267,12 +309,14 @@ function isSameLocalRuntime(previous: ConnectedSession, registration: SessionReg && previous.info.startedAt === registration.startedAt; } -class IntercomBroker { +export class IntercomBroker { private sessions = new Map(); private askEdges = new Map(); private pendingDeliveries = new Map(); private pendingDeliveryKeys = new Map(); private recentDeliveries = new Map(); + private pendingBossControls = new Map(); + private pendingBossControlKeys = new Map(); private connections = new Set(); private unregisteredConnections = new Set(); private server: net.Server; @@ -282,6 +326,7 @@ class IntercomBroker { private readonly askTimeoutMs = getAskTimeoutMs(); private readonly accessRegistry: RemoteAccessRegistry; private readonly audit: BrokerAuditLog; + private bossControlStoreInstance: DurableBossControlStore | undefined; constructor() { ensureIntercomRuntimeDir(INTERCOM_DIR); @@ -437,6 +482,7 @@ class IntercomBroker { this.broadcastVisible({ type: "session_left", sessionId }, existing.info, sessionId); this.sessions.delete(sessionId); this.clearPendingDeliveriesForSession(sessionId, socket); + this.clearPendingBossControlsForSession(sessionId, socket); this.deferAskEdgesForSession(sessionId); this.scheduleShutdownCheck(); } @@ -580,6 +626,19 @@ class IntercomBroker { break; } + if (clientMessage.compatibility !== undefined) { + const compatibility = negotiateBrokerCompatibility(clientMessage.compatibility); + if (!compatibility.compatible || compatibility.mode !== "ordinary") { + this.sendError( + socket, + "PROTOCOL_MISMATCH", + `Intercom compatibility negotiation failed: ${"code" in compatibility ? compatibility.code : "BOSS_MODE_DORMANT"}`, + ); + socket.end(); + break; + } + } + if (currentId) { throw new Error("Received duplicate register message"); } @@ -663,6 +722,7 @@ class IntercomBroker { } if (previous) { this.clearPendingDeliveriesForSession(id, previous.socket); + this.clearPendingBossControlsForSession(id, previous.socket); this.deferAskEdgesForSession(id); previous.socket.end(); } @@ -779,6 +839,7 @@ class IntercomBroker { this.broadcastVisible({ type: "session_left", sessionId: currentId }, existing.info, currentId); this.sessions.delete(currentId); this.clearPendingDeliveriesForSession(currentId, socket); + this.clearPendingBossControlsForSession(currentId, socket); if (clientMessage.preserveAsks) { this.deferAskEdgesForSession(currentId); } else { @@ -1002,6 +1063,172 @@ class IntercomBroker { break; } + case "boss_control_send": { + if (!currentId) throw new Error("Received boss_control_send before register"); + const rawEnvelope = clientMessage.envelope; + const rawMessageId = typeof rawEnvelope === "object" + && rawEnvelope !== null + && "messageId" in rawEnvelope + && typeof rawEnvelope.messageId === "string" + ? rawEnvelope.messageId + : "unknown"; + if ( + typeof clientMessage.to !== "string" + || clientMessage.to.length === 0 + || clientMessage.to.length > MAX_TARGET_LENGTH + ) { + this.sendBossControlFailure(socket, rawMessageId, false, "INVALID_BOSS_CONTROL", "Invalid Boss control target"); + break; + } + const sender = this.sessions.get(currentId); + let envelope: BossControlEnvelope; + try { + if (!sender || sender.socket !== socket) throw new Error("Sender session not found"); + envelope = parseBoundBossControl(rawEnvelope, sender.info); + } catch (error) { + this.sendBossControlFailure( + socket, + rawMessageId, + false, + "INVALID_BOSS_CONTROL", + error instanceof Error ? error.message : "Invalid Boss control envelope", + ); + break; + } + const controlKind = bossControlKind(envelope.type); + const senderBoss = validatedBossMetadata(sender!.info)!; + const identity: BossControlIdempotencyIdentity = { + senderSessionId: currentId, + bossRunId: envelope.bossRunId, + participantId: envelope.participantId, + senderBindingEpoch: envelope.bindingEpoch, + idempotencyKey: envelope.idempotencyKey, + }; + const key = bossControlIdempotencyScope(identity); + const fingerprint = bossControlFingerprint(clientMessage.to, envelope); + const durable = this.bossControlStore().get(identity); + if (durable) { + if (durable.fingerprint !== fingerprint) { + this.sendBossControlFailure(socket, envelope.messageId, false, "CONFLICTING_MESSAGE_ID", "Boss control idempotency key was reused with different content"); + break; + } + this.writeBossControlReplay(socket, durable, envelope.messageId); + if (durable.state !== "accepted") break; + const pendingId = this.pendingBossControlKeys.get(key); + if (pendingId) { + const pending = this.pendingBossControls.get(pendingId); + if (pending) { + pending.requesters.set(envelope.messageId, socket); + break; + } + } + const target = this.sessions.get(durable.targetSessionId); + let targetBoss; + try { + targetBoss = target === undefined ? undefined : validatedBossMetadata(target.info); + } catch { + targetBoss = undefined; + } + if ( + !target + || !targetBoss + || targetBoss.principal.bindingEpoch !== durable.targetBindingEpoch + || !this.isBossControlAuthorized( + currentId, + durable.targetSessionId, + durable.controlKind, + durable.senderBindingEpoch, + durable.targetBindingEpoch, + ) + ) { + this.failDurableBossControl( + durable, + "RECIPIENT_DISCONNECTED", + "The exact accepted Boss control target is not available at its bound epoch", + socket, + envelope.messageId, + ); + break; + } + this.activateBossControl(durable, socket, target.socket, envelope.messageId); + break; + } + const pendingId = this.pendingBossControlKeys.get(key); + if (pendingId) { + const pending = this.pendingBossControls.get(pendingId); + if (!pending || pending.fingerprint !== fingerprint) { + this.sendBossControlFailure(socket, envelope.messageId, false, "CONFLICTING_MESSAGE_ID", "Boss control idempotency key is already pending with different content"); + break; + } + if (this.isBossControlAuthorized(pending.from, pending.to, pending.controlKind, pending.fromBindingEpoch, pending.toBindingEpoch)) { + pending.requesters.set(envelope.messageId, socket); + writeMessage(socket, { type: "boss_control_accepted", messageId: envelope.messageId, deliveryId: pending.id }); + break; + } + this.failPendingBossControl(pending.id, "BOSS_CONTROL_DENIED", "Boss control authorization changed while pending"); + } + if ( + this.pendingDeliveries.size + this.pendingBossControls.size >= MAX_PENDING_DELIVERIES + || this.countPendingDeliveriesFrom(currentId) + this.countPendingBossControlsFrom(currentId) >= MAX_PENDING_DELIVERIES_PER_SESSION + ) { + this.sendBossControlFailure(socket, envelope.messageId, false, "TOO_MANY_PENDING_DELIVERIES", "Too many deliveries are waiting for acknowledgement"); + break; + } + const target = this.sessions.get(clientMessage.to); + if (!target) { + this.sendBossControlFailure( + socket, + envelope.messageId, + false, + "SESSION_NOT_FOUND", + "Exact Boss control target session not found", + ); + break; + } + let targetBoss; + try { + targetBoss = validatedBossMetadata(target.info); + } catch { + targetBoss = undefined; + } + if ( + !targetBoss + || !this.isBossControlAuthorized( + currentId, + target.info.id, + controlKind, + senderBoss.principal.bindingEpoch, + targetBoss.principal.bindingEpoch, + ) + ) { + this.sendBossControlFailure(socket, envelope.messageId, false, "BOSS_CONTROL_DENIED", "Boss control policy denied the exact target"); + break; + } + const deliveryId = randomUUID(); + const reserved = this.bossControlStore().reserve({ + ...identity, + targetSessionId: target.info.id, + targetBindingEpoch: targetBoss.principal.bindingEpoch, + controlKind, + envelope, + fingerprint, + deliveryId, + state: "accepted", + acceptedAt: new Date().toISOString(), + }).record; + writeMessage(socket, bossControlAcceptedFrame(reserved, envelope.messageId)); + this.activateBossControl(reserved, socket, target.socket, envelope.messageId); + break; + } + + case "boss_control_received": { + if (!currentId) throw new Error("Received boss_control_received before register"); + assertExactKeys(clientMessage, ["type", "deliveryId"], [], "$.boss_control_received"); + if (typeof clientMessage.deliveryId !== "string") throw new Error("Invalid boss_control_received message"); + this.acknowledgePendingBossControl(clientMessage.deliveryId, currentId, socket); + break; + } + case "message_received": { if (!currentId) { throw new Error("Received message_received before register"); @@ -1473,6 +1700,7 @@ class IntercomBroker { } } this.clearPendingDeliveriesForSession(principal.id, live.socket); + this.clearPendingBossControlsForSession(principal.id, live.socket); this.clearAskEdgesForSession(principal.id, "authorization_revoked"); this.sessions.delete(principal.id); for (const [key, recent] of this.recentDeliveries) { @@ -1514,6 +1742,26 @@ class IntercomBroker { ).allowed; } + private isBossControlAuthorized( + actorId: string, + targetId: string, + controlKind: BossControlKind, + actorBindingEpoch: ParticipantBindingEpoch, + targetBindingEpoch: ParticipantBindingEpoch, + ): boolean { + if (!this.isCurrentPrincipal(actorId) || !this.isCurrentPrincipal(targetId)) return false; + return authorizeSessionAction( + Array.from(this.sessions.values(), (session) => session.info), + actorId, + "control", + targetId, + // This adapter has no authenticated Orc/Controller causation ledger in + // the current slice. Binding epochs prove identity freshness, not that a + // specific Boss operation is correlated, so control must remain denied. + { actorBindingEpoch, targetBindingEpoch, controlKind, correlated: false }, + ).allowed; + } + private broadcastVisible(message: BrokerMessage, subject: SessionInfo, exclude?: string): void { for (const [id, session] of this.sessions) { if (id !== exclude && this.isAuthorized(id, "discover", subject.id)) { @@ -1739,6 +1987,220 @@ class IntercomBroker { return count; } + private countPendingBossControlsFrom(sessionId: string): number { + let count = 0; + for (const control of this.pendingBossControls.values()) { + if (control.from === sessionId) count += 1; + } + return count; + } + + private sendBossControlFailure( + socket: net.Socket, + messageId: string, + accepted: boolean, + code: DeliveryFailureCode, + reason: string, + deliveryId?: string, + ): void { + if (accepted) { + if (!deliveryId) throw new Error("Accepted Boss control failure requires deliveryId"); + writeMessage(socket, { type: "boss_control_failed", messageId, deliveryId, accepted: true, code, reason }); + return; + } + if (deliveryId !== undefined) throw new Error("Pre-acceptance Boss control failure cannot contain deliveryId"); + writeMessage(socket, { type: "boss_control_failed", messageId, accepted: false, code, reason }); + } + + private bossControlStore(): DurableBossControlStore { + this.bossControlStoreInstance ??= new DurableBossControlStore(BOSS_CONTROL_STATE_PATH); + return this.bossControlStoreInstance; + } + + private writeBossControlReplay(socket: net.Socket, record: DurableBossControlRecord, requestMessageId: string): void { + for (const frame of bossControlReplayFrames(record, requestMessageId)) writeMessage(socket, frame); + } + + private activateBossControl( + record: DurableBossControlRecord, + senderSocket: net.Socket, + recipientSocket: net.Socket, + requestMessageId: string, + ): void { + const key = bossControlIdempotencyScope(record); + const timeout = setTimeout(() => { + try { + this.failPendingBossControl(record.deliveryId, "DELIVERY_TIMEOUT", "Recipient did not acknowledge the Boss control in time"); + } catch (error) { + console.error("Boss control timeout callback failed:", error); + } + }, DELIVERY_ACK_TIMEOUT_MS); + timeout.unref?.(); + this.pendingBossControls.set(record.deliveryId, { + id: record.deliveryId, + key, + fingerprint: record.fingerprint, + envelope: record.envelope, + controlKind: record.controlKind, + from: record.senderSessionId, + to: record.targetSessionId, + requesters: new Map([[requestMessageId, senderSocket]]), + recipientSocket, + fromBindingEpoch: record.senderBindingEpoch, + toBindingEpoch: record.targetBindingEpoch, + timeout, + }); + this.pendingBossControlKeys.set(key, record.deliveryId); + const sender = this.sessions.get(record.senderSessionId); + if (!sender || sender.socket !== senderSocket) { + this.failPendingBossControl(record.deliveryId, "SENDER_DISCONNECTED", "Sender disconnected before the Boss control was dispatched"); + return; + } + writeMessage(recipientSocket, { + type: "boss_control", + deliveryId: record.deliveryId, + from: sender.info, + envelope: record.envelope, + }); + } + + private failDurableBossControl( + record: DurableBossControlRecord, + code: BossControlTerminalFailureCode, + reason: string, + senderSocket?: net.Socket, + requestMessageId?: string, + ): void { + try { + const reconciled = this.mutateBossControlTerminal( + record, + record.deliveryId, + "rejection", + () => this.bossControlStore().markRejected(record, record.deliveryId, code, reason), + ); + if (!reconciled || reconciled.state === "accepted") return; + const socket = senderSocket ?? this.sessions.get(record.senderSessionId)?.socket; + if (socket) writeMessage(socket, bossControlTerminalFrame(reconciled, requestMessageId ?? reconciled.envelope.messageId)); + } catch (error) { + console.error("Boss control durable rejection callback failed:", error); + } + } + + private acknowledgePendingBossControl(deliveryId: string, sessionId: string, socket: net.Socket): void { + try { + const pending = this.pendingBossControls.get(deliveryId); + if (!pending || pending.to !== sessionId || pending.recipientSocket !== socket) return; + if (!this.isBossControlAuthorized( + pending.from, + pending.to, + pending.controlKind, + pending.fromBindingEpoch, + pending.toBindingEpoch, + )) { + this.failPendingBossControl(deliveryId, "BOSS_CONTROL_DENIED", "Boss control authorization changed before acknowledgement"); + return; + } + const identity = this.pendingBossControlIdentity(pending); + const reconciled = this.mutateBossControlTerminal( + identity, + deliveryId, + "delivery", + () => this.bossControlStore().markDelivered(identity, deliveryId), + ); + this.completePendingBossControl(pending, reconciled); + } catch (error) { + console.error("Boss control acknowledgement callback failed:", error); + } + } + + private failPendingBossControl(deliveryId: string, code: BossControlTerminalFailureCode, reason: string): void { + try { + const pending = this.pendingBossControls.get(deliveryId); + if (!pending) return; + const identity = this.pendingBossControlIdentity(pending); + const reconciled = this.mutateBossControlTerminal( + identity, + deliveryId, + "rejection", + () => this.bossControlStore().markRejected(identity, deliveryId, code, reason), + ); + this.completePendingBossControl(pending, reconciled); + } catch (error) { + console.error("Boss control rejection callback failed:", error); + } + } + + private pendingBossControlIdentity(pending: PendingBossControl): BossControlIdempotencyIdentity { + return { + senderSessionId: pending.from, + bossRunId: pending.envelope.bossRunId, + participantId: pending.envelope.participantId, + senderBindingEpoch: pending.envelope.bindingEpoch, + idempotencyKey: pending.envelope.idempotencyKey, + }; + } + + private mutateBossControlTerminal( + identity: BossControlIdempotencyIdentity, + deliveryId: string, + operation: "delivery" | "rejection", + mutate: () => DurableBossControlRecord, + ): DurableBossControlRecord | undefined { + try { + return mutate(); + } catch (mutationError) { + try { + const reconciled = this.bossControlStore().get(identity); + if (!reconciled || reconciled.deliveryId !== deliveryId) { + console.error(`Boss control ${operation} mutation failed without an exact durable record:`, mutationError); + return undefined; + } + console.error( + `Boss control ${operation} mutation threw after reconciling durable state ${reconciled.state}:`, + mutationError, + ); + return reconciled; + } catch (reconcileError) { + console.error(`Boss control ${operation} mutation and exact reconciliation failed:`, mutationError, reconcileError); + return undefined; + } + } + } + + private completePendingBossControl( + pending: PendingBossControl, + reconciled: DurableBossControlRecord | undefined, + ): void { + if (this.pendingBossControls.get(pending.id) !== pending) return; + clearTimeout(pending.timeout); + this.pendingBossControls.delete(pending.id); + if (this.pendingBossControlKeys.get(pending.key) === pending.id) this.pendingBossControlKeys.delete(pending.key); + if (!reconciled || reconciled.state === "accepted") return; + const sender = this.sessions.get(pending.from); + for (const [messageId, requesterSocket] of pending.requesters) { + if (sender?.socket !== requesterSocket) continue; + try { + writeMessage(requesterSocket, bossControlTerminalFrame(reconciled, messageId)); + } catch (error) { + console.error("Failed to publish reconciled Boss control terminal frame:", error); + } + } + } + + private clearPendingBossControlsForSession(sessionId: string, socket: net.Socket): void { + try { + for (const control of Array.from(this.pendingBossControls.values())) { + if (control.to === sessionId && control.recipientSocket === socket) { + this.failPendingBossControl(control.id, "RECIPIENT_DISCONNECTED", "Recipient disconnected before acknowledging the Boss control"); + } else if (control.from === sessionId && Array.from(control.requesters.values()).includes(socket)) { + this.failPendingBossControl(control.id, "SENDER_DISCONNECTED", "Sender disconnected before the Boss control was acknowledged"); + } + } + } catch (error) { + console.error("Boss control socket-close callback failed:", error); + } + } + private acknowledgePendingDelivery(deliveryId: string, sessionId: string, socket: net.Socket): void { const pending = this.pendingDeliveries.get(deliveryId); if (!pending || pending.to !== sessionId || pending.recipientSocket !== socket) { @@ -1867,6 +2329,11 @@ class IntercomBroker { } this.pendingDeliveries.clear(); this.pendingDeliveryKeys.clear(); + for (const control of this.pendingBossControls.values()) { + clearTimeout(control.timeout); + } + this.pendingBossControls.clear(); + this.pendingBossControlKeys.clear(); for (const edge of this.askEdges.values()) { clearTimeout(edge.timeout); } @@ -1900,4 +2367,6 @@ class IntercomBroker { } } -new IntercomBroker().start(); +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + new IntercomBroker().start(); +} diff --git a/broker/client.test.ts b/broker/client.test.ts index f372e7f..39505b9 100644 --- a/broker/client.test.ts +++ b/broker/client.test.ts @@ -1,6 +1,86 @@ import test from "node:test"; import assert from "node:assert/strict"; import { IntercomClient } from "./client.ts"; +import { + BOSS_CAPABILITY_FEATURE_DIGEST, + BOSS_PARTICIPANT_BINDING_VERSION, + BOSS_POLICY_PRINCIPAL_VERSION, + BOSS_POLICY_SEMANTICS_HASH, + BOSS_RUN_FEATURE_CONTRACT, +} from "@dataforxyz/agent-intercom-core/boss"; +import { brokerGeneration, participantBindingEpoch } from "@dataforxyz/agent-intercom-core/canonical"; +import type { SessionInfo } from "../types.ts"; + +function bossSender(role: "manager" | "worker" = "worker"): SessionInfo { + const bindingEpoch = participantBindingEpoch(2); + const participantId = `${role}-a`; + const sessionId = `${role}-session`; + const worker = role === "worker"; + return { + id: sessionId, + name: sessionId, + cwd: "/repo", + model: "test", + pid: 1, + startedAt: 1, + lastActivity: 1, + origin: "local", + boss: { + registration: { + principalId: sessionId, + principalClass: "boss-bound", + state: "active", + bossRunId: "run-a", + participantId, + bindingEpoch, + featureContract: BOSS_RUN_FEATURE_CONTRACT, + policySemanticsHash: BOSS_POLICY_SEMANTICS_HASH, + capabilityDigest: BOSS_CAPABILITY_FEATURE_DIGEST, + brokerIdentityVerified: true, + }, + principal: { + version: BOSS_POLICY_PRINCIPAL_VERSION, + principalId: sessionId, + principalClass: "boss-private", + state: "active", + bossRunId: "run-a", + participantId, + role, + bindingEpoch, + ...(worker + ? { assignedManagerParticipantId: "manager-a" } + : { assignedParticipantIds: ["worker-a"] }), + }, + binding: { + version: BOSS_PARTICIPANT_BINDING_VERSION, + bossRunId: "run-a", + participantId, + role, + communicationProfile: role, + bindingEpoch, + sessionId, + brokerGeneration: brokerGeneration(3), + brokerBootInstance: "boot-a", + state: "active", + ...(worker ? { assignedManagerParticipantId: "manager-a" } : {}), + authorityTransitionId: "transition-a", + }, + }, + }; +} + +function workerControlEnvelope() { + return { + type: "boss.assignment.submitted" as const, + version: 1 as const, + messageId: "control-delivery-1", + bossRunId: "run-a", + participantId: "worker-a", + bindingEpoch: participantBindingEpoch(2), + idempotencyKey: "assignment-a:submission:delivery", + payload: { assignmentId: "assignment-a" }, + }; +} test("cancelAsk resolves false after synchronous socket write failures", async () => { const client = new IntercomClient(); @@ -16,3 +96,361 @@ test("cancelAsk resolves false after synchronous socket write failures", async ( assert.equal(await client.cancelAsk("ask-1"), false); }); + +test("Boss typed control uses a distinct broker frame and stable message correlation", async () => { + const frames: Buffer[] = []; + const client = new IntercomClient(); + (client as any)._sessionId = "worker-session"; + (client as any).socket = { + destroyed: false, + writableEnded: false, + writable: true, + write(frame: Buffer) { + frames.push(frame); + return true; + }, + }; + const envelope = { + type: "boss.assignment.submitted" as const, + version: 1 as const, + messageId: "control-message-1", + bossRunId: "run-a", + participantId: "worker-a", + bindingEpoch: participantBindingEpoch(1), + causationId: "assignment-a", + replyTo: "control-message-0", + idempotencyKey: "assignment-a:submission:1", + payload: { assignmentId: "assignment-a" }, + }; + const pending = client.sendBossControl("manager-session", envelope); + const written = JSON.parse(frames[0]!.subarray(4).toString("utf8")); + assert.deepEqual(written, { type: "boss_control_send", to: "manager-session", envelope }); + assert.notEqual(written.type, "send"); + + (client as any).handleBrokerMessage({ + type: "boss_control_accepted", + messageId: envelope.messageId, + deliveryId: "delivery-1", + }); + (client as any).handleBrokerMessage({ + type: "boss_control_delivered", + messageId: envelope.messageId, + deliveryId: "delivery-1", + }); + assert.deepEqual(await pending, { + id: envelope.messageId, + accepted: true, + delivered: true, + deliveryId: "delivery-1", + }); +}); + +test("Boss control delivery requires accepted-to-delivered correlation on one delivery ID", async () => { + const frames: Buffer[] = []; + const client = new IntercomClient(); + (client as any)._sessionId = "worker-session"; + (client as any).socket = { + destroyed: false, + writableEnded: false, + writable: true, + write(frame: Buffer) { + frames.push(frame); + return true; + }, + }; + const envelope = { + type: "boss.assignment.submitted" as const, + version: 1 as const, + messageId: "control-state-machine", + bossRunId: "run-a", + participantId: "worker-a", + bindingEpoch: participantBindingEpoch(1), + idempotencyKey: "assignment-a:submission:state-machine", + payload: { assignmentId: "assignment-a" }, + }; + const pending = client.sendBossControl("manager-session", envelope); + assert.throws(() => (client as any).handleBrokerMessage({ + type: "boss_control_delivered", + messageId: envelope.messageId, + deliveryId: "delivery-a", + }), /did not follow matching acceptance/); + (client as any).handleBrokerMessage({ + type: "boss_control_accepted", + messageId: envelope.messageId, + deliveryId: "delivery-a", + }); + assert.throws(() => (client as any).handleBrokerMessage({ + type: "boss_control_accepted", + messageId: envelope.messageId, + deliveryId: "delivery-a", + }), /Duplicate Boss control acceptance/); + assert.throws(() => (client as any).handleBrokerMessage({ + type: "boss_control_delivered", + messageId: envelope.messageId, + deliveryId: "delivery-b", + }), /did not follow matching acceptance/); + assert.throws(() => (client as any).handleBrokerMessage({ + type: "boss_control_delivered", + messageId: envelope.messageId, + deliveryId: "delivery-a", + accepted: true, + }), /not supported/); + (client as any).handleBrokerMessage({ + type: "boss_control_delivered", + messageId: envelope.messageId, + deliveryId: "delivery-a", + }); + assert.equal((await pending).deliveryId, "delivery-a"); +}); + +test("Boss control failures have exact pre/post-acceptance shapes and delivery correlation", async () => { + const client = new IntercomClient(); + (client as any)._sessionId = "worker-session"; + (client as any).socket = { + destroyed: false, + writableEnded: false, + writable: true, + write() { + return true; + }, + }; + const base = { + type: "boss.assignment.submitted" as const, + version: 1 as const, + bossRunId: "run-a", + participantId: "worker-a", + bindingEpoch: participantBindingEpoch(1), + payload: { assignmentId: "assignment-a" }, + }; + + const preEnvelope = { + ...base, + messageId: "control-pre-failure", + idempotencyKey: "assignment-a:pre-failure", + }; + const pre = client.sendBossControl("manager-session", preEnvelope); + assert.throws(() => (client as any).handleBrokerMessage({ + type: "boss_control_failed", + messageId: preEnvelope.messageId, + deliveryId: "contradictory-delivery", + accepted: false, + code: "SESSION_NOT_FOUND", + reason: "missing", + }), /not supported/); + assert.throws(() => (client as any).handleBrokerMessage({ + type: "boss_control_failed", + messageId: preEnvelope.messageId, + accepted: false, + code: "UNKNOWN_FAILURE", + reason: "unknown", + }), /Invalid boss_control_failed message/); + (client as any).handleBrokerMessage({ + type: "boss_control_failed", + messageId: preEnvelope.messageId, + accepted: false, + code: "SESSION_NOT_FOUND", + reason: "missing", + }); + assert.deepEqual(await pre, { + id: preEnvelope.messageId, + accepted: false, + delivered: false, + code: "SESSION_NOT_FOUND", + reason: "missing", + }); + + const postEnvelope = { + ...base, + messageId: "control-post-failure", + idempotencyKey: "assignment-a:post-failure", + }; + const post = client.sendBossControl("manager-session", postEnvelope); + assert.throws(() => (client as any).handleBrokerMessage({ + type: "boss_control_failed", + messageId: postEnvelope.messageId, + deliveryId: "delivery-a", + accepted: true, + code: "DELIVERY_TIMEOUT", + reason: "timeout", + }), /acceptance state is inconsistent/); + (client as any).handleBrokerMessage({ + type: "boss_control_accepted", + messageId: postEnvelope.messageId, + deliveryId: "delivery-a", + }); + assert.throws(() => (client as any).handleBrokerMessage({ + type: "boss_control_failed", + messageId: postEnvelope.messageId, + deliveryId: "delivery-b", + accepted: true, + code: "DELIVERY_TIMEOUT", + reason: "timeout", + }), /did not follow matching acceptance/); + assert.throws(() => (client as any).handleBrokerMessage({ + type: "boss_control_failed", + messageId: postEnvelope.messageId, + accepted: true, + code: "DELIVERY_TIMEOUT", + reason: "timeout", + }), /deliveryId.*not supported|deliveryId/); + (client as any).handleBrokerMessage({ + type: "boss_control_failed", + messageId: postEnvelope.messageId, + deliveryId: "delivery-a", + accepted: true, + code: "DELIVERY_TIMEOUT", + reason: "timeout", + }); + assert.deepEqual(await post, { + id: postEnvelope.messageId, + accepted: true, + delivered: false, + deliveryId: "delivery-a", + code: "DELIVERY_TIMEOUT", + reason: "timeout", + }); +}); + +test("ordinary client rejects Boss session metadata and feature-shaped registration responses", () => { + const client = new IntercomClient(); + assert.throws(() => (client as any).handleBrokerMessage({ + type: "registered", + sessionId: "ordinary-session", + protocol: "pi-intercom", + version: 3, + capabilities: { baseProtocolVersion: 3, features: [] }, + }), /must not contain feature or Boss metadata/); + + (client as any)._sessionId = "ordinary-session"; + (client as any).pendingLists.set("list-a", { resolve() {}, reject() {} }); + assert.throws(() => (client as any).handleBrokerMessage({ + type: "sessions", + requestId: "list-a", + sessions: [{ + id: "boss-shaped", + cwd: "/repo", + model: "test", + pid: 1, + startedAt: 1, + lastActivity: 1, + boss: {}, + }], + }), /Invalid sessions message/); +}); + +test("broker-delivered Boss control accepts exact authoritative sender metadata", () => { + const client = new IntercomClient(); + (client as any)._sessionId = "manager-session"; + const sender = bossSender(); + const envelope = workerControlEnvelope(); + let received: unknown[] | undefined; + client.once("boss_control", (...args) => { + received = args; + }); + + (client as any).handleBrokerMessage({ + type: "boss_control", + deliveryId: "delivery-1", + from: sender, + envelope, + }); + + assert.deepEqual(received, [sender, envelope, "delivery-1"]); + assert.notStrictEqual(received![0], sender); +}); + +test("Boss control rejects ordinary, stale, substituted, and envelope-mismatched senders", () => { + const client = new IntercomClient(); + (client as any)._sessionId = "manager-session"; + const envelope = workerControlEnvelope(); + const ordinary = { + id: "ordinary-session", + cwd: "/repo", + model: "test", + pid: 1, + startedAt: 1, + lastActivity: 1, + }; + const stale = structuredClone(bossSender()); + stale.boss!.registration.state = "revoked"; + const substituted = structuredClone(bossSender()); + substituted.boss!.binding!.sessionId = "replacement-session"; + + for (const from of [ordinary, stale, substituted]) { + assert.throws(() => (client as any).handleBrokerMessage({ + type: "boss_control", + deliveryId: "delivery-1", + from, + envelope, + }), /Invalid boss_control event/); + } + + assert.throws(() => (client as any).handleBrokerMessage({ + type: "boss_control", + deliveryId: "delivery-1", + from: bossSender(), + envelope: { ...envelope, participantId: "substituted-worker" }, + }), /must match the sender/); +}); + +test("Boss control rejects proxy, inherited, accessor, extra, and sparse sender shapes", () => { + const client = new IntercomClient(); + (client as any)._sessionId = "manager-session"; + const envelope = workerControlEnvelope(); + const deliver = (from: unknown) => (client as any).handleBrokerMessage({ + type: "boss_control", + deliveryId: "delivery-1", + from, + envelope, + }); + + let proxyTraps = 0; + const proxied = new Proxy(bossSender(), { + get() { + proxyTraps += 1; + throw new Error("proxy get trap must not run"); + }, + ownKeys() { + proxyTraps += 1; + throw new Error("proxy ownKeys trap must not run"); + }, + }); + assert.throws(() => deliver(proxied), /Invalid boss_control event/); + assert.equal(proxyTraps, 0); + + const nestedProxy = bossSender(); + nestedProxy.boss = new Proxy(nestedProxy.boss!, { + ownKeys() { + proxyTraps += 1; + throw new Error("nested proxy ownKeys trap must not run"); + }, + }); + assert.throws(() => deliver(nestedProxy), /Invalid boss_control event/); + assert.equal(proxyTraps, 0); + + assert.throws(() => deliver(Object.create(bossSender())), /Invalid boss_control event/); + const inheritedBoss = bossSender(); + inheritedBoss.boss = Object.create(inheritedBoss.boss!); + assert.throws(() => deliver(inheritedBoss), /Invalid boss_control event/); + + let accessorReads = 0; + const accessor = bossSender(); + Object.defineProperty(accessor.boss!.registration, "bossRunId", { + enumerable: true, + get() { + accessorReads += 1; + return "run-a"; + }, + }); + assert.throws(() => deliver(accessor), /Invalid boss_control event/); + assert.equal(accessorReads, 0); + + assert.throws(() => deliver({ ...bossSender(), unexpectedAuthority: true }), /Invalid boss_control event/); + const extraBossField = bossSender(); + (extraBossField.boss as any).unexpectedAuthority = true; + assert.throws(() => deliver(extraBossField), /Invalid boss_control event/); + + const sparse = bossSender("manager"); + sparse.boss!.principal.assignedParticipantIds = new Array(1); + assert.throws(() => deliver(sparse), /Invalid boss_control event/); +}); diff --git a/broker/client.ts b/broker/client.ts index c96d794..e36c3af 100644 --- a/broker/client.ts +++ b/broker/client.ts @@ -1,7 +1,17 @@ import { EventEmitter } from "events"; import net from "net"; import { randomUUID } from "crypto"; -import { POLICY_SEMANTICS_HASH, POLICY_SEMANTICS_VERSION } from "@dataforxyz/agent-intercom-core"; +import { types as nodeUtilTypes } from "node:util"; +import { + POLICY_SEMANTICS_HASH, + POLICY_SEMANTICS_VERSION, +} from "@dataforxyz/agent-intercom-core"; +import { + parseBossControlEnvelope, + type BossControlEnvelope, +} from "@dataforxyz/agent-intercom-core/boss"; +import { assertExactKeys } from "@dataforxyz/agent-intercom-core/canonical"; +import { parseBossSessionMetadata, parseBoundBossControl } from "./boss.ts"; import { writeMessage, createMessageReader } from "./framing.ts"; import { PersistentOutboundOutbox } from "../outbound-outbox.ts"; import { loadRemoteAccessCredential, writeRemoteSessionCredential, type LoadedRemoteAccessCredential } from "./access-credential.ts"; @@ -101,6 +111,39 @@ function isMessage(value: unknown): value is Message { || (Array.isArray(content.attachments) && content.attachments.every(isAttachment)); } +const PRE_ACCEPT_BOSS_CONTROL_FAILURE_CODES: readonly DeliveryFailureCode[] = [ + "INVALID_BOSS_CONTROL", + "SESSION_NOT_FOUND", + "CONFLICTING_MESSAGE_ID", + "TOO_MANY_PENDING_DELIVERIES", + "BOSS_CONTROL_DENIED", +]; + +const POST_ACCEPT_BOSS_CONTROL_FAILURE_CODES: readonly DeliveryFailureCode[] = [ + "BOSS_CONTROL_DENIED", + "RECIPIENT_DISCONNECTED", + "SENDER_DISCONNECTED", + "DELIVERY_TIMEOUT", +]; + +function isBossControlFailureCode(value: unknown, accepted: boolean): value is DeliveryFailureCode { + return typeof value === "string" && ( + accepted ? POST_ACCEPT_BOSS_CONTROL_FAILURE_CODES : PRE_ACCEPT_BOSS_CONTROL_FAILURE_CODES + ).includes(value as DeliveryFailureCode); +} + +function exactBossControlFrame( + frame: Record, + required: string[], + path: string, +): void { + assertExactKeys(frame, required, [], path); +} + +function bossControlFrameString(value: unknown, path: string): asserts value is string { + if (typeof value !== "string" || value.length === 0) throw new Error(`${path} must be a non-empty string`); +} + function isSessionInfo(value: unknown): value is SessionInfo { if (typeof value !== "object" || value === null) { return false; @@ -141,7 +184,136 @@ function isSessionInfo(value: unknown): value is SessionInfo { for (const field of ["depth", "maxDepth", "maxChildren"] as const) { if (session[field] !== undefined && (typeof session[field] !== "number" || !Number.isSafeInteger(session[field]))) return false; } - return true; + return session.boss === undefined; +} + +const BOSS_SESSION_REQUIRED_FIELDS = [ + "id", + "cwd", + "model", + "pid", + "startedAt", + "lastActivity", + "boss", +] as const; + +const BOSS_SESSION_OPTIONAL_FIELDS = [ + "name", + "status", + "peerUid", + "trustedLocal", + "origin", + "remoteHostId", + "parentSessionId", + "rootSessionId", + "generation", + "canDelegate", + "depth", + "maxDepth", + "maxChildren", +] as const; + +/** + * Snapshot broker-owned data without invoking source accessors or proxy traps. + * The resulting tree has only plain records and dense arrays, so the Core and + * broker parsers can safely enforce their exact semantic shapes afterwards. + */ +function snapshotBossData( + value: unknown, + path: string, + seen: WeakSet = new WeakSet(), + depth = 0, +): unknown { + if (value === null || typeof value === "string" || typeof value === "boolean") return value; + if (typeof value === "number") { + if (!Number.isFinite(value) || Object.is(value, -0)) throw new Error(`${path} must be a JSON number`); + return value; + } + if (typeof value !== "object" || nodeUtilTypes.isProxy(value)) { + throw new Error(`${path} must be unproxied broker-owned data`); + } + if (depth >= 32 || seen.has(value)) throw new Error(`${path} must be an acyclic bounded data tree`); + seen.add(value); + + if (Array.isArray(value)) { + if (Object.getPrototypeOf(value) !== Array.prototype) throw new Error(`${path} must be a plain array`); + const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length"); + if ( + lengthDescriptor === undefined + || !Object.hasOwn(lengthDescriptor, "value") + || !Number.isSafeInteger(lengthDescriptor.value) + || lengthDescriptor.value < 0 + ) { + throw new Error(`${path} must be a dense array`); + } + const entries = new Map(); + for (const key of Reflect.ownKeys(value)) { + if (key === "length") continue; + if (typeof key !== "string") throw new Error(`${path} must not contain symbol properties`); + const index = Number(key); + if ( + !Number.isInteger(index) + || index < 0 + || index >= lengthDescriptor.value + || String(index) !== key + ) { + throw new Error(`${path}.${key} is not a supported array index`); + } + const descriptor = Object.getOwnPropertyDescriptor(value, key); + if (descriptor === undefined || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) { + throw new Error(`${path}[${index}] must be an enumerable data property`); + } + entries.set(index, snapshotBossData(descriptor.value, `${path}[${index}]`, seen, depth + 1)); + } + if (entries.size !== lengthDescriptor.value) throw new Error(`${path} must not contain sparse array holes`); + return Array.from({ length: lengthDescriptor.value }, (_, index) => entries.get(index)); + } + + if (Object.getPrototypeOf(value) !== Object.prototype) throw new Error(`${path} must be a plain object`); + const snapshot: Record = {}; + for (const key of Reflect.ownKeys(value)) { + if (typeof key !== "string") throw new Error(`${path} must not contain symbol properties`); + const descriptor = Object.getOwnPropertyDescriptor(value, key); + if (descriptor === undefined || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) { + throw new Error(`${path}.${key} must be an enumerable data property`); + } + Object.defineProperty(snapshot, key, { + configurable: true, + enumerable: true, + value: snapshotBossData(descriptor.value, `${path}.${key}`, seen, depth + 1), + writable: true, + }); + } + return snapshot; +} + +function authoritativeBossSessionInfo(value: unknown): SessionInfo | undefined { + try { + const snapshot = snapshotBossData(value, "$.boss_control.from"); + if (typeof snapshot !== "object" || snapshot === null || Array.isArray(snapshot)) return undefined; + const session = snapshot as Record; + assertExactKeys( + session, + [...BOSS_SESSION_REQUIRED_FIELDS], + [...BOSS_SESSION_OPTIONAL_FIELDS], + "$.boss_control.from", + ); + + const { boss, ...ordinaryFields } = session; + if (!isSessionInfo(ordinaryFields)) return undefined; + const parsedBoss = parseBossSessionMetadata(boss, ordinaryFields.id); + if ( + parsedBoss.registration.state !== "active" + || !parsedBoss.registration.brokerIdentityVerified + || parsedBoss.principal.state !== "active" + || (parsedBoss.binding !== undefined && parsedBoss.binding.state !== "active") + ) { + return undefined; + } + return { ...ordinaryFields, boss: parsedBoss }; + } catch { + return undefined; + } } function isRemoteAccessMetadata(value: unknown): value is import("../types.ts").RemoteAccessMetadata { @@ -175,6 +347,12 @@ export class IntercomClient extends EventEmitter { }>(); private pendingLists = new Map void; reject: (e: Error) => void }>(); private pendingAskControls = new Map void; timeout: NodeJS.Timeout }>(); + private pendingBossControls = new Map void; + reject: (error: Error) => void; + }>(); private outbox: PersistentOutboundOutbox | null = null; private remoteAccessCredential: LoadedRemoteAccessCredential | undefined; private disconnecting = false; @@ -194,6 +372,8 @@ export class IntercomClient extends EventEmitter { pending.resolve(false); } this.pendingAskControls.clear(); + for (const pending of this.pendingBossControls.values()) pending.reject(error); + this.pendingBossControls.clear(); } get sessionId(): string | null { @@ -382,6 +562,10 @@ export class IntercomClient extends EventEmitter { throw new Error("Invalid registered message"); } + if (brokerMessage.boss !== undefined || brokerMessage.capabilities !== undefined) { + throw new Error("Ordinary registration must not contain feature or Boss metadata"); + } + if (this._sessionId !== null) { throw new Error("Received duplicate registered message"); } @@ -446,6 +630,85 @@ export class IntercomClient extends EventEmitter { break; } + case "boss_control": { + const { deliveryId, envelope } = brokerMessage; + const from = authoritativeBossSessionInfo(brokerMessage.from); + if (typeof deliveryId !== "string" || from === undefined) throw new Error("Invalid boss_control event"); + const parsed = parseBoundBossControl( + snapshotBossData(envelope, "$.boss_control.envelope"), + from, + ); + this.emit("boss_control", from, parsed, deliveryId); + break; + } + + case "boss_control_accepted": { + exactBossControlFrame(brokerMessage, ["type", "messageId", "deliveryId"], "$.boss_control_accepted"); + const { deliveryId, messageId } = brokerMessage; + bossControlFrameString(deliveryId, "$.boss_control_accepted.deliveryId"); + bossControlFrameString(messageId, "$.boss_control_accepted.messageId"); + const pending = this.pendingBossControls.get(messageId); + if (!pending) break; + if (pending.accepted) throw new Error("Duplicate Boss control acceptance"); + if (pending.deliveryId !== undefined) throw new Error("Boss control acceptance state is contradictory"); + pending.accepted = true; + pending.deliveryId = deliveryId as string; + break; + } + + case "boss_control_delivered": { + exactBossControlFrame(brokerMessage, ["type", "messageId", "deliveryId"], "$.boss_control_delivered"); + const { deliveryId, messageId } = brokerMessage; + bossControlFrameString(deliveryId, "$.boss_control_delivered.deliveryId"); + bossControlFrameString(messageId, "$.boss_control_delivered.messageId"); + const pending = this.pendingBossControls.get(messageId); + if (!pending) break; + if (!pending.accepted || pending.deliveryId !== deliveryId) { + throw new Error("Boss control delivery did not follow matching acceptance"); + } + this.pendingBossControls.delete(messageId); + pending.resolve({ id: messageId, accepted: true, delivered: true, deliveryId }); + break; + } + + case "boss_control_failed": { + const { accepted } = brokerMessage; + if (typeof accepted !== "boolean") throw new Error("Invalid boss_control_failed message"); + exactBossControlFrame( + brokerMessage, + accepted + ? ["type", "messageId", "deliveryId", "accepted", "code", "reason"] + : ["type", "messageId", "accepted", "code", "reason"], + "$.boss_control_failed", + ); + const { code, deliveryId, messageId, reason } = brokerMessage; + if ( + !isBossControlFailureCode(code, accepted) + || typeof reason !== "string" + || reason.length === 0 + ) { + throw new Error("Invalid boss_control_failed message"); + } + bossControlFrameString(messageId, "$.boss_control_failed.messageId"); + if (accepted) bossControlFrameString(deliveryId, "$.boss_control_failed.deliveryId"); + const pending = this.pendingBossControls.get(messageId); + if (!pending) break; + if (accepted !== pending.accepted) throw new Error("Boss control failure acceptance state is inconsistent"); + if (accepted && pending.deliveryId !== deliveryId) { + throw new Error("Boss control failure did not follow matching acceptance"); + } + this.pendingBossControls.delete(messageId); + pending.resolve({ + id: messageId, + accepted, + delivered: false, + code, + reason, + ...(accepted ? { deliveryId: deliveryId as string } : {}), + }); + break; + } + case "delivery_accepted": { const { deliveryId, messageId } = brokerMessage; if (typeof deliveryId !== "string" || typeof messageId !== "string") { @@ -739,10 +1002,59 @@ export class IntercomClient extends EventEmitter { }); } + sendBossControl(to: string, envelopeValue: BossControlEnvelope): Promise { + let socket: net.Socket; + try { + socket = this.requireActiveSocket(); + } catch (error) { + return Promise.reject(toError(error)); + } + let envelope: BossControlEnvelope; + try { + envelope = parseBossControlEnvelope(envelopeValue); + } catch (error) { + return Promise.reject(toError(error)); + } + if (this.pendingBossControls.has(envelope.messageId)) { + return Promise.resolve({ + id: envelope.messageId, + accepted: false, + delivered: false, + code: "CONFLICTING_MESSAGE_ID", + reason: `Boss control message ID ${envelope.messageId} is already pending`, + }); + } + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + if (!this.pendingBossControls.delete(envelope.messageId)) return; + reject(new Error("Boss control send timeout")); + }, 10000); + const wrappedResolve = (result: SendResult) => { + clearTimeout(timeout); + resolve(result); + }; + const wrappedReject = (error: Error) => { + clearTimeout(timeout); + reject(error); + }; + this.pendingBossControls.set(envelope.messageId, { accepted: false, resolve: wrappedResolve, reject: wrappedReject }); + try { + writeMessage(socket, { type: "boss_control_send", to, envelope }); + } catch (error) { + this.pendingBossControls.delete(envelope.messageId); + wrappedReject(toError(error)); + } + }); + } + acknowledgeMessage(deliveryId: string): boolean { return this.writeControlMessage({ type: "message_received", deliveryId }); } + acknowledgeBossControl(deliveryId: string): boolean { + return this.writeControlMessage({ type: "boss_control_received", deliveryId }); + } + rejectMessage(deliveryId: string, reason: string): boolean { return this.writeControlMessage({ type: "message_rejected", deliveryId, code: "CONFLICTING_MESSAGE_ID", reason }); } diff --git a/broker/negotiation.test.ts b/broker/negotiation.test.ts new file mode 100644 index 0000000..7615d9d --- /dev/null +++ b/broker/negotiation.test.ts @@ -0,0 +1,31 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { INTERCOM_BASE_PROTOCOL_VERSION } from "@dataforxyz/agent-intercom-core/boss"; +import { INTERCOM_PROTOCOL_VERSION } from "./paths.ts"; +import { + DORMANT_BROKER_CAPABILITIES, + ORDINARY_BASE3_COMPATIBILITY, + negotiateBrokerCompatibility, +} from "./negotiation.ts"; + +test("OpenCode negotiates the exact Core base-3 contract while Boss stays dormant", () => { + assert.equal(INTERCOM_PROTOCOL_VERSION, 3); + assert.equal(INTERCOM_PROTOCOL_VERSION, INTERCOM_BASE_PROTOCOL_VERSION); + assert.deepEqual(DORMANT_BROKER_CAPABILITIES, { baseProtocolVersion: 3, features: [] }); + assert.deepEqual(negotiateBrokerCompatibility(ORDINARY_BASE3_COMPATIBILITY), { + compatible: true, + mode: "ordinary", + }); +}); + +test("negotiation rejects alternate base versions and incomplete Boss requests", () => { + assert.deepEqual(negotiateBrokerCompatibility({ + clientKind: "ordinary", + supportedBaseProtocolVersions: [2], + }), { compatible: false, code: "BASE_PROTOCOL_UNSUPPORTED" }); + assert.deepEqual(negotiateBrokerCompatibility({ + clientKind: "boss", + supportedBaseProtocolVersions: [3], + requiredFeature: "boss-run-v1", + }), { compatible: false, code: "INVALID_COMPATIBILITY_REQUEST" }); +}); diff --git a/broker/negotiation.ts b/broker/negotiation.ts new file mode 100644 index 0000000..b05e8c7 --- /dev/null +++ b/broker/negotiation.ts @@ -0,0 +1,35 @@ +import { + INTERCOM_BASE_PROTOCOL_VERSION, + evaluateBrokerCompatibility, + parseBrokerCapabilityAdvertisement, + type BrokerCapabilityAdvertisement, + type BrokerCompatibilityDecision, + type BrokerCompatibilityRequest, +} from "@dataforxyz/agent-intercom-core/boss"; +import { INTERCOM_PROTOCOL_VERSION } from "./paths.ts"; + +if (INTERCOM_PROTOCOL_VERSION !== INTERCOM_BASE_PROTOCOL_VERSION) { + throw new Error( + `OpenCode protocol v${INTERCOM_PROTOCOL_VERSION} diverges from Core base protocol v${INTERCOM_BASE_PROTOCOL_VERSION}`, + ); +} + +/** + * Publishing Boss contracts is not readiness. The OpenCode provider remains + * ordinary/base-3 only until protected identity, credentials, transitions, + * health, and ledger predicates are implemented in lockstep. + */ +export const DORMANT_BROKER_CAPABILITIES: BrokerCapabilityAdvertisement = + Object.freeze(parseBrokerCapabilityAdvertisement({ + baseProtocolVersion: INTERCOM_BASE_PROTOCOL_VERSION, + features: [], + })); + +export const ORDINARY_BASE3_COMPATIBILITY: BrokerCompatibilityRequest = Object.freeze({ + clientKind: "ordinary", + supportedBaseProtocolVersions: [INTERCOM_BASE_PROTOCOL_VERSION], +}); + +export function negotiateBrokerCompatibility(value: unknown): BrokerCompatibilityDecision { + return evaluateBrokerCompatibility(value, DORMANT_BROKER_CAPABILITIES); +} diff --git a/broker/paths.test.ts b/broker/paths.test.ts index 20502de..49f43bd 100644 --- a/broker/paths.test.ts +++ b/broker/paths.test.ts @@ -11,6 +11,7 @@ import { getBrokerAdminCredentialFilePath, getBrokerAskStateFilePath, getBrokerAuditFilePath, + getBrokerBossControlStateFilePath, getBrokerListenTarget, getBrokerPortFilePath, getBrokerSocketPath, @@ -41,6 +42,7 @@ test("getAgentDirPath resolves relative PI_CODING_AGENT_DIR values from the call test("getIntercomDirPath points at the intercom runtime directory under the agent dir", () => { assert.equal(getIntercomDirPath("/tmp/pi-agent"), join("/tmp/pi-agent", "intercom")); assert.equal(getBrokerAskStateFilePath("/tmp/pi-agent/intercom"), join("/tmp/pi-agent/intercom", "broker-asks.json")); + assert.equal(getBrokerBossControlStateFilePath("/tmp/pi-agent/intercom"), join("/tmp/pi-agent/intercom", "broker-boss-controls.json")); assert.equal(getBrokerAccessStateFilePath("/tmp/pi-agent/intercom"), join("/tmp/pi-agent/intercom", "broker-access.json")); assert.equal(getBrokerAdminCredentialFilePath("/tmp/pi-agent/intercom"), join("/tmp/pi-agent/intercom", "broker-admin.json")); assert.equal(getBrokerAuditFilePath("/tmp/pi-agent/intercom"), join("/tmp/pi-agent/intercom", "broker-audit.jsonl")); diff --git a/broker/paths.ts b/broker/paths.ts index 95285e1..2336581 100644 --- a/broker/paths.ts +++ b/broker/paths.ts @@ -66,6 +66,10 @@ export function getBrokerAskStateFilePath(intercomDir: string = getIntercomDirPa return join(intercomDir, "broker-asks.json"); } +export function getBrokerBossControlStateFilePath(intercomDir: string = getIntercomDirPath()): string { + return join(intercomDir, "broker-boss-controls.json"); +} + export function getBrokerAccessStateFilePath(intercomDir: string = getIntercomDirPath()): string { return join(intercomDir, "broker-access.json"); } diff --git a/broker/spawn.ts b/broker/spawn.ts index e75df07..4d780ed 100644 --- a/broker/spawn.ts +++ b/broker/spawn.ts @@ -5,7 +5,10 @@ import { fileURLToPath } from "url"; import { createRequire } from "module"; import net from "net"; import { randomUUID } from "crypto"; -import { POLICY_SEMANTICS_HASH, POLICY_SEMANTICS_VERSION } from "@dataforxyz/agent-intercom-core"; +import { + POLICY_SEMANTICS_HASH, + POLICY_SEMANTICS_VERSION, +} from "@dataforxyz/agent-intercom-core"; import { createMessageReader, writeMessage } from "./framing.ts"; import { ensureIntercomRuntimeDir, diff --git a/dist/broker.mjs b/dist/broker.mjs index af8371b..ebbc084 100644 --- a/dist/broker.mjs +++ b/dist/broker.mjs @@ -1,214 +1,15 @@ // broker/broker.ts import net from "net"; -import { existsSync as existsSync2, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs"; +import { existsSync as existsSync3, readFileSync as readFileSync5, renameSync as renameSync2, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs"; import { join as join2 } from "path"; import { randomUUID as randomUUID3 } from "crypto"; - -// ../../src/github.com/dataforxyz/agent-intercom-opencode/node_modules/@dataforxyz/agent-intercom-core/dist/policy.js -var POLICY_SEMANTICS_VERSION = 2; -function activePrincipal(state, id) { - return state.principals[id]; -} -function isDirectParentPair(left, right) { - return left.parentSessionId === right.id || right.parentSessionId === left.id; -} -function isAncestor(state, ancestorId, descendantId) { - if (ancestorId === descendantId) - return false; - const visited = /* @__PURE__ */ new Set(); - let current = state.principals[descendantId]; - while (current?.parentSessionId && !visited.has(current.id)) { - if (current.parentSessionId === ancestorId) - return true; - visited.add(current.id); - current = state.principals[current.parentSessionId]; - } - return false; -} -function authorize(state, actorId, action, targetId, context = {}) { - const actor = activePrincipal(state, actorId); - const target = activePrincipal(state, targetId); - if (!actor || !target) - return { allowed: false, code: "UNKNOWN_PRINCIPAL" }; - if (actor.state !== "active" || target.state !== "active") - return { allowed: false, code: "REVOKED_PRINCIPAL" }; - if (context.actorGeneration !== void 0 && context.actorGeneration !== actor.generation || context.targetGeneration !== void 0 && context.targetGeneration !== target.generation) { - return { allowed: false, code: "STALE_GENERATION" }; - } - if (actor.id === target.id) - return { allowed: true, reason: "self" }; - if (actor.kind === "local" && target.kind === "local") - return { allowed: true, reason: "local-public" }; - if (action === "discover" || action === "send" || action === "ask" || action === "reply") { - if (isDirectParentPair(actor, target)) - return { allowed: true, reason: "direct-parent" }; - if (isAncestor(state, actor.id, target.id) || isAncestor(state, target.id, actor.id)) { - return { allowed: true, reason: "ancestor-chain" }; - } - } - if (action === "inspect_tree" || action === "revoke" || action === "adopt") { - if (isAncestor(state, actor.id, target.id)) - return { allowed: true, reason: "ancestor-control" }; - } - return { allowed: false, code: "POLICY_DENIED" }; -} - -// ../../src/github.com/dataforxyz/agent-intercom-opencode/node_modules/@dataforxyz/agent-intercom-core/dist/policy-vectors.js -var localRoot = { - id: "local-root", - kind: "local", - state: "active", - generation: 1, - policy: "local-public", - rootSessionId: "local-root" -}; -var localPeer = { - id: "local-peer", - kind: "local", - state: "active", - generation: 1, - policy: "local-public", - rootSessionId: "local-peer" -}; -var remoteManager = { - id: "remote-manager", - kind: "remote", - state: "active", - generation: 1, - policy: "remote-tree", - parentSessionId: "local-root", - rootSessionId: "local-root" -}; -var remoteChild = { - id: "remote-child", - kind: "remote", - state: "active", - generation: 1, - policy: "remote-tree", - parentSessionId: "remote-manager", - rootSessionId: "local-root" -}; -var remoteSibling = { - id: "remote-sibling", - kind: "remote", - state: "active", - generation: 1, - policy: "remote-tree", - parentSessionId: "remote-manager", - rootSessionId: "local-root" -}; -var POLICY_VECTORS = [ - { - name: "local sessions remain public", - principals: [localRoot, localPeer], - actorId: "local-root", - action: "send", - targetId: "local-peer", - expectedAllowed: true, - expectedReasonOrCode: "local-public" - }, - { - name: "remote manager can reach direct local parent", - principals: [localRoot, remoteManager], - actorId: "remote-manager", - action: "send", - targetId: "local-root", - expectedAllowed: true, - expectedReasonOrCode: "direct-parent" - }, - { - name: "local parent can reach direct remote child", - principals: [localRoot, remoteManager], - actorId: "local-root", - action: "ask", - targetId: "remote-manager", - expectedAllowed: true, - expectedReasonOrCode: "direct-parent" - }, - { - name: "remote child can reach its local root through the ancestor chain", - principals: [localRoot, remoteManager, remoteChild], - actorId: "remote-child", - action: "send", - targetId: "local-root", - expectedAllowed: true, - expectedReasonOrCode: "ancestor-chain" - }, - { - name: "remote siblings cannot communicate in phase one", - principals: [localRoot, remoteManager, remoteChild, remoteSibling], - actorId: "remote-child", - action: "discover", - targetId: "remote-sibling", - expectedAllowed: false, - expectedReasonOrCode: "POLICY_DENIED" - }, - { - name: "unrelated local session cannot discover remote principal", - principals: [localRoot, localPeer, remoteManager], - actorId: "local-peer", - action: "discover", - targetId: "remote-manager", - expectedAllowed: false, - expectedReasonOrCode: "POLICY_DENIED" - }, - { - name: "remote principal cannot reach unrelated local session", - principals: [localRoot, localPeer, remoteManager], - actorId: "remote-manager", - action: "send", - targetId: "local-peer", - expectedAllowed: false, - expectedReasonOrCode: "POLICY_DENIED" - }, - { - name: "remote manager may inspect its descendant subtree", - principals: [localRoot, remoteManager, remoteChild], - actorId: "remote-manager", - action: "inspect_tree", - targetId: "remote-child", - expectedAllowed: true, - expectedReasonOrCode: "ancestor-control" - }, - { - name: "remote child cannot revoke its ancestor", - principals: [localRoot, remoteManager, remoteChild], - actorId: "remote-child", - action: "revoke", - targetId: "remote-manager", - expectedAllowed: false, - expectedReasonOrCode: "POLICY_DENIED" - }, - { - name: "remote principal may request attenuated delegation under itself", - principals: [localRoot, remoteManager], - actorId: "remote-manager", - action: "delegate_child", - targetId: "remote-manager", - expectedAllowed: true, - expectedReasonOrCode: "self" - }, - { - name: "revoked principal cannot communicate", - principals: [localRoot, { ...remoteManager, state: "revoked" }], - actorId: "remote-manager", - action: "send", - targetId: "local-root", - expectedAllowed: false, - expectedReasonOrCode: "REVOKED_PRINCIPAL" - }, - { - name: "stale actor generation cannot send", - principals: [localRoot, { ...remoteManager, generation: 2 }], - actorId: "remote-manager", - action: "send", - targetId: "local-root", - context: { actorGeneration: 1 }, - expectedAllowed: false, - expectedReasonOrCode: "STALE_GENERATION" - } -]; -var POLICY_SEMANTICS_HASH = "f3b00e503631bc91123aedfbcf1df72cc9913e1893c09728b2c598f3dcdfdfe0"; +import { pathToFileURL } from "node:url"; +import { + authorize, + POLICY_SEMANTICS_HASH, + POLICY_SEMANTICS_VERSION +} from "@dataforxyz/agent-intercom-core"; +import { assertExactKeys as assertExactKeys3 } from "@dataforxyz/agent-intercom-core/canonical"; // broker/framing.ts var MAX_FRAME_BYTES = 1024 * 1024; @@ -313,6 +114,9 @@ function getBrokerPortFilePath(intercomDir = getIntercomDirPath()) { function getBrokerAskStateFilePath(intercomDir = getIntercomDirPath()) { return join(intercomDir, "broker-asks.json"); } +function getBrokerBossControlStateFilePath(intercomDir = getIntercomDirPath()) { + return join(intercomDir, "broker-boss-controls.json"); +} function getBrokerAccessStateFilePath(intercomDir = getIntercomDirPath()) { return join(intercomDir, "broker-access.json"); } @@ -377,23 +181,44 @@ function getAskTimeoutMs() { import { randomUUID } from "crypto"; import { closeSync, fsyncSync, openSync, renameSync, writeFileSync } from "fs"; import { dirname } from "path"; -function writeDurableJson(filePath, value) { +var DURABLE_JSON_FILE_OPERATIONS = Object.freeze({ + writeFile(filePath, contents, options) { + writeFileSync(filePath, contents, options); + }, + open(filePath, flags) { + return openSync(filePath, flags); + }, + fsync(fileDescriptor) { + fsyncSync(fileDescriptor); + }, + close(fileDescriptor) { + closeSync(fileDescriptor); + }, + rename(from, to) { + renameSync(from, to); + }, + restrict(filePath) { + restrictIntercomRuntimeFile(filePath); + }, + platform: process.platform +}); +function writeDurableJson(filePath, value, operations = DURABLE_JSON_FILE_OPERATIONS) { const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`; - writeFileSync(temporaryPath, JSON.stringify(value), { encoding: "utf-8", mode: INTERCOM_RUNTIME_FILE_MODE }); - const fileDescriptor = openSync(temporaryPath, "r"); + operations.writeFile(temporaryPath, JSON.stringify(value), { encoding: "utf-8", mode: INTERCOM_RUNTIME_FILE_MODE }); + const fileDescriptor = operations.open(temporaryPath, "r"); try { - fsyncSync(fileDescriptor); + operations.fsync(fileDescriptor); } finally { - closeSync(fileDescriptor); + operations.close(fileDescriptor); } - renameSync(temporaryPath, filePath); - restrictIntercomRuntimeFile(filePath); - if (process.platform !== "win32") { - const directoryDescriptor = openSync(dirname(filePath), "r"); + operations.rename(temporaryPath, filePath); + operations.restrict(filePath); + if (operations.platform !== "win32") { + const directoryDescriptor = operations.open(dirname(filePath), "r"); try { - fsyncSync(directoryDescriptor); + operations.fsync(directoryDescriptor); } finally { - closeSync(directoryDescriptor); + operations.close(directoryDescriptor); } } } @@ -798,6 +623,88 @@ var RemoteAccessRegistry = class { } }; +// broker/authorization.ts +import { + authorizeFeatureAware +} from "@dataforxyz/agent-intercom-core/boss"; + +// broker/boss.ts +import { + BOSS_CONTROL_TYPES, + parseBossControlEnvelope, + parseBossParticipantBinding, + parseBossPolicyPrincipal, + parseFeatureRegistration +} from "@dataforxyz/agent-intercom-core/boss"; +import { + ContractValidationError, + assertExactKeys, + assertRecord +} from "@dataforxyz/agent-intercom-core/canonical"; +var CONTROL_KIND_BY_TYPE = { + "boss.assignment.created": "assignment_request", + "boss.assignment.accepted": "assignment_response", + "boss.assignment.checkpoint": "assignment_response", + "boss.assignment.submitted": "assignment_response", + "boss.assignment.rejected": "assignment_response", + "boss.assignment.cancelled": "lifecycle", + "boss.staffing.requested": "staffing", + "boss.staffing.resolved": "staffing", + "boss.review.requested": "review_request", + "boss.review.submitted": "review_result", + "boss.council.requested": "review_request", + "boss.council.submitted": "review_result", + "boss.proof.submitted": "proof", + "boss.worker.health": "health", + "boss.worker.blocked": "health", + "boss.worker.failed": "health", + "boss.worker.notice": "lifecycle", + "boss.worker.notice_delivery_failed": "lifecycle", + "boss.decision.required": "decision" +}; +if (Object.keys(CONTROL_KIND_BY_TYPE).length !== BOSS_CONTROL_TYPES.length) { + throw new Error("Boss control type mapping is incomplete"); +} +function bossControlKind(type) { + return CONTROL_KIND_BY_TYPE[type]; +} +function parseBossSessionMetadata(value, sessionId) { + assertRecord(value, "$.boss"); + assertExactKeys(value, ["registration", "principal"], ["binding"], "$.boss"); + const metadata = value; + const registration = parseFeatureRegistration(metadata.registration); + const principal = parseBossPolicyPrincipal(metadata.principal); + if (registration.principalClass !== "boss-bound" || principal.principalClass !== "boss-private") { + throw new ContractValidationError("$.boss", "must contain Boss-bound registration and private principal metadata"); + } + if (registration.principalId !== sessionId || principal.principalId !== sessionId || registration.bossRunId !== principal.bossRunId || registration.participantId !== principal.participantId || registration.bindingEpoch !== principal.bindingEpoch) { + throw new ContractValidationError("$.boss", "registration and principal identity bindings must exactly match the session"); + } + const binding = metadata.binding === void 0 ? void 0 : parseBossParticipantBinding(metadata.binding); + if (principal.role === "controller") { + if (binding !== void 0) throw new ContractValidationError("$.boss.binding", "is forbidden for Controller principals"); + } else { + if (binding === void 0) throw new ContractValidationError("$.boss.binding", "is required for Boss participants"); + if (binding.sessionId !== sessionId || binding.bossRunId !== principal.bossRunId || binding.participantId !== principal.participantId || binding.role !== principal.role || binding.bindingEpoch !== principal.bindingEpoch || binding.state !== principal.state || binding.assignedManagerParticipantId !== principal.assignedManagerParticipantId) { + throw new ContractValidationError("$.boss.binding", "must exactly match the authenticated session principal"); + } + } + return { registration, principal, ...binding === void 0 ? {} : { binding } }; +} +function validatedBossMetadata(session) { + if (session.boss === void 0) return void 0; + return parseBossSessionMetadata(session.boss, session.id); +} +function parseBoundBossControl(value, sender) { + const envelope = parseBossControlEnvelope(value); + const boss = validatedBossMetadata(sender); + if (!boss) throw new ContractValidationError("$.envelope", "sender is not an authenticated Boss participant"); + if (envelope.bossRunId !== boss.principal.bossRunId || envelope.participantId !== boss.principal.participantId || envelope.bindingEpoch !== boss.principal.bindingEpoch) { + throw new ContractValidationError("$.envelope", "run, participant, and binding epoch must match the sender"); + } + return envelope; +} + // broker/authorization.ts function policyPrincipalForSession(session) { if (session.origin === "remote") { @@ -825,23 +732,389 @@ function policyPrincipalForSession(session) { } function policyStateForSessions(sessions) { const principals = {}; - for (const session of sessions) principals[session.id] = policyPrincipalForSession(session); + for (const session of sessions) { + if (session.boss === void 0) principals[session.id] = policyPrincipalForSession(session); + } return { principals }; } -function authorizeSessionAction(sessions, actorId, action, targetId) { - const state = policyStateForSessions(sessions); - const actor = state.principals[actorId]; - const target = state.principals[targetId]; - return authorize(state, actorId, action, targetId, { - actorGeneration: actor?.generation, - targetGeneration: target?.generation - }); +function featurePolicyStateForSessions(sessions) { + const values = Array.from(sessions); + const legacy = policyStateForSessions(values); + const registrations = {}; + const boss = { principals: {} }; + for (const session of values) { + let metadata; + try { + metadata = validatedBossMetadata(session); + } catch { + registrations[session.id] = {}; + continue; + } + if (metadata) { + registrations[session.id] = metadata.registration; + boss.principals[session.id] = metadata.principal; + } else { + registrations[session.id] = { + principalId: session.id, + principalClass: "ordinary", + state: "active" + }; + } + } + return { legacy, boss, registrations }; +} +function authorizeSessionAction(sessions, actorId, action, targetId, bossContext) { + const state = featurePolicyStateForSessions(sessions); + const actorRegistration = state.registrations[actorId]; + const targetRegistration = state.registrations[targetId]; + const request = { + actorId, + action, + targetId, + ...actorRegistration?.principalClass === "boss-bound" || targetRegistration?.principalClass === "boss-bound" ? { bossContext } : { + legacyContext: { + actorGeneration: state.legacy.principals[actorId]?.generation, + targetGeneration: state.legacy.principals[targetId]?.generation + } + } + }; + return authorizeFeatureAware(state, request); } function visibleSessions(sessions, actorId) { const values = Array.from(sessions); return values.filter((target) => authorizeSessionAction(values, actorId, "discover", target.id).allowed); } +// broker/boss-control-store.ts +import { existsSync as existsSync2, readFileSync as readFileSync4 } from "node:fs"; +import { dirname as dirname2 } from "node:path"; +import { + parseBossControlEnvelope as parseBossControlEnvelope2 +} from "@dataforxyz/agent-intercom-core/boss"; +import { + canonicalJson, + assertExactKeys as assertExactKeys2, + assertRecord as assertRecord2, + participantBindingEpoch +} from "@dataforxyz/agent-intercom-core/canonical"; +var STATE_VERSION = 1; +var CONTROL_KINDS = [ + "assignment_request", + "assignment_response", + "health", + "staffing", + "review_request", + "review_result", + "proof", + "lifecycle", + "decision" +]; +var TERMINAL_FAILURE_CODES = [ + "BOSS_CONTROL_DENIED", + "RECIPIENT_DISCONNECTED", + "SENDER_DISCONNECTED", + "DELIVERY_TIMEOUT" +]; +function recordValue(value, path) { + assertRecord2(value, path); + return value; +} +function exactKeys(value, required, optional, path) { + assertExactKeys2(value, required, optional, path); +} +function stringValue(value, path) { + if (typeof value !== "string" || value.length === 0) throw new Error(`${path} must be a non-empty string`); + return value; +} +function timestampValue(value, path) { + const timestamp = stringValue(value, path); + if (!Number.isFinite(Date.parse(timestamp))) throw new Error(`${path} must be a timestamp`); + return timestamp; +} +function bossControlIdempotencyScope(identity) { + return canonicalJson({ + senderSessionId: identity.senderSessionId, + bossRunId: identity.bossRunId, + participantId: identity.participantId, + senderBindingEpoch: identity.senderBindingEpoch, + idempotencyKey: identity.idempotencyKey + }); +} +function bossControlFingerprint(targetSessionId, envelope) { + return canonicalJson({ + targetSessionId, + envelope: { + type: envelope.type, + version: envelope.version, + bossRunId: envelope.bossRunId, + participantId: envelope.participantId, + bindingEpoch: envelope.bindingEpoch, + ...envelope.causationId === void 0 ? {} : { causationId: envelope.causationId }, + ...envelope.replyTo === void 0 ? {} : { replyTo: envelope.replyTo }, + idempotencyKey: envelope.idempotencyKey, + payload: envelope.payload + } + }); +} +function bossControlAcceptedFrame(record, requestMessageId) { + return { + type: "boss_control_accepted", + messageId: stringValue(requestMessageId, "$requestMessageId"), + deliveryId: record.deliveryId + }; +} +function bossControlTerminalFrame(record, requestMessageId) { + const messageId = stringValue(requestMessageId, "$requestMessageId"); + if (record.state === "delivered") { + return { type: "boss_control_delivered", messageId, deliveryId: record.deliveryId }; + } + if (record.state === "rejected") { + return { + type: "boss_control_failed", + messageId, + deliveryId: record.deliveryId, + accepted: true, + code: record.failureCode, + reason: record.failureReason + }; + } + throw new Error("Accepted Boss control has no terminal result"); +} +function bossControlReplayFrames(record, requestMessageId) { + const accepted = bossControlAcceptedFrame(record, requestMessageId); + return record.state === "accepted" ? [accepted] : [accepted, bossControlTerminalFrame(record, requestMessageId)]; +} +function parseRecord(value, path) { + const record = recordValue(value, path); + exactKeys(record, [ + "senderSessionId", + "bossRunId", + "participantId", + "senderBindingEpoch", + "idempotencyKey", + "targetSessionId", + "targetBindingEpoch", + "controlKind", + "envelope", + "fingerprint", + "deliveryId", + "state", + "acceptedAt" + ], ["deliveredAt", "failureCode", "failureReason", "rejectedAt"], path); + const envelope = parseBossControlEnvelope2(record.envelope); + const senderSessionId = stringValue(record.senderSessionId, `${path}.senderSessionId`); + const bossRunId = stringValue(record.bossRunId, `${path}.bossRunId`); + const participantId = stringValue(record.participantId, `${path}.participantId`); + const senderBindingEpoch = participantBindingEpoch(record.senderBindingEpoch, `${path}.senderBindingEpoch`); + const idempotencyKey = stringValue(record.idempotencyKey, `${path}.idempotencyKey`); + const targetSessionId = stringValue(record.targetSessionId, `${path}.targetSessionId`); + const targetBindingEpoch = participantBindingEpoch(record.targetBindingEpoch, `${path}.targetBindingEpoch`); + const controlKind = record.controlKind; + if (!CONTROL_KINDS.includes(controlKind)) throw new Error(`${path}.controlKind is invalid`); + const fingerprint = stringValue(record.fingerprint, `${path}.fingerprint`); + const deliveryId = stringValue(record.deliveryId, `${path}.deliveryId`); + const acceptedAt = timestampValue(record.acceptedAt, `${path}.acceptedAt`); + if (envelope.bossRunId !== bossRunId || envelope.participantId !== participantId || envelope.bindingEpoch !== senderBindingEpoch || envelope.idempotencyKey !== idempotencyKey) { + throw new Error(`${path} identity does not match its envelope`); + } + if (controlKind !== bossControlKind(envelope.type)) throw new Error(`${path}.controlKind does not match its envelope type`); + if (fingerprint !== bossControlFingerprint(targetSessionId, envelope)) { + throw new Error(`${path}.fingerprint does not match its canonical target and envelope`); + } + const state = record.state; + if (state !== "accepted" && state !== "delivered" && state !== "rejected") throw new Error(`${path}.state is invalid`); + const deliveredAt = record.deliveredAt === void 0 ? void 0 : timestampValue(record.deliveredAt, `${path}.deliveredAt`); + const failureCode = record.failureCode; + const failureReason = record.failureReason === void 0 ? void 0 : stringValue(record.failureReason, `${path}.failureReason`); + const rejectedAt = record.rejectedAt === void 0 ? void 0 : timestampValue(record.rejectedAt, `${path}.rejectedAt`); + if (state === "accepted" && (deliveredAt !== void 0 || failureCode !== void 0 || failureReason !== void 0 || rejectedAt !== void 0)) { + throw new Error(`${path} accepted record contains terminal evidence`); + } + if (state === "delivered" && (deliveredAt === void 0 || failureCode !== void 0 || failureReason !== void 0 || rejectedAt !== void 0)) { + throw new Error(`${path} delivered record has invalid terminal evidence`); + } + if (state === "rejected" && (failureCode === void 0 || !TERMINAL_FAILURE_CODES.includes(failureCode) || failureReason === void 0 || rejectedAt === void 0 || deliveredAt !== void 0)) { + throw new Error(`${path} rejected record has invalid terminal evidence`); + } + if (deliveredAt !== void 0 && Date.parse(deliveredAt) < Date.parse(acceptedAt)) { + throw new Error(`${path}.deliveredAt precedes acceptance`); + } + if (rejectedAt !== void 0 && Date.parse(rejectedAt) < Date.parse(acceptedAt)) { + throw new Error(`${path}.rejectedAt precedes acceptance`); + } + return { + senderSessionId, + bossRunId, + participantId, + senderBindingEpoch, + idempotencyKey, + targetSessionId, + targetBindingEpoch, + controlKind, + envelope, + fingerprint, + deliveryId, + state, + acceptedAt, + ...deliveredAt === void 0 ? {} : { deliveredAt }, + ...failureCode === void 0 ? {} : { failureCode }, + ...failureReason === void 0 ? {} : { failureReason }, + ...rejectedAt === void 0 ? {} : { rejectedAt } + }; +} +function parseState2(value) { + const state = recordValue(value, "$bossControls"); + exactKeys(state, ["version", "records"], [], "$bossControls"); + if (state.version !== STATE_VERSION) throw new Error("Unsupported Boss control state version"); + const recordsValue = recordValue(state.records, "$bossControls.records"); + const records = {}; + for (const [scope, value2] of Object.entries(recordsValue)) { + const record = parseRecord(value2, `$bossControls.records[${JSON.stringify(scope)}]`); + if (scope !== bossControlIdempotencyScope(record)) throw new Error("Boss control scope key does not match its record"); + records[scope] = record; + } + return { version: STATE_VERSION, records }; +} +function clone(record) { + return structuredClone(record); +} +var DurableBossControlStore = class { + constructor(path, persist = writeDurableJson) { + this.path = path; + ensureIntercomRuntimeDir(dirname2(path)); + this.persist = persist; + this.state = this.load(); + } + path; + state; + persist; + poisoned; + get(identity) { + this.assertUsable(); + const record = this.state.records[bossControlIdempotencyScope(identity)]; + return record === void 0 ? void 0 : clone(record); + } + reserve(recordValue2) { + this.assertUsable(); + const record = parseRecord(recordValue2, "$record"); + if (record.state !== "accepted") throw new Error("A Boss control reservation must start accepted"); + const scope = bossControlIdempotencyScope(record); + const existing = this.state.records[scope]; + if (existing) { + if (existing.fingerprint !== record.fingerprint || existing.targetBindingEpoch !== record.targetBindingEpoch || existing.controlKind !== record.controlKind) { + throw new Error("Conflicting Boss control idempotency replay"); + } + return { created: false, record: clone(existing) }; + } + const next = structuredClone(this.state); + next.records[scope] = record; + this.commit(next); + return { created: true, record: clone(record) }; + } + markDelivered(identity, deliveryId, deliveredAt = (/* @__PURE__ */ new Date()).toISOString()) { + this.assertUsable(); + const scope = bossControlIdempotencyScope(identity); + const record = this.state.records[scope]; + if (!record || record.deliveryId !== deliveryId) throw new Error("Boss control delivery does not match its durable acceptance"); + if (record.state === "rejected") throw new Error("A rejected Boss control cannot become delivered"); + if (record.state === "delivered") return clone(record); + const updated = { + ...record, + state: "delivered", + deliveredAt: timestampValue(deliveredAt, "$deliveredAt") + }; + parseRecord(updated, "$record"); + const next = structuredClone(this.state); + next.records[scope] = updated; + this.commit(next); + return clone(updated); + } + markRejected(identity, deliveryId, failureCode, failureReason, rejectedAt = (/* @__PURE__ */ new Date()).toISOString()) { + this.assertUsable(); + const scope = bossControlIdempotencyScope(identity); + const record = this.state.records[scope]; + if (!record || record.deliveryId !== deliveryId) throw new Error("Boss control failure does not match its durable acceptance"); + if (record.state === "delivered") throw new Error("A delivered Boss control cannot become rejected"); + if (record.state === "rejected") { + if (record.failureCode !== failureCode || record.failureReason !== failureReason) { + throw new Error("Conflicting terminal Boss control failure"); + } + return clone(record); + } + const updated = { + ...record, + state: "rejected", + failureCode, + failureReason: stringValue(failureReason, "$failureReason"), + rejectedAt: timestampValue(rejectedAt, "$rejectedAt") + }; + parseRecord(updated, "$record"); + const next = structuredClone(this.state); + next.records[scope] = updated; + this.commit(next); + return clone(updated); + } + load() { + if (!existsSync2(this.path)) return { version: STATE_VERSION, records: {} }; + return parseState2(JSON.parse(readFileSync4(this.path, "utf8"))); + } + loadExactTarget() { + if (!existsSync2(this.path)) throw new Error("Durable Boss control target is missing"); + return parseState2(JSON.parse(readFileSync4(this.path, "utf8"))); + } + commit(next) { + const stagedCanonical = canonicalJson(parseState2(structuredClone(next))); + const priorCanonical = canonicalJson(this.state); + try { + this.persist(this.path, parseState2(JSON.parse(stagedCanonical))); + } catch (persistError) { + try { + const recovered = this.loadExactTarget(); + const recoveredCanonical = canonicalJson(recovered); + if (recoveredCanonical === stagedCanonical) { + this.state = parseState2(JSON.parse(stagedCanonical)); + } else if (recoveredCanonical === priorCanonical) { + this.state = parseState2(JSON.parse(priorCanonical)); + } else { + throw new Error("Durable Boss control state does not match the prior or staged commit"); + } + } catch (reconcileError) { + this.poisoned = new Error("Durable Boss control store is unavailable after commit reconciliation failed", { + cause: reconcileError + }); + } + throw persistError; + } + this.state = parseState2(JSON.parse(stagedCanonical)); + } + assertUsable() { + if (this.poisoned) throw this.poisoned; + } +}; + +// broker/negotiation.ts +import { + INTERCOM_BASE_PROTOCOL_VERSION, + evaluateBrokerCompatibility, + parseBrokerCapabilityAdvertisement +} from "@dataforxyz/agent-intercom-core/boss"; +if (INTERCOM_PROTOCOL_VERSION !== INTERCOM_BASE_PROTOCOL_VERSION) { + throw new Error( + `OpenCode protocol v${INTERCOM_PROTOCOL_VERSION} diverges from Core base protocol v${INTERCOM_BASE_PROTOCOL_VERSION}` + ); +} +var DORMANT_BROKER_CAPABILITIES = Object.freeze(parseBrokerCapabilityAdvertisement({ + baseProtocolVersion: INTERCOM_BASE_PROTOCOL_VERSION, + features: [] +})); +var ORDINARY_BASE3_COMPATIBILITY = Object.freeze({ + clientKind: "ordinary", + supportedBaseProtocolVersions: [INTERCOM_BASE_PROTOCOL_VERSION] +}); +function negotiateBrokerCompatibility(value) { + return evaluateBrokerCompatibility(value, DORMANT_BROKER_CAPABILITIES); +} + // broker/audit.ts import { closeSync as closeSync3, fsyncSync as fsyncSync2, openSync as openSync3, writeSync } from "fs"; var BROKER_AUDIT_VERSION = 1; @@ -888,6 +1161,7 @@ var ASK_STATE_PATH = getBrokerAskStateFilePath(INTERCOM_DIR); var ACCESS_STATE_PATH = getBrokerAccessStateFilePath(INTERCOM_DIR); var ADMIN_CREDENTIAL_PATH = getBrokerAdminCredentialFilePath(INTERCOM_DIR); var AUDIT_PATH = getBrokerAuditFilePath(INTERCOM_DIR); +var BOSS_CONTROL_STATE_PATH = getBrokerBossControlStateFilePath(INTERCOM_DIR); var BROKER_STATE_ID = randomUUID3(); var MAX_SESSIONS = 128; var MAX_UNREGISTERED_CONNECTIONS = 32; @@ -958,6 +1232,8 @@ function isSessionRegistration(value) { return false; } const session = value; + const allowedKeys = /* @__PURE__ */ new Set(["name", "cwd", "model", "pid", "startedAt", "lastActivity", "status", "runtimeInstanceId"]); + if (Object.keys(session).some((key) => !allowedKeys.has(key))) return false; if (typeof session.cwd !== "string" || session.cwd.length === 0 || session.cwd.length > MAX_SESSION_CWD_LENGTH || typeof session.model !== "string" || session.model.length === 0 || session.model.length > MAX_SESSION_MODEL_LENGTH || typeof session.pid !== "number" || !Number.isFinite(session.pid) || typeof session.startedAt !== "number" || !Number.isFinite(session.startedAt) || typeof session.lastActivity !== "number" || !Number.isFinite(session.lastActivity)) { return false; } @@ -981,6 +1257,8 @@ var IntercomBroker = class { pendingDeliveries = /* @__PURE__ */ new Map(); pendingDeliveryKeys = /* @__PURE__ */ new Map(); recentDeliveries = /* @__PURE__ */ new Map(); + pendingBossControls = /* @__PURE__ */ new Map(); + pendingBossControlKeys = /* @__PURE__ */ new Map(); connections = /* @__PURE__ */ new Set(); unregisteredConnections = /* @__PURE__ */ new Set(); server; @@ -990,6 +1268,7 @@ var IntercomBroker = class { askTimeoutMs = getAskTimeoutMs(); accessRegistry; audit; + bossControlStoreInstance; constructor() { ensureIntercomRuntimeDir(INTERCOM_DIR); acquireBrokerOwnership(OWNER_PATH); @@ -1138,6 +1417,7 @@ var IntercomBroker = class { this.broadcastVisible({ type: "session_left", sessionId }, existing.info, sessionId); this.sessions.delete(sessionId); this.clearPendingDeliveriesForSession(sessionId, socket); + this.clearPendingBossControlsForSession(sessionId, socket); this.deferAskEdgesForSession(sessionId); this.scheduleShutdownCheck(); } @@ -1250,6 +1530,18 @@ var IntercomBroker = class { socket.end(); break; } + if (clientMessage.compatibility !== void 0) { + const compatibility = negotiateBrokerCompatibility(clientMessage.compatibility); + if (!compatibility.compatible || compatibility.mode !== "ordinary") { + this.sendError( + socket, + "PROTOCOL_MISMATCH", + `Intercom compatibility negotiation failed: ${"code" in compatibility ? compatibility.code : "BOSS_MODE_DORMANT"}` + ); + socket.end(); + break; + } + } if (currentId) { throw new Error("Received duplicate register message"); } @@ -1327,6 +1619,7 @@ var IntercomBroker = class { } if (previous) { this.clearPendingDeliveriesForSession(id, previous.socket); + this.clearPendingBossControlsForSession(id, previous.socket); this.deferAskEdgesForSession(id); previous.socket.end(); } @@ -1438,6 +1731,7 @@ var IntercomBroker = class { this.broadcastVisible({ type: "session_left", sessionId: currentId }, existing.info, currentId); this.sessions.delete(currentId); this.clearPendingDeliveriesForSession(currentId, socket); + this.clearPendingBossControlsForSession(currentId, socket); if (clientMessage.preserveAsks) { this.deferAskEdgesForSession(currentId); } else { @@ -1620,6 +1914,150 @@ var IntercomBroker = class { this.sendDeliveryFailure(socket, message.id, false, "SESSION_NOT_FOUND", "Session not found"); break; } + case "boss_control_send": { + if (!currentId) throw new Error("Received boss_control_send before register"); + const rawEnvelope = clientMessage.envelope; + const rawMessageId = typeof rawEnvelope === "object" && rawEnvelope !== null && "messageId" in rawEnvelope && typeof rawEnvelope.messageId === "string" ? rawEnvelope.messageId : "unknown"; + if (typeof clientMessage.to !== "string" || clientMessage.to.length === 0 || clientMessage.to.length > MAX_TARGET_LENGTH) { + this.sendBossControlFailure(socket, rawMessageId, false, "INVALID_BOSS_CONTROL", "Invalid Boss control target"); + break; + } + const sender = this.sessions.get(currentId); + let envelope; + try { + if (!sender || sender.socket !== socket) throw new Error("Sender session not found"); + envelope = parseBoundBossControl(rawEnvelope, sender.info); + } catch (error) { + this.sendBossControlFailure( + socket, + rawMessageId, + false, + "INVALID_BOSS_CONTROL", + error instanceof Error ? error.message : "Invalid Boss control envelope" + ); + break; + } + const controlKind = bossControlKind(envelope.type); + const senderBoss = validatedBossMetadata(sender.info); + const identity = { + senderSessionId: currentId, + bossRunId: envelope.bossRunId, + participantId: envelope.participantId, + senderBindingEpoch: envelope.bindingEpoch, + idempotencyKey: envelope.idempotencyKey + }; + const key = bossControlIdempotencyScope(identity); + const fingerprint = bossControlFingerprint(clientMessage.to, envelope); + const durable = this.bossControlStore().get(identity); + if (durable) { + if (durable.fingerprint !== fingerprint) { + this.sendBossControlFailure(socket, envelope.messageId, false, "CONFLICTING_MESSAGE_ID", "Boss control idempotency key was reused with different content"); + break; + } + this.writeBossControlReplay(socket, durable, envelope.messageId); + if (durable.state !== "accepted") break; + const pendingId2 = this.pendingBossControlKeys.get(key); + if (pendingId2) { + const pending = this.pendingBossControls.get(pendingId2); + if (pending) { + pending.requesters.set(envelope.messageId, socket); + break; + } + } + const target2 = this.sessions.get(durable.targetSessionId); + let targetBoss2; + try { + targetBoss2 = target2 === void 0 ? void 0 : validatedBossMetadata(target2.info); + } catch { + targetBoss2 = void 0; + } + if (!target2 || !targetBoss2 || targetBoss2.principal.bindingEpoch !== durable.targetBindingEpoch || !this.isBossControlAuthorized( + currentId, + durable.targetSessionId, + durable.controlKind, + durable.senderBindingEpoch, + durable.targetBindingEpoch + )) { + this.failDurableBossControl( + durable, + "RECIPIENT_DISCONNECTED", + "The exact accepted Boss control target is not available at its bound epoch", + socket, + envelope.messageId + ); + break; + } + this.activateBossControl(durable, socket, target2.socket, envelope.messageId); + break; + } + const pendingId = this.pendingBossControlKeys.get(key); + if (pendingId) { + const pending = this.pendingBossControls.get(pendingId); + if (!pending || pending.fingerprint !== fingerprint) { + this.sendBossControlFailure(socket, envelope.messageId, false, "CONFLICTING_MESSAGE_ID", "Boss control idempotency key is already pending with different content"); + break; + } + if (this.isBossControlAuthorized(pending.from, pending.to, pending.controlKind, pending.fromBindingEpoch, pending.toBindingEpoch)) { + pending.requesters.set(envelope.messageId, socket); + writeMessage(socket, { type: "boss_control_accepted", messageId: envelope.messageId, deliveryId: pending.id }); + break; + } + this.failPendingBossControl(pending.id, "BOSS_CONTROL_DENIED", "Boss control authorization changed while pending"); + } + if (this.pendingDeliveries.size + this.pendingBossControls.size >= MAX_PENDING_DELIVERIES || this.countPendingDeliveriesFrom(currentId) + this.countPendingBossControlsFrom(currentId) >= MAX_PENDING_DELIVERIES_PER_SESSION) { + this.sendBossControlFailure(socket, envelope.messageId, false, "TOO_MANY_PENDING_DELIVERIES", "Too many deliveries are waiting for acknowledgement"); + break; + } + const target = this.sessions.get(clientMessage.to); + if (!target) { + this.sendBossControlFailure( + socket, + envelope.messageId, + false, + "SESSION_NOT_FOUND", + "Exact Boss control target session not found" + ); + break; + } + let targetBoss; + try { + targetBoss = validatedBossMetadata(target.info); + } catch { + targetBoss = void 0; + } + if (!targetBoss || !this.isBossControlAuthorized( + currentId, + target.info.id, + controlKind, + senderBoss.principal.bindingEpoch, + targetBoss.principal.bindingEpoch + )) { + this.sendBossControlFailure(socket, envelope.messageId, false, "BOSS_CONTROL_DENIED", "Boss control policy denied the exact target"); + break; + } + const deliveryId = randomUUID3(); + const reserved = this.bossControlStore().reserve({ + ...identity, + targetSessionId: target.info.id, + targetBindingEpoch: targetBoss.principal.bindingEpoch, + controlKind, + envelope, + fingerprint, + deliveryId, + state: "accepted", + acceptedAt: (/* @__PURE__ */ new Date()).toISOString() + }).record; + writeMessage(socket, bossControlAcceptedFrame(reserved, envelope.messageId)); + this.activateBossControl(reserved, socket, target.socket, envelope.messageId); + break; + } + case "boss_control_received": { + if (!currentId) throw new Error("Received boss_control_received before register"); + assertExactKeys3(clientMessage, ["type", "deliveryId"], [], "$.boss_control_received"); + if (typeof clientMessage.deliveryId !== "string") throw new Error("Invalid boss_control_received message"); + this.acknowledgePendingBossControl(clientMessage.deliveryId, currentId, socket); + break; + } case "message_received": { if (!currentId) { throw new Error("Received message_received before register"); @@ -2004,6 +2442,7 @@ var IntercomBroker = class { } } this.clearPendingDeliveriesForSession(principal.id, live.socket); + this.clearPendingBossControlsForSession(principal.id, live.socket); this.clearAskEdgesForSession(principal.id, "authorization_revoked"); this.sessions.delete(principal.id); for (const [key, recent] of this.recentDeliveries) { @@ -2042,6 +2481,19 @@ var IntercomBroker = class { targetId ).allowed; } + isBossControlAuthorized(actorId, targetId, controlKind, actorBindingEpoch, targetBindingEpoch) { + if (!this.isCurrentPrincipal(actorId) || !this.isCurrentPrincipal(targetId)) return false; + return authorizeSessionAction( + Array.from(this.sessions.values(), (session) => session.info), + actorId, + "control", + targetId, + // This adapter has no authenticated Orc/Controller causation ledger in + // the current slice. Binding epochs prove identity freshness, not that a + // specific Boss operation is correlated, so control must remain denied. + { actorBindingEpoch, targetBindingEpoch, controlKind, correlated: false } + ).allowed; + } broadcastVisible(message, subject, exclude) { for (const [id, session] of this.sessions) { if (id !== exclude && this.isAuthorized(id, "discover", subject.id)) { @@ -2152,11 +2604,11 @@ var IntercomBroker = class { return timeout; } loadAskEdges() { - if (!existsSync2(ASK_STATE_PATH)) { + if (!existsSync3(ASK_STATE_PATH)) { return; } try { - const parsed = JSON.parse(readFileSync4(ASK_STATE_PATH, "utf-8")); + const parsed = JSON.parse(readFileSync5(ASK_STATE_PATH, "utf-8")); if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) { throw new Error("expected an object"); } @@ -2232,6 +2684,182 @@ var IntercomBroker = class { } return count; } + countPendingBossControlsFrom(sessionId) { + let count = 0; + for (const control of this.pendingBossControls.values()) { + if (control.from === sessionId) count += 1; + } + return count; + } + sendBossControlFailure(socket, messageId, accepted, code, reason, deliveryId) { + if (accepted) { + if (!deliveryId) throw new Error("Accepted Boss control failure requires deliveryId"); + writeMessage(socket, { type: "boss_control_failed", messageId, deliveryId, accepted: true, code, reason }); + return; + } + if (deliveryId !== void 0) throw new Error("Pre-acceptance Boss control failure cannot contain deliveryId"); + writeMessage(socket, { type: "boss_control_failed", messageId, accepted: false, code, reason }); + } + bossControlStore() { + this.bossControlStoreInstance ??= new DurableBossControlStore(BOSS_CONTROL_STATE_PATH); + return this.bossControlStoreInstance; + } + writeBossControlReplay(socket, record, requestMessageId) { + for (const frame of bossControlReplayFrames(record, requestMessageId)) writeMessage(socket, frame); + } + activateBossControl(record, senderSocket, recipientSocket, requestMessageId) { + const key = bossControlIdempotencyScope(record); + const timeout = setTimeout(() => { + try { + this.failPendingBossControl(record.deliveryId, "DELIVERY_TIMEOUT", "Recipient did not acknowledge the Boss control in time"); + } catch (error) { + console.error("Boss control timeout callback failed:", error); + } + }, DELIVERY_ACK_TIMEOUT_MS); + timeout.unref?.(); + this.pendingBossControls.set(record.deliveryId, { + id: record.deliveryId, + key, + fingerprint: record.fingerprint, + envelope: record.envelope, + controlKind: record.controlKind, + from: record.senderSessionId, + to: record.targetSessionId, + requesters: /* @__PURE__ */ new Map([[requestMessageId, senderSocket]]), + recipientSocket, + fromBindingEpoch: record.senderBindingEpoch, + toBindingEpoch: record.targetBindingEpoch, + timeout + }); + this.pendingBossControlKeys.set(key, record.deliveryId); + const sender = this.sessions.get(record.senderSessionId); + if (!sender || sender.socket !== senderSocket) { + this.failPendingBossControl(record.deliveryId, "SENDER_DISCONNECTED", "Sender disconnected before the Boss control was dispatched"); + return; + } + writeMessage(recipientSocket, { + type: "boss_control", + deliveryId: record.deliveryId, + from: sender.info, + envelope: record.envelope + }); + } + failDurableBossControl(record, code, reason, senderSocket, requestMessageId) { + try { + const reconciled = this.mutateBossControlTerminal( + record, + record.deliveryId, + "rejection", + () => this.bossControlStore().markRejected(record, record.deliveryId, code, reason) + ); + if (!reconciled || reconciled.state === "accepted") return; + const socket = senderSocket ?? this.sessions.get(record.senderSessionId)?.socket; + if (socket) writeMessage(socket, bossControlTerminalFrame(reconciled, requestMessageId ?? reconciled.envelope.messageId)); + } catch (error) { + console.error("Boss control durable rejection callback failed:", error); + } + } + acknowledgePendingBossControl(deliveryId, sessionId, socket) { + try { + const pending = this.pendingBossControls.get(deliveryId); + if (!pending || pending.to !== sessionId || pending.recipientSocket !== socket) return; + if (!this.isBossControlAuthorized( + pending.from, + pending.to, + pending.controlKind, + pending.fromBindingEpoch, + pending.toBindingEpoch + )) { + this.failPendingBossControl(deliveryId, "BOSS_CONTROL_DENIED", "Boss control authorization changed before acknowledgement"); + return; + } + const identity = this.pendingBossControlIdentity(pending); + const reconciled = this.mutateBossControlTerminal( + identity, + deliveryId, + "delivery", + () => this.bossControlStore().markDelivered(identity, deliveryId) + ); + this.completePendingBossControl(pending, reconciled); + } catch (error) { + console.error("Boss control acknowledgement callback failed:", error); + } + } + failPendingBossControl(deliveryId, code, reason) { + try { + const pending = this.pendingBossControls.get(deliveryId); + if (!pending) return; + const identity = this.pendingBossControlIdentity(pending); + const reconciled = this.mutateBossControlTerminal( + identity, + deliveryId, + "rejection", + () => this.bossControlStore().markRejected(identity, deliveryId, code, reason) + ); + this.completePendingBossControl(pending, reconciled); + } catch (error) { + console.error("Boss control rejection callback failed:", error); + } + } + pendingBossControlIdentity(pending) { + return { + senderSessionId: pending.from, + bossRunId: pending.envelope.bossRunId, + participantId: pending.envelope.participantId, + senderBindingEpoch: pending.envelope.bindingEpoch, + idempotencyKey: pending.envelope.idempotencyKey + }; + } + mutateBossControlTerminal(identity, deliveryId, operation, mutate) { + try { + return mutate(); + } catch (mutationError) { + try { + const reconciled = this.bossControlStore().get(identity); + if (!reconciled || reconciled.deliveryId !== deliveryId) { + console.error(`Boss control ${operation} mutation failed without an exact durable record:`, mutationError); + return void 0; + } + console.error( + `Boss control ${operation} mutation threw after reconciling durable state ${reconciled.state}:`, + mutationError + ); + return reconciled; + } catch (reconcileError) { + console.error(`Boss control ${operation} mutation and exact reconciliation failed:`, mutationError, reconcileError); + return void 0; + } + } + } + completePendingBossControl(pending, reconciled) { + if (this.pendingBossControls.get(pending.id) !== pending) return; + clearTimeout(pending.timeout); + this.pendingBossControls.delete(pending.id); + if (this.pendingBossControlKeys.get(pending.key) === pending.id) this.pendingBossControlKeys.delete(pending.key); + if (!reconciled || reconciled.state === "accepted") return; + const sender = this.sessions.get(pending.from); + for (const [messageId, requesterSocket] of pending.requesters) { + if (sender?.socket !== requesterSocket) continue; + try { + writeMessage(requesterSocket, bossControlTerminalFrame(reconciled, messageId)); + } catch (error) { + console.error("Failed to publish reconciled Boss control terminal frame:", error); + } + } + } + clearPendingBossControlsForSession(sessionId, socket) { + try { + for (const control of Array.from(this.pendingBossControls.values())) { + if (control.to === sessionId && control.recipientSocket === socket) { + this.failPendingBossControl(control.id, "RECIPIENT_DISCONNECTED", "Recipient disconnected before acknowledging the Boss control"); + } else if (control.from === sessionId && Array.from(control.requesters.values()).includes(socket)) { + this.failPendingBossControl(control.id, "SENDER_DISCONNECTED", "Sender disconnected before the Boss control was acknowledged"); + } + } + } catch (error) { + console.error("Boss control socket-close callback failed:", error); + } + } acknowledgePendingDelivery(deliveryId, sessionId, socket) { const pending = this.pendingDeliveries.get(deliveryId); if (!pending || pending.to !== sessionId || pending.recipientSocket !== socket) { @@ -2342,6 +2970,11 @@ var IntercomBroker = class { } this.pendingDeliveries.clear(); this.pendingDeliveryKeys.clear(); + for (const control of this.pendingBossControls.values()) { + clearTimeout(control.timeout); + } + this.pendingBossControls.clear(); + this.pendingBossControlKeys.clear(); for (const edge of this.askEdges.values()) { clearTimeout(edge.timeout); } @@ -2371,4 +3004,9 @@ var IntercomBroker = class { process.exit(0); } }; -new IntercomBroker().start(); +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + new IntercomBroker().start(); +} +export { + IntercomBroker +}; diff --git a/dist/plugin.mjs b/dist/plugin.mjs index 7293b29..14c972d 100644 --- a/dist/plugin.mjs +++ b/dist/plugin.mjs @@ -12,182 +12,105 @@ import { cwd as processCwd } from "process"; import { EventEmitter } from "events"; import net from "net"; import { randomUUID as randomUUID2 } from "crypto"; +import { types as nodeUtilTypes } from "node:util"; +import { + POLICY_SEMANTICS_HASH, + POLICY_SEMANTICS_VERSION +} from "@dataforxyz/agent-intercom-core"; +import { + parseBossControlEnvelope as parseBossControlEnvelope2 +} from "@dataforxyz/agent-intercom-core/boss"; +import { assertExactKeys as assertExactKeys2 } from "@dataforxyz/agent-intercom-core/canonical"; -// ../../src/github.com/dataforxyz/agent-intercom-opencode/node_modules/@dataforxyz/agent-intercom-core/dist/policy.js -var POLICY_SEMANTICS_VERSION = 2; - -// ../../src/github.com/dataforxyz/agent-intercom-opencode/node_modules/@dataforxyz/agent-intercom-core/dist/policy-vectors.js -var localRoot = { - id: "local-root", - kind: "local", - state: "active", - generation: 1, - policy: "local-public", - rootSessionId: "local-root" -}; -var localPeer = { - id: "local-peer", - kind: "local", - state: "active", - generation: 1, - policy: "local-public", - rootSessionId: "local-peer" -}; -var remoteManager = { - id: "remote-manager", - kind: "remote", - state: "active", - generation: 1, - policy: "remote-tree", - parentSessionId: "local-root", - rootSessionId: "local-root" +// broker/boss.ts +import { + BOSS_CONTROL_TYPES, + parseBossControlEnvelope, + parseBossParticipantBinding, + parseBossPolicyPrincipal, + parseFeatureRegistration +} from "@dataforxyz/agent-intercom-core/boss"; +import { + ContractValidationError, + assertExactKeys, + assertRecord +} from "@dataforxyz/agent-intercom-core/canonical"; +var CONTROL_KIND_BY_TYPE = { + "boss.assignment.created": "assignment_request", + "boss.assignment.accepted": "assignment_response", + "boss.assignment.checkpoint": "assignment_response", + "boss.assignment.submitted": "assignment_response", + "boss.assignment.rejected": "assignment_response", + "boss.assignment.cancelled": "lifecycle", + "boss.staffing.requested": "staffing", + "boss.staffing.resolved": "staffing", + "boss.review.requested": "review_request", + "boss.review.submitted": "review_result", + "boss.council.requested": "review_request", + "boss.council.submitted": "review_result", + "boss.proof.submitted": "proof", + "boss.worker.health": "health", + "boss.worker.blocked": "health", + "boss.worker.failed": "health", + "boss.worker.notice": "lifecycle", + "boss.worker.notice_delivery_failed": "lifecycle", + "boss.decision.required": "decision" }; -var remoteChild = { - id: "remote-child", - kind: "remote", - state: "active", - generation: 1, - policy: "remote-tree", - parentSessionId: "remote-manager", - rootSessionId: "local-root" -}; -var remoteSibling = { - id: "remote-sibling", - kind: "remote", - state: "active", - generation: 1, - policy: "remote-tree", - parentSessionId: "remote-manager", - rootSessionId: "local-root" -}; -var POLICY_VECTORS = [ - { - name: "local sessions remain public", - principals: [localRoot, localPeer], - actorId: "local-root", - action: "send", - targetId: "local-peer", - expectedAllowed: true, - expectedReasonOrCode: "local-public" - }, - { - name: "remote manager can reach direct local parent", - principals: [localRoot, remoteManager], - actorId: "remote-manager", - action: "send", - targetId: "local-root", - expectedAllowed: true, - expectedReasonOrCode: "direct-parent" - }, - { - name: "local parent can reach direct remote child", - principals: [localRoot, remoteManager], - actorId: "local-root", - action: "ask", - targetId: "remote-manager", - expectedAllowed: true, - expectedReasonOrCode: "direct-parent" - }, - { - name: "remote child can reach its local root through the ancestor chain", - principals: [localRoot, remoteManager, remoteChild], - actorId: "remote-child", - action: "send", - targetId: "local-root", - expectedAllowed: true, - expectedReasonOrCode: "ancestor-chain" - }, - { - name: "remote siblings cannot communicate in phase one", - principals: [localRoot, remoteManager, remoteChild, remoteSibling], - actorId: "remote-child", - action: "discover", - targetId: "remote-sibling", - expectedAllowed: false, - expectedReasonOrCode: "POLICY_DENIED" - }, - { - name: "unrelated local session cannot discover remote principal", - principals: [localRoot, localPeer, remoteManager], - actorId: "local-peer", - action: "discover", - targetId: "remote-manager", - expectedAllowed: false, - expectedReasonOrCode: "POLICY_DENIED" - }, - { - name: "remote principal cannot reach unrelated local session", - principals: [localRoot, localPeer, remoteManager], - actorId: "remote-manager", - action: "send", - targetId: "local-peer", - expectedAllowed: false, - expectedReasonOrCode: "POLICY_DENIED" - }, - { - name: "remote manager may inspect its descendant subtree", - principals: [localRoot, remoteManager, remoteChild], - actorId: "remote-manager", - action: "inspect_tree", - targetId: "remote-child", - expectedAllowed: true, - expectedReasonOrCode: "ancestor-control" - }, - { - name: "remote child cannot revoke its ancestor", - principals: [localRoot, remoteManager, remoteChild], - actorId: "remote-child", - action: "revoke", - targetId: "remote-manager", - expectedAllowed: false, - expectedReasonOrCode: "POLICY_DENIED" - }, - { - name: "remote principal may request attenuated delegation under itself", - principals: [localRoot, remoteManager], - actorId: "remote-manager", - action: "delegate_child", - targetId: "remote-manager", - expectedAllowed: true, - expectedReasonOrCode: "self" - }, - { - name: "revoked principal cannot communicate", - principals: [localRoot, { ...remoteManager, state: "revoked" }], - actorId: "remote-manager", - action: "send", - targetId: "local-root", - expectedAllowed: false, - expectedReasonOrCode: "REVOKED_PRINCIPAL" - }, - { - name: "stale actor generation cannot send", - principals: [localRoot, { ...remoteManager, generation: 2 }], - actorId: "remote-manager", - action: "send", - targetId: "local-root", - context: { actorGeneration: 1 }, - expectedAllowed: false, - expectedReasonOrCode: "STALE_GENERATION" +if (Object.keys(CONTROL_KIND_BY_TYPE).length !== BOSS_CONTROL_TYPES.length) { + throw new Error("Boss control type mapping is incomplete"); +} +function parseBossSessionMetadata(value, sessionId) { + assertRecord(value, "$.boss"); + assertExactKeys(value, ["registration", "principal"], ["binding"], "$.boss"); + const metadata = value; + const registration = parseFeatureRegistration(metadata.registration); + const principal = parseBossPolicyPrincipal(metadata.principal); + if (registration.principalClass !== "boss-bound" || principal.principalClass !== "boss-private") { + throw new ContractValidationError("$.boss", "must contain Boss-bound registration and private principal metadata"); } -]; -var POLICY_SEMANTICS_HASH = "f3b00e503631bc91123aedfbcf1df72cc9913e1893c09728b2c598f3dcdfdfe0"; + if (registration.principalId !== sessionId || principal.principalId !== sessionId || registration.bossRunId !== principal.bossRunId || registration.participantId !== principal.participantId || registration.bindingEpoch !== principal.bindingEpoch) { + throw new ContractValidationError("$.boss", "registration and principal identity bindings must exactly match the session"); + } + const binding = metadata.binding === void 0 ? void 0 : parseBossParticipantBinding(metadata.binding); + if (principal.role === "controller") { + if (binding !== void 0) throw new ContractValidationError("$.boss.binding", "is forbidden for Controller principals"); + } else { + if (binding === void 0) throw new ContractValidationError("$.boss.binding", "is required for Boss participants"); + if (binding.sessionId !== sessionId || binding.bossRunId !== principal.bossRunId || binding.participantId !== principal.participantId || binding.role !== principal.role || binding.bindingEpoch !== principal.bindingEpoch || binding.state !== principal.state || binding.assignedManagerParticipantId !== principal.assignedManagerParticipantId) { + throw new ContractValidationError("$.boss.binding", "must exactly match the authenticated session principal"); + } + } + return { registration, principal, ...binding === void 0 ? {} : { binding } }; +} +function validatedBossMetadata(session) { + if (session.boss === void 0) return void 0; + return parseBossSessionMetadata(session.boss, session.id); +} +function parseBoundBossControl(value, sender) { + const envelope = parseBossControlEnvelope(value); + const boss = validatedBossMetadata(sender); + if (!boss) throw new ContractValidationError("$.envelope", "sender is not an authenticated Boss participant"); + if (envelope.bossRunId !== boss.principal.bossRunId || envelope.participantId !== boss.principal.participantId || envelope.bindingEpoch !== boss.principal.bindingEpoch) { + throw new ContractValidationError("$.envelope", "run, participant, and binding epoch must match the sender"); + } + return envelope; +} // broker/framing.ts var MAX_FRAME_BYTES = 1024 * 1024; function writeMessage(socket, msg) { const json = JSON.stringify(msg); - const payload = Buffer.from(json, "utf-8"); + const payload2 = Buffer.from(json, "utf-8"); const header = Buffer.alloc(4); - header.writeUInt32BE(payload.length, 0); - socket.write(Buffer.concat([header, payload])); + header.writeUInt32BE(payload2.length, 0); + socket.write(Buffer.concat([header, payload2])); } function createMessageReader(onMessage, onError, maxFrameBytes = MAX_FRAME_BYTES) { let buffer = Buffer.alloc(0); - function reportMessage(payload) { + function reportMessage(payload2) { let msg; try { - msg = JSON.parse(payload.toString("utf-8")); + msg = JSON.parse(payload2.toString("utf-8")); } catch (error) { const message = error instanceof Error ? error.message : String(error); onError(new Error(`Failed to parse intercom message: ${message}`, { cause: error })); @@ -228,9 +151,9 @@ function createMessageReader(onMessage, onError, maxFrameBytes = MAX_FRAME_BYTES if (buffer.length < 4 + length) { return; } - const payload = buffer.subarray(4, 4 + length); + const payload2 = buffer.subarray(4, 4 + length); buffer = Buffer.alloc(0); - if (!reportMessage(payload)) { + if (!reportMessage(payload2)) { return; } } @@ -316,23 +239,44 @@ function restrictIntercomRuntimeFile(filePath, platform = process.platform) { import { randomUUID } from "crypto"; import { closeSync, fsyncSync, openSync, renameSync, writeFileSync } from "fs"; import { dirname } from "path"; -function writeDurableJson(filePath, value) { +var DURABLE_JSON_FILE_OPERATIONS = Object.freeze({ + writeFile(filePath, contents, options) { + writeFileSync(filePath, contents, options); + }, + open(filePath, flags) { + return openSync(filePath, flags); + }, + fsync(fileDescriptor) { + fsyncSync(fileDescriptor); + }, + close(fileDescriptor) { + closeSync(fileDescriptor); + }, + rename(from, to) { + renameSync(from, to); + }, + restrict(filePath) { + restrictIntercomRuntimeFile(filePath); + }, + platform: process.platform +}); +function writeDurableJson(filePath, value, operations = DURABLE_JSON_FILE_OPERATIONS) { const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`; - writeFileSync(temporaryPath, JSON.stringify(value), { encoding: "utf-8", mode: INTERCOM_RUNTIME_FILE_MODE }); - const fileDescriptor = openSync(temporaryPath, "r"); + operations.writeFile(temporaryPath, JSON.stringify(value), { encoding: "utf-8", mode: INTERCOM_RUNTIME_FILE_MODE }); + const fileDescriptor = operations.open(temporaryPath, "r"); try { - fsyncSync(fileDescriptor); + operations.fsync(fileDescriptor); } finally { - closeSync(fileDescriptor); + operations.close(fileDescriptor); } - renameSync(temporaryPath, filePath); - restrictIntercomRuntimeFile(filePath); - if (process.platform !== "win32") { - const directoryDescriptor = openSync(dirname(filePath), "r"); + operations.rename(temporaryPath, filePath); + operations.restrict(filePath); + if (operations.platform !== "win32") { + const directoryDescriptor = operations.open(dirname(filePath), "r"); try { - fsyncSync(directoryDescriptor); + operations.fsync(directoryDescriptor); } finally { - closeSync(directoryDescriptor); + operations.close(directoryDescriptor); } } } @@ -508,6 +452,28 @@ function isMessage(value) { } return content.attachments === void 0 || Array.isArray(content.attachments) && content.attachments.every(isAttachment); } +var PRE_ACCEPT_BOSS_CONTROL_FAILURE_CODES = [ + "INVALID_BOSS_CONTROL", + "SESSION_NOT_FOUND", + "CONFLICTING_MESSAGE_ID", + "TOO_MANY_PENDING_DELIVERIES", + "BOSS_CONTROL_DENIED" +]; +var POST_ACCEPT_BOSS_CONTROL_FAILURE_CODES = [ + "BOSS_CONTROL_DENIED", + "RECIPIENT_DISCONNECTED", + "SENDER_DISCONNECTED", + "DELIVERY_TIMEOUT" +]; +function isBossControlFailureCode(value, accepted) { + return typeof value === "string" && (accepted ? POST_ACCEPT_BOSS_CONTROL_FAILURE_CODES : PRE_ACCEPT_BOSS_CONTROL_FAILURE_CODES).includes(value); +} +function exactBossControlFrame(frame, required, path) { + assertExactKeys2(frame, required, [], path); +} +function bossControlFrameString(value, path) { + if (typeof value !== "string" || value.length === 0) throw new Error(`${path} must be a non-empty string`); +} function isSessionInfo(value) { if (typeof value !== "object" || value === null) { return false; @@ -535,7 +501,104 @@ function isSessionInfo(value) { for (const field of ["depth", "maxDepth", "maxChildren"]) { if (session[field] !== void 0 && (typeof session[field] !== "number" || !Number.isSafeInteger(session[field]))) return false; } - return true; + return session.boss === void 0; +} +var BOSS_SESSION_REQUIRED_FIELDS = [ + "id", + "cwd", + "model", + "pid", + "startedAt", + "lastActivity", + "boss" +]; +var BOSS_SESSION_OPTIONAL_FIELDS = [ + "name", + "status", + "peerUid", + "trustedLocal", + "origin", + "remoteHostId", + "parentSessionId", + "rootSessionId", + "generation", + "canDelegate", + "depth", + "maxDepth", + "maxChildren" +]; +function snapshotBossData(value, path, seen = /* @__PURE__ */ new WeakSet(), depth = 0) { + if (value === null || typeof value === "string" || typeof value === "boolean") return value; + if (typeof value === "number") { + if (!Number.isFinite(value) || Object.is(value, -0)) throw new Error(`${path} must be a JSON number`); + return value; + } + if (typeof value !== "object" || nodeUtilTypes.isProxy(value)) { + throw new Error(`${path} must be unproxied broker-owned data`); + } + if (depth >= 32 || seen.has(value)) throw new Error(`${path} must be an acyclic bounded data tree`); + seen.add(value); + if (Array.isArray(value)) { + if (Object.getPrototypeOf(value) !== Array.prototype) throw new Error(`${path} must be a plain array`); + const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length"); + if (lengthDescriptor === void 0 || !Object.hasOwn(lengthDescriptor, "value") || !Number.isSafeInteger(lengthDescriptor.value) || lengthDescriptor.value < 0) { + throw new Error(`${path} must be a dense array`); + } + const entries = /* @__PURE__ */ new Map(); + for (const key of Reflect.ownKeys(value)) { + if (key === "length") continue; + if (typeof key !== "string") throw new Error(`${path} must not contain symbol properties`); + const index = Number(key); + if (!Number.isInteger(index) || index < 0 || index >= lengthDescriptor.value || String(index) !== key) { + throw new Error(`${path}.${key} is not a supported array index`); + } + const descriptor = Object.getOwnPropertyDescriptor(value, key); + if (descriptor === void 0 || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) { + throw new Error(`${path}[${index}] must be an enumerable data property`); + } + entries.set(index, snapshotBossData(descriptor.value, `${path}[${index}]`, seen, depth + 1)); + } + if (entries.size !== lengthDescriptor.value) throw new Error(`${path} must not contain sparse array holes`); + return Array.from({ length: lengthDescriptor.value }, (_, index) => entries.get(index)); + } + if (Object.getPrototypeOf(value) !== Object.prototype) throw new Error(`${path} must be a plain object`); + const snapshot = {}; + for (const key of Reflect.ownKeys(value)) { + if (typeof key !== "string") throw new Error(`${path} must not contain symbol properties`); + const descriptor = Object.getOwnPropertyDescriptor(value, key); + if (descriptor === void 0 || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) { + throw new Error(`${path}.${key} must be an enumerable data property`); + } + Object.defineProperty(snapshot, key, { + configurable: true, + enumerable: true, + value: snapshotBossData(descriptor.value, `${path}.${key}`, seen, depth + 1), + writable: true + }); + } + return snapshot; +} +function authoritativeBossSessionInfo(value) { + try { + const snapshot = snapshotBossData(value, "$.boss_control.from"); + if (typeof snapshot !== "object" || snapshot === null || Array.isArray(snapshot)) return void 0; + const session = snapshot; + assertExactKeys2( + session, + [...BOSS_SESSION_REQUIRED_FIELDS], + [...BOSS_SESSION_OPTIONAL_FIELDS], + "$.boss_control.from" + ); + const { boss, ...ordinaryFields } = session; + if (!isSessionInfo(ordinaryFields)) return void 0; + const parsedBoss = parseBossSessionMetadata(boss, ordinaryFields.id); + if (parsedBoss.registration.state !== "active" || !parsedBoss.registration.brokerIdentityVerified || parsedBoss.principal.state !== "active" || parsedBoss.binding !== void 0 && parsedBoss.binding.state !== "active") { + return void 0; + } + return { ...ordinaryFields, boss: parsedBoss }; + } catch { + return void 0; + } } function isRemoteAccessMetadata(value) { if (typeof value !== "object" || value === null || Array.isArray(value)) return false; @@ -548,6 +611,7 @@ var IntercomClient = class extends EventEmitter { pendingSends = /* @__PURE__ */ new Map(); pendingLists = /* @__PURE__ */ new Map(); pendingAskControls = /* @__PURE__ */ new Map(); + pendingBossControls = /* @__PURE__ */ new Map(); outbox = null; remoteAccessCredential; disconnecting = false; @@ -566,6 +630,8 @@ var IntercomClient = class extends EventEmitter { pending.resolve(false); } this.pendingAskControls.clear(); + for (const pending of this.pendingBossControls.values()) pending.reject(error); + this.pendingBossControls.clear(); } get sessionId() { return this._sessionId; @@ -724,6 +790,9 @@ var IntercomClient = class extends EventEmitter { if (typeof brokerMessage.sessionId !== "string" || brokerMessage.protocol !== INTERCOM_PROTOCOL_NAME || brokerMessage.version !== INTERCOM_PROTOCOL_VERSION) { throw new Error("Invalid registered message"); } + if (brokerMessage.boss !== void 0 || brokerMessage.capabilities !== void 0) { + throw new Error("Ordinary registration must not contain feature or Boss metadata"); + } if (this._sessionId !== null) { throw new Error("Received duplicate registered message"); } @@ -772,6 +841,75 @@ var IntercomClient = class extends EventEmitter { this.emit("message", from, message, deliveryId); break; } + case "boss_control": { + const { deliveryId, envelope } = brokerMessage; + const from = authoritativeBossSessionInfo(brokerMessage.from); + if (typeof deliveryId !== "string" || from === void 0) throw new Error("Invalid boss_control event"); + const parsed = parseBoundBossControl( + snapshotBossData(envelope, "$.boss_control.envelope"), + from + ); + this.emit("boss_control", from, parsed, deliveryId); + break; + } + case "boss_control_accepted": { + exactBossControlFrame(brokerMessage, ["type", "messageId", "deliveryId"], "$.boss_control_accepted"); + const { deliveryId, messageId } = brokerMessage; + bossControlFrameString(deliveryId, "$.boss_control_accepted.deliveryId"); + bossControlFrameString(messageId, "$.boss_control_accepted.messageId"); + const pending = this.pendingBossControls.get(messageId); + if (!pending) break; + if (pending.accepted) throw new Error("Duplicate Boss control acceptance"); + if (pending.deliveryId !== void 0) throw new Error("Boss control acceptance state is contradictory"); + pending.accepted = true; + pending.deliveryId = deliveryId; + break; + } + case "boss_control_delivered": { + exactBossControlFrame(brokerMessage, ["type", "messageId", "deliveryId"], "$.boss_control_delivered"); + const { deliveryId, messageId } = brokerMessage; + bossControlFrameString(deliveryId, "$.boss_control_delivered.deliveryId"); + bossControlFrameString(messageId, "$.boss_control_delivered.messageId"); + const pending = this.pendingBossControls.get(messageId); + if (!pending) break; + if (!pending.accepted || pending.deliveryId !== deliveryId) { + throw new Error("Boss control delivery did not follow matching acceptance"); + } + this.pendingBossControls.delete(messageId); + pending.resolve({ id: messageId, accepted: true, delivered: true, deliveryId }); + break; + } + case "boss_control_failed": { + const { accepted } = brokerMessage; + if (typeof accepted !== "boolean") throw new Error("Invalid boss_control_failed message"); + exactBossControlFrame( + brokerMessage, + accepted ? ["type", "messageId", "deliveryId", "accepted", "code", "reason"] : ["type", "messageId", "accepted", "code", "reason"], + "$.boss_control_failed" + ); + const { code, deliveryId, messageId, reason } = brokerMessage; + if (!isBossControlFailureCode(code, accepted) || typeof reason !== "string" || reason.length === 0) { + throw new Error("Invalid boss_control_failed message"); + } + bossControlFrameString(messageId, "$.boss_control_failed.messageId"); + if (accepted) bossControlFrameString(deliveryId, "$.boss_control_failed.deliveryId"); + const pending = this.pendingBossControls.get(messageId); + if (!pending) break; + if (accepted !== pending.accepted) throw new Error("Boss control failure acceptance state is inconsistent"); + if (accepted && pending.deliveryId !== deliveryId) { + throw new Error("Boss control failure did not follow matching acceptance"); + } + this.pendingBossControls.delete(messageId); + pending.resolve({ + id: messageId, + accepted, + delivered: false, + code, + reason, + ...accepted ? { deliveryId } : {} + }); + break; + } case "delivery_accepted": { const { deliveryId, messageId } = brokerMessage; if (typeof deliveryId !== "string" || typeof messageId !== "string") { @@ -1022,9 +1160,56 @@ var IntercomClient = class extends EventEmitter { } }); } + sendBossControl(to, envelopeValue) { + let socket; + try { + socket = this.requireActiveSocket(); + } catch (error) { + return Promise.reject(toError(error)); + } + let envelope; + try { + envelope = parseBossControlEnvelope2(envelopeValue); + } catch (error) { + return Promise.reject(toError(error)); + } + if (this.pendingBossControls.has(envelope.messageId)) { + return Promise.resolve({ + id: envelope.messageId, + accepted: false, + delivered: false, + code: "CONFLICTING_MESSAGE_ID", + reason: `Boss control message ID ${envelope.messageId} is already pending` + }); + } + return new Promise((resolve3, reject) => { + const timeout = setTimeout(() => { + if (!this.pendingBossControls.delete(envelope.messageId)) return; + reject(new Error("Boss control send timeout")); + }, 1e4); + const wrappedResolve = (result) => { + clearTimeout(timeout); + resolve3(result); + }; + const wrappedReject = (error) => { + clearTimeout(timeout); + reject(error); + }; + this.pendingBossControls.set(envelope.messageId, { accepted: false, resolve: wrappedResolve, reject: wrappedReject }); + try { + writeMessage(socket, { type: "boss_control_send", to, envelope }); + } catch (error) { + this.pendingBossControls.delete(envelope.messageId); + wrappedReject(toError(error)); + } + }); + } acknowledgeMessage(deliveryId) { return this.writeControlMessage({ type: "message_received", deliveryId }); } + acknowledgeBossControl(deliveryId) { + return this.writeControlMessage({ type: "boss_control_received", deliveryId }); + } rejectMessage(deliveryId, reason) { return this.writeControlMessage({ type: "message_rejected", deliveryId, code: "CONFLICTING_MESSAGE_ID", reason }); } @@ -1097,6 +1282,10 @@ import { fileURLToPath } from "url"; import { createRequire } from "module"; import net2 from "net"; import { randomUUID as randomUUID3 } from "crypto"; +import { + POLICY_SEMANTICS_HASH as POLICY_SEMANTICS_HASH2, + POLICY_SEMANTICS_VERSION as POLICY_SEMANTICS_VERSION2 +} from "@dataforxyz/agent-intercom-core"; var INTERCOM_DIR = getIntercomDirPath(); var EXTENSION_DIR = join3(dirname2(fileURLToPath(import.meta.url)), ".."); var BROKER_PID = join3(INTERCOM_DIR, "broker.pid"); @@ -1158,7 +1347,7 @@ function isBrokerHealthOkMessage(message, requestId) { const remoteAccess = response.remoteAccess; if (typeof remoteAccess !== "object" || remoteAccess === null || Array.isArray(remoteAccess)) return false; const contract = remoteAccess; - return contract.feature === "remote-access-v1" && contract.policySemanticsVersion === POLICY_SEMANTICS_VERSION && contract.policySemanticsHash === POLICY_SEMANTICS_HASH; + return contract.feature === "remote-access-v1" && contract.policySemanticsVersion === POLICY_SEMANTICS_VERSION2 && contract.policySemanticsHash === POLICY_SEMANTICS_HASH2; } function writeWindowsHiddenLauncher(commandLine, launcherPath = getWindowsHiddenLauncherPath()) { ensureIntercomRuntimeDir(dirname2(launcherPath)); @@ -2272,6 +2461,680 @@ function startOpenCodeControlServer(options) { return () => clearInterval(timer); } +// opencode/notice-ingress.ts +import { createHash as createHash3, randomUUID as randomUUID6 } from "node:crypto"; +import { existsSync as existsSync5, readFileSync as readFileSync8 } from "node:fs"; +import { dirname as dirname5, join as join8 } from "node:path"; +import { + parseDeliveryClaimRecord, + parseNoticeRecipientIngressEnvelope, + parseTargetLedgerLookupResult +} from "@dataforxyz/agent-intercom-core/boss"; +import { + canonicalJson, + assertExactKeys as assertExactKeys3, + assertRecord as assertRecord2 +} from "@dataforxyz/agent-intercom-core/canonical"; +var OPENCODE_NOTICE_ATOMIC_INSERTION_VERSION = "opencode.notice-atomic-insertion.v1"; +var OPENCODE_NOTICE_CURRENT_CLAIM_EVIDENCE_VERSION = "opencode.notice-current-claim-evidence.v1"; +var OPENCODE_NOTICE_AUTHORITY_UNAVAILABLE = "OPENCODE_NOTICE_AUTHORITY_UNAVAILABLE"; +var OPENCODE_NOTICE_CURRENT_CLAIM_UNAVAILABLE = "OPENCODE_NOTICE_CURRENT_CLAIM_UNAVAILABLE"; +var INSERTION_FENCING_UNAVAILABLE = "INSERTION_FENCING_UNAVAILABLE"; +var OpenCodeNoticeAuthorityUnavailableError = class extends Error { + code = OPENCODE_NOTICE_AUTHORITY_UNAVAILABLE; + constructor() { + super("OpenCode Boss notice ingress is unavailable until an authenticated Orc/Controller authority client and typed notice-to-prompt entrypoint are provided"); + this.name = "OpenCodeNoticeAuthorityUnavailableError"; + } +}; +var OpenCodeNoticeCurrentClaimUnavailableError = class extends Error { + code = OPENCODE_NOTICE_CURRENT_CLAIM_UNAVAILABLE; + retryable = true; + constructor(reason, options) { + super(`OpenCode Boss notice insertion requires a new authenticated reservation before retry: ${reason}`, options); + this.name = "OpenCodeNoticeCurrentClaimUnavailableError"; + } +}; +var OpenCodeNoticeInsertionFencingUnavailableError = class extends Error { + code = INSERTION_FENCING_UNAVAILABLE; + retryable = true; + constructor() { + super("OpenCode Boss notice insertion is unavailable without a protected authenticated atomic current-claim/deadline-bound insertion authority"); + this.name = "OpenCodeNoticeInsertionFencingUnavailableError"; + } +}; +function createProductionOpenCodeNoticeRecipientIngress() { + throw new OpenCodeNoticeAuthorityUnavailableError(); +} +function emptyState() { + return { version: 1, records: /* @__PURE__ */ Object.create(null) }; +} +function collisionResistantName(value) { + return createHash3("sha256").update(value).digest("hex"); +} +function getOpenCodeNoticeIngressStatePath(sessionId, intercomDir = getIntercomDirPath()) { + return join8(intercomDir, `opencode-notice-ingress-${collisionResistantName(sessionId)}.json`); +} +function ownRecord(value, path) { + assertRecord2(value, path); + return value; +} +function exactKeys(value, required, optional, path) { + assertExactKeys3(value, required, optional, path); +} +function payload(envelope) { + return envelope.payload; +} +function timestamp(value, path) { + if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) throw new Error(`${path} must be a timestamp`); + return value; +} +function nonEmptyString2(value, path) { + if (typeof value !== "string" || value.length === 0) throw new Error(`${path} must be a non-empty string`); + return value; +} +function assertSame(actual, expected, field, context) { + if (actual !== expected) throw new Error(`${context} ${field} does not match the winning claim`); +} +function assertReservationMatches(envelope, claim) { + if (envelope.operation !== "reserve_delivery") throw new Error("Expected reserve_delivery before prompt injection"); + if (claim.state !== "reserved") throw new Error("Notice delivery claim must be reserved before prompt injection"); + if (claim.recipientContext !== "opencode") throw new Error("Notice delivery claim is not for OpenCode"); + const request = payload(envelope); + const comparisons = [ + [claim.deliveryGroupId, request.deliveryGroupId, "deliveryGroupId"], + [claim.membershipRevision, request.membershipRevision, "membershipRevision"], + [claim.effectiveDeliveryIntent, request.effectiveDeliveryIntent, "effectiveDeliveryIntent"], + [claim.primaryNoticeId, request.primaryNoticeId, "primaryNoticeId"], + [claim.recipientContext, request.recipientContext, "recipientContext"], + [claim.recipientSessionId, request.recipientSessionId, "recipientSessionId"], + [claim.recipientTargetSessionId, request.recipientTargetSessionId, "recipientTargetSessionId"], + [claim.recipientPrincipalId, request.recipientPrincipalId, "recipientPrincipalId"], + [claim.recipientBindingEpoch, request.recipientBindingEpoch, "recipientBindingEpoch"], + [claim.recipientTransferGeneration, request.recipientTransferGeneration, "recipientTransferGeneration"], + [claim.workerGeneration, request.workerGeneration, "workerGeneration"] + ]; + for (const [actual, expected, field] of comparisons) assertSame(actual, expected, field, "Reserved notice claim"); + if (canonicalJson(claim.memberNoticeIds) !== canonicalJson(request.memberNoticeIds)) { + throw new Error("Reserved notice claim memberNoticeIds do not match the ingress request"); + } + if (Date.parse(timestamp(request.requestedAt, "$.payload.requestedAt")) >= Date.parse(claim.expiresAt)) { + throw new Error("Notice reservation was requested after the winning claim expired"); + } +} +function assertWallClockFresh(claim, now) { + if (Date.parse(claim.expiresAt) <= now) { + throw new OpenCodeNoticeCurrentClaimUnavailableError("the winning delivery claim is wall-clock expired"); + } +} +function atomicInsertionRequest(record, insertion, now) { + return { + version: OPENCODE_NOTICE_ATOMIC_INSERTION_VERSION, + requestNonce: randomUUID6(), + requestedAt: new Date(now).toISOString(), + claim: structuredClone(record.claim), + insertion: structuredClone(insertion) + }; +} +function parseInsertionReceipt(value, path) { + const receipt = ownRecord(value, path); + exactKeys(receipt, ["deliveryClaimId", "claimGeneration", "targetLedgerEntryId", "insertedAt"], [], path); + const claimGeneration = receipt.claimGeneration; + if (!Number.isSafeInteger(claimGeneration) || claimGeneration < 0) { + throw new Error(`${path}.claimGeneration must be a non-negative safe integer`); + } + return { + deliveryClaimId: nonEmptyString2(receipt.deliveryClaimId, `${path}.deliveryClaimId`), + claimGeneration, + targetLedgerEntryId: nonEmptyString2(receipt.targetLedgerEntryId, `${path}.targetLedgerEntryId`), + insertedAt: timestamp(receipt.insertedAt, `${path}.insertedAt`) + }; +} +function parseAtomicInsertionResult(value) { + const result = ownRecord(value, "$atomicInsertionResult"); + exactKeys( + result, + ["version", "requestNonce", "status"], + ["claim", "receipt"], + "$atomicInsertionResult" + ); + if (result.version !== OPENCODE_NOTICE_ATOMIC_INSERTION_VERSION) { + throw new Error("Unsupported OpenCode atomic insertion result version"); + } + const status = result.status; + if (status !== "inserted" && status !== "revoked" && status !== "superseded" && status !== "expired") { + throw new Error("$atomicInsertionResult.status is invalid"); + } + const claim = result.claim === void 0 ? void 0 : parseDeliveryClaimRecord(result.claim); + const receipt = result.receipt === void 0 ? void 0 : parseInsertionReceipt(result.receipt, "$atomicInsertionResult.receipt"); + if (status === "inserted" ? claim === void 0 || receipt === void 0 : claim !== void 0 || receipt !== void 0) { + throw new Error("$atomicInsertionResult claim and receipt are present exactly for inserted status"); + } + return { + version: OPENCODE_NOTICE_ATOMIC_INSERTION_VERSION, + requestNonce: nonEmptyString2(result.requestNonce, "$atomicInsertionResult.requestNonce"), + status, + ...claim === void 0 ? {} : { claim }, + ...receipt === void 0 ? {} : { receipt } + }; +} +function assertAtomicInsertionResultMatches(record, request, result) { + if (result.requestNonce !== request.requestNonce) { + throw new OpenCodeNoticeCurrentClaimUnavailableError("atomic insertion result does not bind the fresh request nonce"); + } + if (result.status !== "inserted" || !result.claim || !result.receipt) { + throw new OpenCodeNoticeCurrentClaimUnavailableError(`the authenticated winning claim is ${result.status}`); + } + if (canonicalJson(result.claim) !== canonicalJson(record.claim)) { + throw new OpenCodeNoticeCurrentClaimUnavailableError("atomic insertion current-claim proof does not exactly match the durable winner"); + } + assertInsertionReceiptMatches(record, result.receipt); +} +function assertInsertionMatches(record, envelope) { + if (envelope.operation !== "insert_or_attach") throw new Error("Expected insert_or_attach"); + const request = payload(envelope); + const claim = record.claim; + const comparisons = [ + [claim.deliveryClaimId, request.deliveryClaimId, "deliveryClaimId"], + [claim.claimGeneration, request.claimGeneration, "claimGeneration"], + [claim.deliveryGroupId, request.deliveryGroupId, "deliveryGroupId"], + [claim.membershipRevision, request.membershipRevision, "membershipRevision"], + [claim.effectiveDeliveryIntent, request.effectiveDeliveryIntent, "effectiveDeliveryIntent"], + [claim.primaryNoticeId, request.primaryNoticeId, "primaryNoticeId"], + [claim.recipientPrincipalId, request.recipientPrincipalId, "recipientPrincipalId"], + [claim.recipientBindingEpoch, request.recipientBindingEpoch, "recipientBindingEpoch"], + [claim.workerGeneration, request.workerGeneration, "workerGeneration"], + [claim.ingressMode, request.ingressMode, "ingressMode"] + ]; + for (const [actual, expected, field] of comparisons) assertSame(actual, expected, field, "Notice insertion"); + if (canonicalJson(claim.memberNoticeIds) !== canonicalJson(request.memberNoticeIds)) { + throw new Error("Notice insertion memberNoticeIds do not match the winning claim"); + } + if (canonicalJson(request.transitionIds) !== canonicalJson([claim.transitionId])) { + throw new Error("Notice insertion transitionIds do not exactly match the winning claim transition"); + } + const requestedAt = timestamp(request.requestedAt, "$.payload.requestedAt"); + if (Date.parse(requestedAt) >= Date.parse(claim.expiresAt)) throw new Error("Notice insertion was requested after claim expiry"); + const reserveRequestedAt = timestamp(payload(record.reserve).requestedAt, "$.reserve.payload.requestedAt"); + if (Date.parse(requestedAt) < Date.parse(reserveRequestedAt)) throw new Error("Notice insertion predates its reservation"); +} +function assertLookupMatches(record, request, lookup) { + assertSame(record.claim.deliveryClaimId, lookup.deliveryClaimId, "deliveryClaimId", "Target ledger lookup"); + assertSame(record.claim.claimGeneration, lookup.claimGeneration, "claimGeneration", "Target ledger lookup"); + const requestedAt = timestamp(payload(request).checkedAt, "$.lookup.payload.checkedAt"); + if (Date.parse(lookup.checkedAt) < Date.parse(requestedAt)) { + throw new Error("Target ledger result predates its authenticated lookup request"); + } +} +function assertInsertionReceiptMatches(record, receipt) { + assertSame(record.claim.deliveryClaimId, receipt.deliveryClaimId, "deliveryClaimId", "OpenCode insertion receipt"); + assertSame(record.claim.claimGeneration, receipt.claimGeneration, "claimGeneration", "OpenCode insertion receipt"); + if (!receipt.targetLedgerEntryId) throw new Error("OpenCode insertion receipt targetLedgerEntryId is required"); + const insertedAt = timestamp(receipt.insertedAt, "$.insertedAt"); + const requestedAt = timestamp(payload(record.insertion).requestedAt, "$.insertion.payload.requestedAt"); + if (Date.parse(insertedAt) < Date.parse(requestedAt)) throw new Error("OpenCode insertion receipt predates the insertion attempt"); + if (Date.parse(insertedAt) >= Date.parse(record.claim.expiresAt)) { + throw new OpenCodeNoticeCurrentClaimUnavailableError("the insertion receipt is not strictly before the winning claim expiry"); + } +} +function assertReceiptMatches(record, envelope) { + if (envelope.operation !== "record_receipt") throw new Error("Expected record_receipt"); + if (!record.insertion || !record.targetLedgerEntryId || !record.insertedAt) { + throw new Error("Cannot receipt a notice without durable target-ledger insertion evidence"); + } + const request = payload(envelope); + const claim = record.claim; + const comparisons = [ + [claim.deliveryClaimId, request.deliveryClaimId, "deliveryClaimId"], + [claim.claimGeneration, request.claimGeneration, "claimGeneration"], + [claim.deliveryGroupId, request.deliveryGroupId, "deliveryGroupId"], + [claim.membershipRevision, request.membershipRevision, "membershipRevision"], + [claim.recipientPrincipalId, request.recipientPrincipalId, "recipientPrincipalId"], + [claim.recipientBindingEpoch, request.recipientBindingEpoch, "recipientBindingEpoch"], + [claim.workerGeneration, request.workerGeneration, "workerGeneration"], + [claim.ingressMode, request.deliveryMode, "deliveryMode"], + [record.targetLedgerEntryId, request.targetLedgerEntryId, "targetLedgerEntryId"], + [record.insertedAt, request.insertedAt, "insertedAt"], + [payload(record.insertion).resultMessageId, request.resultMessageId, "resultMessageId"] + ]; + for (const [actual, expected, field] of comparisons) assertSame(actual, expected, field, "Notice receipt"); +} +function assertDeliveredClaimMatches(record, delivered) { + if (delivered.state !== "delivered") throw new Error("Receipt authority did not return a delivered claim"); + const receipt = payload(record.receipt); + const immutableFields = [ + "deliveryClaimId", + "claimGeneration", + "deliveryGroupId", + "membershipRevision", + "effectiveDeliveryIntent", + "primaryNoticeId", + "recipientContext", + "recipientSessionId", + "recipientTargetSessionId", + "recipientPrincipalId", + "recipientBindingEpoch", + "recipientTransferGeneration", + "workerId", + "workerGeneration", + "transitionId", + "transitionVersion", + "assignmentId", + "turnId", + "watchdogGeneration", + "ingressMode" + ]; + for (const field of immutableFields) assertSame(record.claim[field], delivered[field], String(field), "Delivered claim"); + if (canonicalJson(record.claim.memberNoticeIds) !== canonicalJson(delivered.memberNoticeIds)) { + throw new Error("Delivered claim memberNoticeIds changed after reservation"); + } + const settlement = [ + [record.targetLedgerEntryId, delivered.targetLedgerEntryId, "targetLedgerEntryId"], + [record.insertedAt, delivered.insertedAt, "insertedAt"], + [receipt.deliveryReceiptId, delivered.deliveryReceiptId, "deliveryReceiptId"], + [receipt.deliveredAt, delivered.deliveredAt, "deliveredAt"], + [receipt.resultMessageId, delivered.resultMessageId, "resultMessageId"], + [receipt.coalescedByResult, delivered.coalescedByResult, "coalescedByResult"] + ]; + for (const [actual, expected, field] of settlement) assertSame(actual, expected, field, "Delivered claim"); +} +function parseRecord(value, path) { + const record = ownRecord(value, path); + exactKeys( + record, + ["reserve", "claim", "phase"], + ["insertion", "targetLedgerEntryId", "insertedAt", "receipt", "deliveredClaim"], + path + ); + const reserve = parseNoticeRecipientIngressEnvelope(record.reserve); + const claim = parseDeliveryClaimRecord(record.claim); + const phase = record.phase; + if (phase !== "reserved" && phase !== "inserting" && phase !== "inserted" && phase !== "receipting" && phase !== "delivered") { + throw new Error(`${path}.phase is invalid`); + } + const insertion = record.insertion === void 0 ? void 0 : parseNoticeRecipientIngressEnvelope(record.insertion); + const targetLedgerEntryId = record.targetLedgerEntryId === void 0 ? void 0 : String(record.targetLedgerEntryId); + if (record.targetLedgerEntryId !== void 0 && (typeof record.targetLedgerEntryId !== "string" || record.targetLedgerEntryId.length === 0)) { + throw new Error(`${path}.targetLedgerEntryId must be a non-empty string`); + } + const insertedAt = record.insertedAt === void 0 ? void 0 : timestamp(record.insertedAt, `${path}.insertedAt`); + const receipt = record.receipt === void 0 ? void 0 : parseNoticeRecipientIngressEnvelope(record.receipt); + const deliveredClaim = record.deliveredClaim === void 0 ? void 0 : parseDeliveryClaimRecord(record.deliveredClaim); + const parsed = { + reserve, + claim, + phase, + ...insertion === void 0 ? {} : { insertion }, + ...targetLedgerEntryId === void 0 ? {} : { targetLedgerEntryId }, + ...insertedAt === void 0 ? {} : { insertedAt }, + ...receipt === void 0 ? {} : { receipt }, + ...deliveredClaim === void 0 ? {} : { deliveredClaim } + }; + assertReservationMatches(reserve, claim); + if (insertion !== void 0) assertInsertionMatches(parsed, insertion); + const hasInsertionEvidence = targetLedgerEntryId !== void 0 || insertedAt !== void 0; + if (hasInsertionEvidence && (targetLedgerEntryId === void 0 || insertedAt === void 0)) { + throw new Error(`${path} target ledger evidence must be present together`); + } + if (phase === "reserved" && (insertion !== void 0 || hasInsertionEvidence || receipt !== void 0 || deliveredClaim !== void 0)) { + throw new Error(`${path} reserved record contains later-phase evidence`); + } + if (phase === "inserting" && (insertion === void 0 || hasInsertionEvidence || receipt !== void 0 || deliveredClaim !== void 0)) { + throw new Error(`${path} inserting record has invalid evidence`); + } + if (phase === "inserted" && (insertion === void 0 || !hasInsertionEvidence || receipt !== void 0 || deliveredClaim !== void 0)) { + throw new Error(`${path} inserted record has invalid evidence`); + } + if (phase === "receipting" && (insertion === void 0 || !hasInsertionEvidence || receipt === void 0 || deliveredClaim !== void 0)) { + throw new Error(`${path} receipting record has invalid evidence`); + } + if (phase === "delivered" && (insertion === void 0 || !hasInsertionEvidence || receipt === void 0 || deliveredClaim === void 0)) { + throw new Error(`${path} delivered record lacks settlement evidence`); + } + if (receipt !== void 0) assertReceiptMatches(parsed, receipt); + if (deliveredClaim !== void 0) assertDeliveredClaimMatches(parsed, deliveredClaim); + return parsed; +} +function parseState(value) { + const state = ownRecord(value, "$noticeIngress"); + exactKeys(state, ["version", "records"], [], "$noticeIngress"); + if (state.version !== 1) throw new Error("Unsupported OpenCode notice ingress state version"); + const recordsValue = ownRecord(state.records, "$noticeIngress.records"); + const records = /* @__PURE__ */ Object.create(null); + const deliveryGroups = /* @__PURE__ */ new Set(); + for (const [claimId, value2] of Object.entries(recordsValue)) { + const record = parseRecord(value2, `$noticeIngress.records[${JSON.stringify(claimId)}]`); + if (record.claim.deliveryClaimId !== claimId) throw new Error("Notice ingress claim key does not match its record"); + if (deliveryGroups.has(record.claim.deliveryGroupId)) throw new Error("Multiple OpenCode notice claims own one delivery group"); + deliveryGroups.add(record.claim.deliveryGroupId); + records[claimId] = record; + } + return { version: 1, records }; +} +function clone(record) { + return structuredClone(record); +} +function cloneState(state) { + return parseState(structuredClone(state)); +} +function getOwnRecord(records, deliveryClaimId) { + return Object.hasOwn(records, deliveryClaimId) ? records[deliveryClaimId] : void 0; +} +function serializableState(state) { + const records = {}; + for (const [deliveryClaimId, record] of Object.entries(state.records)) { + Object.defineProperty(records, deliveryClaimId, { + value: clone(record), + enumerable: true, + writable: true, + configurable: true + }); + } + return { version: 1, records }; +} +function canonicalState(state) { + return canonicalJson(serializableState(state)); +} +function targetLedgerLookupEnvelope(record) { + if (!record.insertion) throw new Error("Cannot look up a notice before insertion begins"); + const claim = record.claim; + const requestNonce = randomUUID6(); + const checkedAt = (/* @__PURE__ */ new Date()).toISOString(); + return parseNoticeRecipientIngressEnvelope({ + version: "orc.notice-recipient-ingress.v1", + operation: "lookup_target_ledger", + requestId: `${claim.deliveryClaimId}:opencode-ledger:${claim.claimGeneration}:${requestNonce}`, + idempotencyKey: `${claim.deliveryClaimId}:opencode-ledger:${claim.claimGeneration}:${requestNonce}`, + payload: { + deliveryClaimId: claim.deliveryClaimId, + claimGeneration: claim.claimGeneration, + recipientContext: claim.recipientContext, + recipientSessionId: claim.recipientSessionId, + ...claim.recipientTargetSessionId === void 0 ? {} : { recipientTargetSessionId: claim.recipientTargetSessionId }, + checkedAt + } + }); +} +var DurableOpenCodeNoticeIngressStore = class { + path; + state; + persist; + poisoned; + constructor(path, persist = writeDurableJson) { + this.path = path; + ensureIntercomRuntimeDir(dirname5(path)); + this.persist = persist; + this.state = this.load(); + } + get(deliveryClaimId) { + this.assertUsable(); + const record = getOwnRecord(this.state.records, deliveryClaimId); + return record === void 0 ? void 0 : clone(record); + } + reserve(envelopeValue, claimValue) { + this.assertUsable(); + const envelope = parseNoticeRecipientIngressEnvelope(envelopeValue); + const claim = parseDeliveryClaimRecord(claimValue); + assertReservationMatches(envelope, claim); + const existing = getOwnRecord(this.state.records, claim.deliveryClaimId); + if (existing) { + if (canonicalJson(existing.reserve) !== canonicalJson(envelope) || canonicalJson(existing.claim) !== canonicalJson(claim)) { + throw new Error("Conflicting OpenCode notice reservation"); + } + return clone(existing); + } + if (Object.values(this.state.records).some((record2) => record2.claim.deliveryGroupId === claim.deliveryGroupId)) { + throw new Error("A different OpenCode notice claim already owns this delivery group"); + } + const record = { reserve: envelope, claim, phase: "reserved" }; + const next = cloneState(this.state); + next.records[claim.deliveryClaimId] = record; + this.commit(next); + return clone(record); + } + beginInsertion(envelopeValue) { + this.assertUsable(); + const envelope = parseNoticeRecipientIngressEnvelope(envelopeValue); + if (envelope.operation !== "insert_or_attach") throw new Error("Expected insert_or_attach"); + const claimId = payload(envelope).deliveryClaimId; + if (typeof claimId !== "string") throw new Error("Notice insertion omitted deliveryClaimId"); + const record = getOwnRecord(this.state.records, claimId); + if (!record) throw new Error("Notice insertion has no durable winning reservation"); + assertInsertionMatches(record, envelope); + if (record.phase !== "reserved") { + if (canonicalJson(record.insertion) !== canonicalJson(envelope)) throw new Error("Conflicting OpenCode notice insertion replay"); + return clone(record); + } + const updated = { ...record, phase: "inserting", insertion: envelope }; + parseRecord(updated, "$record"); + const next = cloneState(this.state); + next.records[claimId] = updated; + this.commit(next); + return clone(updated); + } + markInserted(deliveryClaimId, receipt) { + this.assertUsable(); + const record = getOwnRecord(this.state.records, deliveryClaimId); + if (!record || record.phase === "reserved") throw new Error("Cannot receipt an unreserved OpenCode notice insertion"); + assertInsertionReceiptMatches(record, receipt); + if (record.phase !== "inserting") { + if (record.targetLedgerEntryId !== receipt.targetLedgerEntryId || record.insertedAt !== receipt.insertedAt) { + throw new Error("Conflicting OpenCode notice ledger receipt"); + } + return clone(record); + } + const updated = { + ...record, + phase: "inserted", + targetLedgerEntryId: receipt.targetLedgerEntryId, + insertedAt: receipt.insertedAt + }; + parseRecord(updated, "$record"); + const next = cloneState(this.state); + next.records[deliveryClaimId] = updated; + this.commit(next); + return clone(updated); + } + beginReceipt(envelopeValue) { + this.assertUsable(); + const envelope = parseNoticeRecipientIngressEnvelope(envelopeValue); + if (envelope.operation !== "record_receipt") throw new Error("Expected record_receipt"); + const claimId = payload(envelope).deliveryClaimId; + if (typeof claimId !== "string") throw new Error("Notice receipt omitted deliveryClaimId"); + const record = getOwnRecord(this.state.records, claimId); + if (!record || record.phase !== "inserted" && record.phase !== "receipting" && record.phase !== "delivered") { + throw new Error("Cannot record a receipt before durable OpenCode insertion"); + } + assertReceiptMatches(record, envelope); + if (record.phase !== "inserted") { + if (canonicalJson(record.receipt) !== canonicalJson(envelope)) throw new Error("Conflicting OpenCode notice receipt replay"); + return clone(record); + } + const updated = { ...record, phase: "receipting", receipt: envelope }; + parseRecord(updated, "$record"); + const next = cloneState(this.state); + next.records[claimId] = updated; + this.commit(next); + return clone(updated); + } + markDelivered(deliveryClaimId, claimValue) { + this.assertUsable(); + const record = getOwnRecord(this.state.records, deliveryClaimId); + if (!record || record.phase !== "receipting" && record.phase !== "delivered") { + throw new Error("Cannot settle an OpenCode notice before recording its receipt request"); + } + const deliveredClaim = parseDeliveryClaimRecord(claimValue); + assertDeliveredClaimMatches(record, deliveredClaim); + if (record.phase === "delivered") { + if (canonicalJson(record.deliveredClaim) !== canonicalJson(deliveredClaim)) throw new Error("Conflicting delivered claim replay"); + return clone(record); + } + const updated = { ...record, phase: "delivered", deliveredClaim }; + parseRecord(updated, "$record"); + const next = cloneState(this.state); + next.records[deliveryClaimId] = updated; + this.commit(next); + return clone(updated); + } + pending() { + this.assertUsable(); + return Object.values(this.state.records).filter((record) => record.phase !== "delivered").map(clone); + } + load() { + if (!existsSync5(this.path)) return emptyState(); + return parseState(JSON.parse(readFileSync8(this.path, "utf8"))); + } + loadExactTarget() { + if (!existsSync5(this.path)) throw new Error("Durable OpenCode notice ingress target is missing"); + return parseState(JSON.parse(readFileSync8(this.path, "utf8"))); + } + commit(next) { + const stagedCanonical = canonicalState(parseState(structuredClone(next))); + const priorCanonical = canonicalState(this.state); + try { + this.persist(this.path, serializableState(parseState(JSON.parse(stagedCanonical)))); + } catch (persistError) { + try { + const recovered = this.loadExactTarget(); + const recoveredCanonical = canonicalState(recovered); + if (recoveredCanonical === stagedCanonical) { + this.state = parseState(JSON.parse(stagedCanonical)); + } else if (recoveredCanonical === priorCanonical) { + this.state = parseState(JSON.parse(priorCanonical)); + } else { + throw new Error("Durable OpenCode notice ingress state does not match the prior or staged commit"); + } + } catch (reconcileError) { + this.poisoned = new Error("Durable OpenCode notice ingress store is unavailable after commit reconciliation failed", { + cause: reconcileError + }); + } + throw persistError; + } + this.state = parseState(JSON.parse(stagedCanonical)); + } + assertUsable() { + if (this.poisoned) throw this.poisoned; + } +}; +var OpenCodeNoticeRecipientIngress = class { + constructor(store, authority, now = () => Date.now()) { + this.store = store; + this.authority = authority; + this.now = now; + if (!authority) throw new Error("Authenticated notice authority API is required"); + } + store; + authority; + now; + async reserveBeforePrompt(envelopeValue) { + const envelope = parseNoticeRecipientIngressEnvelope(envelopeValue); + if (envelope.operation !== "reserve_delivery") throw new Error("Expected reserve_delivery"); + const claim = await this.authority.reserveDelivery(envelope); + return this.store.reserve(envelope, claim); + } + async insertOrAttach(envelopeValue, injectPromptOrAttach) { + const envelope = parseNoticeRecipientIngressEnvelope(envelopeValue); + const claimId = payload(envelope).deliveryClaimId; + if (typeof claimId !== "string") throw new Error("Notice insertion omitted deliveryClaimId"); + const prior = this.store.get(claimId); + if (prior?.phase === "reserved") { + assertInsertionMatches(prior, envelope); + const protectedInsertion = this.authority.insertOrAttachWhileClaimCurrent; + if (typeof protectedInsertion !== "function") { + throw new OpenCodeNoticeInsertionFencingUnavailableError(); + } + const requestedAt = this.now(); + assertWallClockFresh(prior.claim, requestedAt); + const request = atomicInsertionRequest(prior, envelope, requestedAt); + let callbackCalls = 0; + let authorityCallOpen = false; + let rawResult; + const guardedInsertion = async () => { + if (!authorityCallOpen || callbackCalls !== 0) throw new Error("FENCING_CALLBACK_CLOSED"); + callbackCalls = 1; + assertWallClockFresh(prior.claim, this.now()); + const inserting2 = this.store.beginInsertion(envelope); + if (inserting2.phase !== "inserting") throw new Error("Protected OpenCode notice insertion did not begin from its reserved phase"); + return parseInsertionReceipt(await injectPromptOrAttach(envelope), "$protectedInsertionReceipt"); + }; + try { + authorityCallOpen = true; + rawResult = await new Promise((resolve3, reject) => { + try { + protectedInsertion.call(this.authority, request, guardedInsertion).then( + (value) => { + authorityCallOpen = false; + resolve3(value); + }, + (error) => { + authorityCallOpen = false; + reject(error); + } + ); + } catch (error) { + authorityCallOpen = false; + reject(error); + } + }); + } finally { + authorityCallOpen = false; + } + const result = parseAtomicInsertionResult(rawResult); + if (result.status !== "inserted") { + if (callbackCalls !== 0) { + throw new Error(`Protected insertion authority invoked the target but returned ${result.status}`); + } + assertAtomicInsertionResultMatches(prior, request, result); + } + const expectedInserting = { + ...prior, + phase: "inserting", + insertion: envelope + }; + parseRecord(expectedInserting, "$expectedProtectedInsertion"); + assertAtomicInsertionResultMatches(expectedInserting, request, result); + if (callbackCalls !== 1) { + throw new Error("Protected insertion authority claimed insertion without invoking the protected target operation"); + } + const inserting = this.store.get(claimId); + if (!inserting || inserting.phase !== "inserting") { + throw new Error("Protected OpenCode notice insertion lacks its durable inserting phase"); + } + return this.store.markInserted(inserting.claim.deliveryClaimId, result.receipt); + } + const record = this.store.beginInsertion(envelope); + if (record.phase === "inserted" || record.phase === "receipting" || record.phase === "delivered") return record; + if (prior?.phase === "inserting") { + const lookupEnvelope = targetLedgerLookupEnvelope(record); + const lookup = parseTargetLedgerLookupResult(await this.authority.lookupTargetLedger(lookupEnvelope)); + assertLookupMatches(record, lookupEnvelope, lookup); + if (lookup.state === "inserted") { + return this.store.markInserted(record.claim.deliveryClaimId, { + deliveryClaimId: lookup.deliveryClaimId, + claimGeneration: lookup.claimGeneration, + targetLedgerEntryId: lookup.targetLedgerEntryId, + insertedAt: lookup.insertedAt + }); + } + if (lookup.state !== "absent") { + throw new Error(`Authenticated target ledger is ${lookup.state}; refusing ambiguous OpenCode replay`); + } + throw new Error( + "Authenticated target-drained proof and generation-incremented current-claim reissue authority are unavailable; refusing OpenCode reinsertion" + ); + } + throw new Error("Unexpected OpenCode notice insertion state"); + } + async recordReceipt(envelopeValue) { + const envelope = parseNoticeRecipientIngressEnvelope(envelopeValue); + const record = this.store.beginReceipt(envelope); + if (record.phase === "delivered") return record; + const deliveredClaim = await this.authority.recordReceipt(envelope); + return this.store.markDelivered(record.claim.deliveryClaimId, deliveredClaim); + } +}; + // opencode/plugin.ts var INJECT_LOG_PATH = "/tmp/intercom-inject.log"; function resultText(result) { @@ -2817,6 +3680,15 @@ var OpenCodeIntercomPlugin = async ({ client, directory, serverUrl }) => { }; var plugin_default = OpenCodeIntercomPlugin; export { + DurableOpenCodeNoticeIngressStore, + OPENCODE_NOTICE_AUTHORITY_UNAVAILABLE, + OPENCODE_NOTICE_CURRENT_CLAIM_EVIDENCE_VERSION, + OPENCODE_NOTICE_CURRENT_CLAIM_UNAVAILABLE, OpenCodeIntercomPlugin, - plugin_default as default + OpenCodeNoticeAuthorityUnavailableError, + OpenCodeNoticeCurrentClaimUnavailableError, + OpenCodeNoticeRecipientIngress, + createProductionOpenCodeNoticeRecipientIngress, + plugin_default as default, + getOpenCodeNoticeIngressStatePath }; diff --git a/dist/tui.mjs b/dist/tui.mjs index da8481c..92e6bd1 100644 --- a/dist/tui.mjs +++ b/dist/tui.mjs @@ -1,161 +1,53 @@ // opencode/contact.ts import { spawnSync } from "node:child_process"; -// ../../src/github.com/dataforxyz/agent-intercom-opencode/node_modules/@dataforxyz/agent-intercom-core/dist/policy-vectors.js -var localRoot = { - id: "local-root", - kind: "local", - state: "active", - generation: 1, - policy: "local-public", - rootSessionId: "local-root" -}; -var localPeer = { - id: "local-peer", - kind: "local", - state: "active", - generation: 1, - policy: "local-public", - rootSessionId: "local-peer" -}; -var remoteManager = { - id: "remote-manager", - kind: "remote", - state: "active", - generation: 1, - policy: "remote-tree", - parentSessionId: "local-root", - rootSessionId: "local-root" -}; -var remoteChild = { - id: "remote-child", - kind: "remote", - state: "active", - generation: 1, - policy: "remote-tree", - parentSessionId: "remote-manager", - rootSessionId: "local-root" -}; -var remoteSibling = { - id: "remote-sibling", - kind: "remote", - state: "active", - generation: 1, - policy: "remote-tree", - parentSessionId: "remote-manager", - rootSessionId: "local-root" +// broker/client.ts +import { + POLICY_SEMANTICS_HASH, + POLICY_SEMANTICS_VERSION +} from "@dataforxyz/agent-intercom-core"; +import { + parseBossControlEnvelope as parseBossControlEnvelope2 +} from "@dataforxyz/agent-intercom-core/boss"; +import { assertExactKeys as assertExactKeys2 } from "@dataforxyz/agent-intercom-core/canonical"; + +// broker/boss.ts +import { + BOSS_CONTROL_TYPES, + parseBossControlEnvelope, + parseBossParticipantBinding, + parseBossPolicyPrincipal, + parseFeatureRegistration +} from "@dataforxyz/agent-intercom-core/boss"; +import { + ContractValidationError, + assertExactKeys, + assertRecord +} from "@dataforxyz/agent-intercom-core/canonical"; +var CONTROL_KIND_BY_TYPE = { + "boss.assignment.created": "assignment_request", + "boss.assignment.accepted": "assignment_response", + "boss.assignment.checkpoint": "assignment_response", + "boss.assignment.submitted": "assignment_response", + "boss.assignment.rejected": "assignment_response", + "boss.assignment.cancelled": "lifecycle", + "boss.staffing.requested": "staffing", + "boss.staffing.resolved": "staffing", + "boss.review.requested": "review_request", + "boss.review.submitted": "review_result", + "boss.council.requested": "review_request", + "boss.council.submitted": "review_result", + "boss.proof.submitted": "proof", + "boss.worker.health": "health", + "boss.worker.blocked": "health", + "boss.worker.failed": "health", + "boss.worker.notice": "lifecycle", + "boss.worker.notice_delivery_failed": "lifecycle", + "boss.decision.required": "decision" }; -var POLICY_VECTORS = [ - { - name: "local sessions remain public", - principals: [localRoot, localPeer], - actorId: "local-root", - action: "send", - targetId: "local-peer", - expectedAllowed: true, - expectedReasonOrCode: "local-public" - }, - { - name: "remote manager can reach direct local parent", - principals: [localRoot, remoteManager], - actorId: "remote-manager", - action: "send", - targetId: "local-root", - expectedAllowed: true, - expectedReasonOrCode: "direct-parent" - }, - { - name: "local parent can reach direct remote child", - principals: [localRoot, remoteManager], - actorId: "local-root", - action: "ask", - targetId: "remote-manager", - expectedAllowed: true, - expectedReasonOrCode: "direct-parent" - }, - { - name: "remote child can reach its local root through the ancestor chain", - principals: [localRoot, remoteManager, remoteChild], - actorId: "remote-child", - action: "send", - targetId: "local-root", - expectedAllowed: true, - expectedReasonOrCode: "ancestor-chain" - }, - { - name: "remote siblings cannot communicate in phase one", - principals: [localRoot, remoteManager, remoteChild, remoteSibling], - actorId: "remote-child", - action: "discover", - targetId: "remote-sibling", - expectedAllowed: false, - expectedReasonOrCode: "POLICY_DENIED" - }, - { - name: "unrelated local session cannot discover remote principal", - principals: [localRoot, localPeer, remoteManager], - actorId: "local-peer", - action: "discover", - targetId: "remote-manager", - expectedAllowed: false, - expectedReasonOrCode: "POLICY_DENIED" - }, - { - name: "remote principal cannot reach unrelated local session", - principals: [localRoot, localPeer, remoteManager], - actorId: "remote-manager", - action: "send", - targetId: "local-peer", - expectedAllowed: false, - expectedReasonOrCode: "POLICY_DENIED" - }, - { - name: "remote manager may inspect its descendant subtree", - principals: [localRoot, remoteManager, remoteChild], - actorId: "remote-manager", - action: "inspect_tree", - targetId: "remote-child", - expectedAllowed: true, - expectedReasonOrCode: "ancestor-control" - }, - { - name: "remote child cannot revoke its ancestor", - principals: [localRoot, remoteManager, remoteChild], - actorId: "remote-child", - action: "revoke", - targetId: "remote-manager", - expectedAllowed: false, - expectedReasonOrCode: "POLICY_DENIED" - }, - { - name: "remote principal may request attenuated delegation under itself", - principals: [localRoot, remoteManager], - actorId: "remote-manager", - action: "delegate_child", - targetId: "remote-manager", - expectedAllowed: true, - expectedReasonOrCode: "self" - }, - { - name: "revoked principal cannot communicate", - principals: [localRoot, { ...remoteManager, state: "revoked" }], - actorId: "remote-manager", - action: "send", - targetId: "local-root", - expectedAllowed: false, - expectedReasonOrCode: "REVOKED_PRINCIPAL" - }, - { - name: "stale actor generation cannot send", - principals: [localRoot, { ...remoteManager, generation: 2 }], - actorId: "remote-manager", - action: "send", - targetId: "local-root", - context: { actorGeneration: 1 }, - expectedAllowed: false, - expectedReasonOrCode: "STALE_GENERATION" - } -]; +if (Object.keys(CONTROL_KIND_BY_TYPE).length !== BOSS_CONTROL_TYPES.length) { + throw new Error("Boss control type mapping is incomplete"); +} // broker/framing.ts var MAX_FRAME_BYTES = 1024 * 1024; @@ -181,9 +73,37 @@ function restrictIntercomRuntimeFile(filePath, platform = process.platform) { } } +// durable-json.ts +import { closeSync, fsyncSync, openSync, renameSync, writeFileSync } from "fs"; +var DURABLE_JSON_FILE_OPERATIONS = Object.freeze({ + writeFile(filePath, contents, options) { + writeFileSync(filePath, contents, options); + }, + open(filePath, flags) { + return openSync(filePath, flags); + }, + fsync(fileDescriptor) { + fsyncSync(fileDescriptor); + }, + close(fileDescriptor) { + closeSync(fileDescriptor); + }, + rename(from, to) { + renameSync(from, to); + }, + restrict(filePath) { + restrictIntercomRuntimeFile(filePath); + }, + platform: process.platform +}); + // broker/spawn.ts import { join as join2, dirname, extname, basename } from "path"; import { fileURLToPath } from "url"; +import { + POLICY_SEMANTICS_HASH as POLICY_SEMANTICS_HASH2, + POLICY_SEMANTICS_VERSION as POLICY_SEMANTICS_VERSION2 +} from "@dataforxyz/agent-intercom-core"; var INTERCOM_DIR = getIntercomDirPath(); var EXTENSION_DIR = join2(dirname(fileURLToPath(import.meta.url)), ".."); var BROKER_PID = join2(INTERCOM_DIR, "broker.pid"); @@ -204,7 +124,7 @@ function copyText(text, platform = process.platform) { // opencode/control.ts import { randomUUID } from "node:crypto"; -import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs"; +import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync, renameSync as renameSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs"; import { join as join3 } from "node:path"; var CONTROL_DIR_NAME = "opencode-control"; function controlDir() { @@ -223,9 +143,9 @@ function responseName(sessionId, requestId) { } function writeJsonAtomic(path, value) { const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`; - writeFileSync(temporary, JSON.stringify(value), { mode: 384 }); + writeFileSync2(temporary, JSON.stringify(value), { mode: 384 }); restrictIntercomRuntimeFile(temporary); - renameSync(temporary, path); + renameSync2(temporary, path); restrictIntercomRuntimeFile(path); } async function requestOpenCodeControl(sessionId, action, timeoutMs = 5e3) { diff --git a/durable-json.ts b/durable-json.ts index 7f3ac21..f8cecf7 100644 --- a/durable-json.ts +++ b/durable-json.ts @@ -3,23 +3,59 @@ import { closeSync, fsyncSync, openSync, renameSync, writeFileSync } from "fs"; import { dirname } from "path"; import { INTERCOM_RUNTIME_FILE_MODE, restrictIntercomRuntimeFile } from "./broker/paths.ts"; -export function writeDurableJson(filePath: string, value: unknown): void { +export interface DurableJsonFileOperations { + writeFile(filePath: string, contents: string, options: { encoding: "utf-8"; mode: number }): void; + open(filePath: string, flags: "r"): number; + fsync(fileDescriptor: number): void; + close(fileDescriptor: number): void; + rename(from: string, to: string): void; + restrict(filePath: string): void; + readonly platform: NodeJS.Platform; +} + +export const DURABLE_JSON_FILE_OPERATIONS: DurableJsonFileOperations = Object.freeze({ + writeFile(filePath: string, contents: string, options: { encoding: "utf-8"; mode: number }): void { + writeFileSync(filePath, contents, options); + }, + open(filePath: string, flags: "r"): number { + return openSync(filePath, flags); + }, + fsync(fileDescriptor: number): void { + fsyncSync(fileDescriptor); + }, + close(fileDescriptor: number): void { + closeSync(fileDescriptor); + }, + rename(from: string, to: string): void { + renameSync(from, to); + }, + restrict(filePath: string): void { + restrictIntercomRuntimeFile(filePath); + }, + platform: process.platform, +}); + +export function writeDurableJson( + filePath: string, + value: unknown, + operations: DurableJsonFileOperations = DURABLE_JSON_FILE_OPERATIONS, +): void { const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`; - writeFileSync(temporaryPath, JSON.stringify(value), { encoding: "utf-8", mode: INTERCOM_RUNTIME_FILE_MODE }); - const fileDescriptor = openSync(temporaryPath, "r"); + operations.writeFile(temporaryPath, JSON.stringify(value), { encoding: "utf-8", mode: INTERCOM_RUNTIME_FILE_MODE }); + const fileDescriptor = operations.open(temporaryPath, "r"); try { - fsyncSync(fileDescriptor); + operations.fsync(fileDescriptor); } finally { - closeSync(fileDescriptor); + operations.close(fileDescriptor); } - renameSync(temporaryPath, filePath); - restrictIntercomRuntimeFile(filePath); - if (process.platform !== "win32") { - const directoryDescriptor = openSync(dirname(filePath), "r"); + operations.rename(temporaryPath, filePath); + operations.restrict(filePath); + if (operations.platform !== "win32") { + const directoryDescriptor = operations.open(dirname(filePath), "r"); try { - fsyncSync(directoryDescriptor); + operations.fsync(directoryDescriptor); } finally { - closeSync(directoryDescriptor); + operations.close(directoryDescriptor); } } } diff --git a/opencode/notice-ingress.test.ts b/opencode/notice-ingress.test.ts new file mode 100644 index 0000000..9ee298b --- /dev/null +++ b/opencode/notice-ingress.test.ts @@ -0,0 +1,1181 @@ +import assert from "node:assert/strict"; +import { existsSync, rmSync, writeFileSync } from "node:fs"; +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { + DELIVERY_CLAIM_VERSION, + NOTICE_RECIPIENT_INGRESS_VERSION, + TARGET_LEDGER_RESULT_VERSION, + type DeliveryClaimRecord, + type NoticeRecipientIngressEnvelope, + type TargetLedgerLookupResult, +} from "@dataforxyz/agent-intercom-core/boss"; +import { + deliveryClaimGeneration, + recipientTransferGeneration, + transitionVersion, + workerGeneration, +} from "@dataforxyz/agent-intercom-core/canonical"; +import { + DURABLE_JSON_FILE_OPERATIONS, + writeDurableJson, + type DurableJsonFileOperations, +} from "../durable-json.ts"; +import { + DurableOpenCodeNoticeIngressStore, + createProductionOpenCodeNoticeRecipientIngress, + getOpenCodeNoticeIngressStatePath, + INSERTION_FENCING_UNAVAILABLE, + OPENCODE_NOTICE_ATOMIC_INSERTION_VERSION, + OpenCodeNoticeCurrentClaimUnavailableError, + OpenCodeNoticeInsertionFencingUnavailableError, + OpenCodeNoticeRecipientIngress, + type AuthenticatedOpenCodeNoticeAuthority, + type OpenCodeNoticeAtomicInsertionRequest, + type OpenCodeNoticeAtomicInsertionResult, + type OpenCodeNoticeProtectedInsertion, +} from "./notice-ingress.ts"; + +const members = ["notice-a", "notice-b"]; + +function envelope( + operation: NoticeRecipientIngressEnvelope["operation"], + payload: Record, +): NoticeRecipientIngressEnvelope { + return { + version: NOTICE_RECIPIENT_INGRESS_VERSION, + operation, + requestId: `request-${operation}`, + idempotencyKey: `idempotency-${operation}`, + payload: payload as never, + }; +} + +const reserve = envelope("reserve_delivery", { + deliveryGroupId: "group-a", + membershipRevision: 1, + effectiveDeliveryIntent: "wake", + primaryNoticeId: "notice-a", + memberNoticeIds: members, + recipientContext: "opencode", + recipientSessionId: "intercom-manager-a", + recipientTargetSessionId: "opencode-ui-a", + recipientPrincipalId: "manager-a", + recipientBindingEpoch: 2, + recipientTransferGeneration: 0, + workerGeneration: 4, + requestedAt: "2026-07-28T12:00:00.000Z", +}); + +const claim: DeliveryClaimRecord = { + version: DELIVERY_CLAIM_VERSION, + deliveryClaimId: "claim-a", + deliveryGroupId: "group-a", + membershipRevision: 1, + effectiveDeliveryIntent: "wake", + primaryNoticeId: "notice-a", + memberNoticeIds: members, + claimGeneration: deliveryClaimGeneration(1), + expiresAt: "2099-07-28T12:10:00.000Z", + recipientContext: "opencode", + recipientSessionId: "intercom-manager-a", + recipientTargetSessionId: "opencode-ui-a", + recipientPrincipalId: "manager-a", + recipientBindingEpoch: 2, + recipientTransferGeneration: recipientTransferGeneration(0), + workerId: "worker-a", + workerGeneration: workerGeneration(4), + transitionId: "transition-a", + transitionVersion: transitionVersion(1), + assignmentId: "assignment-a", + turnId: "turn-a", + ingressMode: "lifecycle_message", + state: "reserved", +}; + +const insertion = envelope("insert_or_attach", { + deliveryClaimId: "claim-a", + claimGeneration: 1, + deliveryGroupId: "group-a", + membershipRevision: 1, + effectiveDeliveryIntent: "wake", + primaryNoticeId: "notice-a", + memberNoticeIds: members, + transitionIds: ["transition-a"], + recipientPrincipalId: "manager-a", + recipientBindingEpoch: 2, + workerGeneration: 4, + ingressMode: "lifecycle_message", + requestedAt: "2026-07-28T12:00:01.000Z", +}); + +const receipt = envelope("record_receipt", { + deliveryClaimId: "claim-a", + claimGeneration: 1, + deliveryGroupId: "group-a", + membershipRevision: 1, + recipientPrincipalId: "manager-a", + recipientBindingEpoch: 2, + workerGeneration: 4, + deliveryReceiptId: "receipt-a", + targetLedgerEntryId: "ledger-a", + deliveryMode: "lifecycle_message", + insertedAt: "2026-07-28T12:00:02.000Z", + deliveredAt: "2026-07-28T12:00:03.000Z", +}); + +const deliveredClaim: DeliveryClaimRecord = { + ...claim, + state: "delivered", + deliveryAttemptedAt: "2026-07-28T12:00:01.000Z", + targetLedgerEntryId: "ledger-a", + insertedAt: "2026-07-28T12:00:02.000Z", + deliveredAt: "2026-07-28T12:00:03.000Z", + deliveryReceiptId: "receipt-a", +}; + +function authority(overrides: Partial = {}): AuthenticatedOpenCodeNoticeAuthority { + return { + async reserveDelivery() { + return claim; + }, + async insertOrAttachWhileClaimCurrent(request, insert) { + const protectedReceipt = await insert(); + return atomicInsertionResult(request, protectedReceipt); + }, + async lookupTargetLedger(lookup): Promise { + return { + version: TARGET_LEDGER_RESULT_VERSION, + deliveryClaimId: claim.deliveryClaimId, + claimGeneration: claim.claimGeneration, + state: "absent", + checkedAt: (lookup.payload as Record).checkedAt as string, + }; + }, + async recordReceipt() { + return deliveredClaim; + }, + ...overrides, + }; +} + +function atomicInsertionResult( + request: OpenCodeNoticeAtomicInsertionRequest, + protectedReceipt = insertionReceipt(), + currentClaim: DeliveryClaimRecord = claim, +): OpenCodeNoticeAtomicInsertionResult { + return { + version: OPENCODE_NOTICE_ATOMIC_INSERTION_VERSION, + requestNonce: request.requestNonce, + status: "inserted", + claim: currentClaim, + receipt: protectedReceipt, + }; +} + +function insertionReceipt() { + return { + deliveryClaimId: claim.deliveryClaimId, + claimGeneration: claim.claimGeneration, + targetLedgerEntryId: "ledger-a", + insertedAt: "2026-07-28T12:00:02.000Z", + }; +} + +function ingressStorePhase(path: string): string | undefined { + return new DurableOpenCodeNoticeIngressStore(path).get(claim.deliveryClaimId)?.phase; +} + +type DurableFaultStage = "write" | "temp-fsync" | "rename" | "restrict" | "dir-fsync"; + +const PRE_RENAME_FAULTS: readonly DurableFaultStage[] = ["write", "temp-fsync", "rename"]; +const DURABLE_FAULT_STAGES: readonly DurableFaultStage[] = [...PRE_RENAME_FAULTS, "restrict", "dir-fsync"]; + +function faultOncePersist(stage: DurableFaultStage): (path: string, state: unknown) => void { + let faulted = false; + return (path, state) => { + if (faulted) { + writeDurableJson(path, state); + return; + } + let fsyncCalls = 0; + const operations: DurableJsonFileOperations = { + ...DURABLE_JSON_FILE_OPERATIONS, + writeFile(filePath, contents, options) { + if (stage === "write") { + faulted = true; + throw new Error("injected durable write fault"); + } + DURABLE_JSON_FILE_OPERATIONS.writeFile(filePath, contents, options); + }, + fsync(fileDescriptor) { + fsyncCalls += 1; + if (stage === "temp-fsync" && fsyncCalls === 1) { + faulted = true; + throw new Error("injected durable temp-fsync fault"); + } + if (stage === "dir-fsync" && fsyncCalls === 2) { + faulted = true; + throw new Error("injected durable dir-fsync fault"); + } + DURABLE_JSON_FILE_OPERATIONS.fsync(fileDescriptor); + }, + rename(from, to) { + if (stage === "rename") { + faulted = true; + throw new Error("injected durable rename fault"); + } + DURABLE_JSON_FILE_OPERATIONS.rename(from, to); + }, + restrict(filePath) { + if (stage === "restrict") { + faulted = true; + throw new Error("injected durable restrict fault"); + } + DURABLE_JSON_FILE_OPERATIONS.restrict(filePath); + }, + }; + writeDurableJson(path, state, operations); + }; +} + +function prepareIngressPhase(path: string, phase: "empty" | "reserved" | "inserting" | "inserted" | "receipting"): void { + if (phase === "empty") { + writeDurableJson(path, { version: 1, records: {} }); + return; + } + const store = new DurableOpenCodeNoticeIngressStore(path); + store.reserve(reserve, claim); + if (phase === "reserved") return; + store.beginInsertion(insertion); + if (phase === "inserting") return; + store.markInserted(claim.deliveryClaimId, insertionReceipt()); + if (phase === "inserted") return; + store.beginReceipt(receipt); +} + +test("OpenCode ingress durably reserves before prompt API and receipts the exact claim", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-ingress-")); + try { + const path = join(root, "notices.json"); + const order: string[] = []; + const ingress = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority({ + async reserveDelivery() { + order.push("authority-reserved"); + return claim; + }, + async insertOrAttachWhileClaimCurrent(request, insert) { + order.push("authority-atomic"); + const protectedReceipt = await insert(); + return atomicInsertionResult(request, protectedReceipt); + }, + async recordReceipt() { + order.push(`receipt-after-${new DurableOpenCodeNoticeIngressStore(path).get(claim.deliveryClaimId)?.phase}`); + return deliveredClaim; + }, + })); + + await ingress.reserveBeforePrompt(reserve); + assert.equal(new DurableOpenCodeNoticeIngressStore(path).get(claim.deliveryClaimId)?.phase, "reserved"); + const inserted = await ingress.insertOrAttach(insertion, async () => { + order.push(`prompt-after-${new DurableOpenCodeNoticeIngressStore(path).get(claim.deliveryClaimId)?.phase}`); + return insertionReceipt(); + }); + assert.equal(inserted.phase, "inserted"); + const delivered = await ingress.recordReceipt(receipt); + assert.equal(delivered.phase, "delivered"); + assert.deepEqual(order, ["authority-reserved", "authority-atomic", "prompt-after-inserting", "receipt-after-receipting"]); + assert.deepEqual(new DurableOpenCodeNoticeIngressStore(path).pending(), []); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("every notice ingress transition reconciles durable faults before same-process retry or reload", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-transition-faults-")); + try { + const transitions = [ + { + name: "reserve", + prepare: "empty", + prior: undefined, + next: "reserved", + mutate: (store: DurableOpenCodeNoticeIngressStore) => store.reserve(reserve, claim), + }, + { + name: "beginInsertion", + prepare: "reserved", + prior: "reserved", + next: "inserting", + mutate: (store: DurableOpenCodeNoticeIngressStore) => store.beginInsertion(insertion), + }, + { + name: "markInserted", + prepare: "inserting", + prior: "inserting", + next: "inserted", + mutate: (store: DurableOpenCodeNoticeIngressStore) => store.markInserted(claim.deliveryClaimId, insertionReceipt()), + }, + { + name: "beginReceipt", + prepare: "inserted", + prior: "inserted", + next: "receipting", + mutate: (store: DurableOpenCodeNoticeIngressStore) => store.beginReceipt(receipt), + }, + { + name: "markDelivered", + prepare: "receipting", + prior: "receipting", + next: "delivered", + mutate: (store: DurableOpenCodeNoticeIngressStore) => store.markDelivered(claim.deliveryClaimId, deliveredClaim), + }, + ] as const; + + for (const transition of transitions) { + for (const stage of DURABLE_FAULT_STAGES) { + const path = join(root, `${transition.name}-${stage}.json`); + prepareIngressPhase(path, transition.prepare); + const store = new DurableOpenCodeNoticeIngressStore(path, faultOncePersist(stage)); + assert.throws( + () => transition.mutate(store), + new RegExp(`injected durable ${stage} fault`), + `${transition.name}/${stage} must surface the persistence exception`, + ); + + const reconciledPhase = PRE_RENAME_FAULTS.includes(stage) ? transition.prior : transition.next; + assert.equal( + store.get(claim.deliveryClaimId)?.phase, + reconciledPhase, + `${transition.name}/${stage} must publish only the exact reconciled disk state`, + ); + assert.equal( + new DurableOpenCodeNoticeIngressStore(path).get(claim.deliveryClaimId)?.phase, + reconciledPhase, + `${transition.name}/${stage} reload must agree with same-process reconciliation`, + ); + + const retried = transition.mutate(store); + assert.equal(retried.phase, transition.next, `${transition.name}/${stage} exact retry must reach the next phase`); + assert.deepEqual( + transition.mutate(store), + retried, + `${transition.name}/${stage} committed replay must remain exactly idempotent`, + ); + assert.equal( + new DurableOpenCodeNoticeIngressStore(path).get(claim.deliveryClaimId)?.phase, + transition.next, + `${transition.name}/${stage} retried phase must survive reload`, + ); + } + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("notice ingress never publishes the mutable state exposed to its persister", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-persist-alias-")); + try { + const path = join(root, "notices.json"); + let retained: { records: Record } | undefined; + const store = new DurableOpenCodeNoticeIngressStore(path, (target, state) => { + writeDurableJson(target, state); + retained = state; + state.records = { callbackMutation: state.records[claim.deliveryClaimId]! }; + }); + + store.reserve(reserve, claim); + assert.equal(store.get(claim.deliveryClaimId)?.phase, "reserved"); + assert.equal(store.pending().length, 1); + assert.equal(new DurableOpenCodeNoticeIngressStore(path).get(claim.deliveryClaimId)?.phase, "reserved"); + + retained!.records = {}; + assert.equal(store.get(claim.deliveryClaimId)?.phase, "reserved"); + assert.equal(store.pending().length, 1); + assert.equal(new DurableOpenCodeNoticeIngressStore(path).get(claim.deliveryClaimId)?.phase, "reserved"); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("notice ingress reconciles exceptions against pre-callback prior and staged snapshots", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-persist-snapshots-")); + try { + for (const durableTarget of ["prior", "staged"] as const) { + const path = join(root, `${durableTarget}.json`); + let call = 0; + let retainedPrior: { records: Record } | undefined; + let retainedStaged: { records: Record } | undefined; + const store = new DurableOpenCodeNoticeIngressStore(path, (target, state) => { + call += 1; + if (call === 1) { + writeDurableJson(target, state); + retainedPrior = state; + return; + } + if (durableTarget === "staged") writeDurableJson(target, state); + retainedStaged = state; + retainedPrior!.records = {}; + state.records = {}; + throw new Error(`injected ${durableTarget} exception after mutation`); + }); + + store.reserve(reserve, claim); + assert.throws( + () => store.beginInsertion(insertion), + new RegExp(`injected ${durableTarget} exception after mutation`), + ); + const expectedPhase = durableTarget === "staged" ? "inserting" : "reserved"; + assert.equal(store.get(claim.deliveryClaimId)?.phase, expectedPhase); + assert.equal(store.pending().length, 1); + assert.equal(new DurableOpenCodeNoticeIngressStore(path).get(claim.deliveryClaimId)?.phase, expectedPhase); + + retainedPrior!.records = {}; + retainedStaged!.records = {}; + assert.equal(store.get(claim.deliveryClaimId)?.phase, expectedPhase); + assert.equal(store.pending().length, 1); + assert.equal(new DurableOpenCodeNoticeIngressStore(path).get(claim.deliveryClaimId)?.phase, expectedPhase); + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("prompt and settlement callbacks never cross an uncommitted ingress phase", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-side-effect-faults-")); + try { + for (const stage of DURABLE_FAULT_STAGES) { + const insertionPath = join(root, `insertion-${stage}.json`); + prepareIngressPhase(insertionPath, "reserved"); + let promptCalls = 0; + const insertionIngress = new OpenCodeNoticeRecipientIngress( + new DurableOpenCodeNoticeIngressStore(insertionPath, faultOncePersist(stage)), + authority({ + async lookupTargetLedger(lookup): Promise { + const checkedAt = (lookup.payload as Record).checkedAt as string; + return { + version: TARGET_LEDGER_RESULT_VERSION, + deliveryClaimId: claim.deliveryClaimId, + claimGeneration: claim.claimGeneration, + state: "inserted", + checkedAt, + targetLedgerEntryId: "ledger-a", + insertedAt: checkedAt, + }; + }, + }), + ); + await assert.rejects( + () => insertionIngress.insertOrAttach(insertion, async () => { + promptCalls += 1; + return insertionReceipt(); + }), + new RegExp(`injected durable ${stage} fault`), + ); + assert.equal(promptCalls, 0, `${stage} must not prompt before inserting is committed`); + const inserted = await insertionIngress.insertOrAttach(insertion, async () => { + promptCalls += 1; + return insertionReceipt(); + }); + assert.equal(inserted.phase, "inserted"); + assert.equal(promptCalls, PRE_RENAME_FAULTS.includes(stage) ? 1 : 0); + + const receiptPath = join(root, `receipt-${stage}.json`); + prepareIngressPhase(receiptPath, "inserted"); + let settlementCalls = 0; + const receiptIngress = new OpenCodeNoticeRecipientIngress( + new DurableOpenCodeNoticeIngressStore(receiptPath, faultOncePersist(stage)), + authority({ + async recordReceipt() { + settlementCalls += 1; + return deliveredClaim; + }, + }), + ); + await assert.rejects(() => receiptIngress.recordReceipt(receipt), new RegExp(`injected durable ${stage} fault`)); + assert.equal(settlementCalls, 0, `${stage} must not settle before receipting is committed`); + assert.equal((await receiptIngress.recordReceipt(receipt)).phase, "delivered"); + assert.equal(settlementCalls, 1, `${stage} retry must settle exactly once after reconciliation`); + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("expired persisted reservations remain dormant without an atomic-authority call or prompt callback", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-expired-")); + try { + const path = join(root, "notices.json"); + const expiredClaim: DeliveryClaimRecord = { + ...claim, + expiresAt: new Date(Date.now() - 1_000).toISOString(), + }; + new DurableOpenCodeNoticeIngressStore(path).reserve(reserve, expiredClaim); + let authorityCalls = 0; + let promptCalls = 0; + const ingress = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority({ + async insertOrAttachWhileClaimCurrent() { + authorityCalls += 1; + throw new Error("expired claim reached atomic authority"); + }, + })); + await assert.rejects( + () => ingress.insertOrAttach(insertion, async () => { + promptCalls += 1; + return insertionReceipt(); + }), + (error: unknown) => error instanceof OpenCodeNoticeCurrentClaimUnavailableError + && error.code === "OPENCODE_NOTICE_CURRENT_CLAIM_UNAVAILABLE" + && error.retryable + && /wall-clock expired/.test(error.message), + ); + assert.equal(authorityCalls, 0); + assert.equal(promptCalls, 0); + assert.equal(ingressStorePhase(path), "reserved"); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("revoked, superseded, and expired atomic decisions never leave reserved or invoke the prompt", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-no-longer-current-")); + try { + for (const status of ["revoked", "superseded", "expired"] as const) { + const path = join(root, `${status}.json`); + new DurableOpenCodeNoticeIngressStore(path).reserve(reserve, claim); + let promptCalls = 0; + const ingress = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority({ + async insertOrAttachWhileClaimCurrent(request) { + return { + version: OPENCODE_NOTICE_ATOMIC_INSERTION_VERSION, + requestNonce: request.requestNonce, + status, + } satisfies OpenCodeNoticeAtomicInsertionResult; + }, + })); + await assert.rejects( + () => ingress.insertOrAttach(insertion, async () => { + promptCalls += 1; + return insertionReceipt(); + }), + new RegExp(`authenticated winning claim is ${status}`), + ); + assert.equal(promptCalls, 0); + assert.equal(ingressStorePhase(path), "reserved"); + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("retained atomic insertion callbacks close after every authority settlement", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-closed-callback-")); + try { + const outcomes = ["inserted", "revoked", "superseded", "expired", "rejected", "thenable"] as const; + for (const outcome of outcomes) { + const path = join(root, `${outcome}.json`); + new DurableOpenCodeNoticeIngressStore(path).reserve(reserve, claim); + let retained: OpenCodeNoticeProtectedInsertion | undefined; + let settledThenableCallback: Promise | undefined; + let promptCalls = 0; + const ingress = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority({ + insertOrAttachWhileClaimCurrent(request, insert): Promise { + retained = insert; + if (outcome === "rejected") return Promise.reject(new Error("authority rejected")); + const result = atomicInsertionResult(request); + if (outcome !== "inserted" && outcome !== "thenable") { + return Promise.resolve({ + version: OPENCODE_NOTICE_ATOMIC_INSERTION_VERSION, + requestNonce: request.requestNonce, + status: outcome, + } satisfies OpenCodeNoticeAtomicInsertionResult); + } + if (outcome === "thenable") { + return { + then(resolve: (value: unknown) => void) { + resolve(result); + settledThenableCallback = insert(); + }, + } as Promise; + } + return Promise.resolve(result); + }, + })); + + await assert.rejects( + () => ingress.insertOrAttach(insertion, async () => { + promptCalls += 1; + return insertionReceipt(); + }), + ); + if (settledThenableCallback) { + await assert.rejects( + settledThenableCallback, + (error: unknown) => error instanceof Error && error.message === "FENCING_CALLBACK_CLOSED", + ); + } + const retainedCallback = retained; + assert.ok(retainedCallback); + await assert.rejects( + retainedCallback, + (error: unknown) => error instanceof Error && error.message === "FENCING_CALLBACK_CLOSED", + ); + assert.equal(promptCalls, 0, `${outcome} retained callback must not invoke the prompt`); + assert.equal(ingressStorePhase(path), "reserved", `${outcome} retained callback must not begin insertion`); + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("atomic insertion callback can be invoked only once while its authority call is open", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-single-callback-")); + try { + const path = join(root, "notices.json"); + new DurableOpenCodeNoticeIngressStore(path).reserve(reserve, claim); + let retained: OpenCodeNoticeProtectedInsertion | undefined; + let duplicateError: unknown; + let promptCalls = 0; + const ingress = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority({ + async insertOrAttachWhileClaimCurrent(request, insert) { + retained = insert; + const protectedReceipt = await insert(); + try { + await insert(); + } catch (error) { + duplicateError = error; + } + return atomicInsertionResult(request, protectedReceipt); + }, + })); + + const inserted = await ingress.insertOrAttach(insertion, async () => { + promptCalls += 1; + return insertionReceipt(); + }); + assert.equal(inserted.phase, "inserted"); + assert.equal(promptCalls, 1); + assert.ok(duplicateError instanceof Error); + assert.equal(duplicateError.message, "FENCING_CALLBACK_CLOSED"); + const retainedCallback = retained; + assert.ok(retainedCallback); + await assert.rejects( + retainedCallback, + (error: unknown) => error instanceof Error && error.message === "FENCING_CALLBACK_CLOSED", + ); + assert.equal(promptCalls, 1); + assert.equal(ingressStorePhase(path), "inserted"); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("atomic insertion result keeps the complete current-claim proof exact", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-current-binding-")); + try { + const substitutions: Array<[string, Partial]> = [ + ["deliveryClaimId", { deliveryClaimId: "claim-superseding" }], + ["claimGeneration", { claimGeneration: deliveryClaimGeneration(2) }], + ["recipientSessionId", { recipientSessionId: "intercom-manager-substituted" }], + ["recipientBindingEpoch", { recipientBindingEpoch: 3 }], + ["recipientTransferGeneration", { recipientTransferGeneration: recipientTransferGeneration(1) }], + ["workerGeneration", { workerGeneration: workerGeneration(5) }], + ["expiresAt", { expiresAt: "2099-07-28T12:11:00.000Z" }], + ["memberNoticeIds", { memberNoticeIds: ["notice-a"] }], + ["transitionId", { transitionId: "transition-substituted" }], + ]; + for (const [field, substitution] of substitutions) { + const path = join(root, `${field}.json`); + new DurableOpenCodeNoticeIngressStore(path).reserve(reserve, claim); + let promptCalls = 0; + const ingress = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority({ + async insertOrAttachWhileClaimCurrent(request) { + return atomicInsertionResult(request, insertionReceipt(), { ...claim, ...substitution }); + }, + })); + await assert.rejects( + () => ingress.insertOrAttach(insertion, async () => { + promptCalls += 1; + return insertionReceipt(); + }), + /current-claim proof does not exactly match/, + ); + assert.equal(promptCalls, 0, `${field} substitution must not invoke the prompt`); + assert.equal(ingressStorePhase(path), "reserved", `${field} substitution must not begin insertion`); + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("callback-only insertion fails explicitly before side effects when atomic fencing is unavailable", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-current-unavailable-")); + try { + const path = join(root, "notices.json"); + new DurableOpenCodeNoticeIngressStore(path).reserve(reserve, claim); + const unavailableAuthority = { + ...authority(), + insertOrAttachWhileClaimCurrent: undefined, + } as AuthenticatedOpenCodeNoticeAuthority; + const recovered = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), unavailableAuthority); + let promptCalls = 0; + await assert.rejects( + () => recovered.insertOrAttach(insertion, async () => { + promptCalls += 1; + return insertionReceipt(); + }), + (error: unknown) => error instanceof OpenCodeNoticeInsertionFencingUnavailableError + && error.code === INSERTION_FENCING_UNAVAILABLE + && error.retryable + && /atomic current-claim\/deadline-bound insertion authority/.test(error.message), + ); + assert.equal(promptCalls, 0); + assert.equal(ingressStorePhase(path), "reserved"); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("a stalled atomic authority cannot start insertion at the claim deadline", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-stalled-current-")); + try { + const path = join(root, "notices.json"); + const expiresAt = "2026-07-28T12:00:02.000Z"; + const shortClaim: DeliveryClaimRecord = { ...claim, expiresAt }; + new DurableOpenCodeNoticeIngressStore(path).reserve(reserve, shortClaim); + let now = Date.parse("2026-07-28T12:00:01.500Z"); + const ingress = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority({ + async insertOrAttachWhileClaimCurrent(_request, insert) { + now = Date.parse(expiresAt); + return insert(); + }, + }), () => now); + let promptCalls = 0; + await assert.rejects( + () => ingress.insertOrAttach(insertion, async () => { + promptCalls += 1; + return insertionReceipt(); + }), + /wall-clock expired/, + ); + assert.equal(promptCalls, 0); + assert.equal(ingressStorePhase(path), "reserved"); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("late and forged atomic insertion receipts are never durably accepted", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-forged-receipts-")); + try { + const cases = [ + ["late", { ...insertionReceipt(), insertedAt: claim.expiresAt }, /not strictly before/], + ["claim-id", { ...insertionReceipt(), deliveryClaimId: "claim-forged" }, /deliveryClaimId does not match/], + ["generation", { ...insertionReceipt(), claimGeneration: deliveryClaimGeneration(2) }, /claimGeneration does not match/], + ] as const; + for (const [name, forgedReceipt, expected] of cases) { + const path = join(root, `${name}.json`); + new DurableOpenCodeNoticeIngressStore(path).reserve(reserve, claim); + let promptCalls = 0; + const ingress = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority({ + async insertOrAttachWhileClaimCurrent(request) { + return atomicInsertionResult(request, forgedReceipt); + }, + })); + await assert.rejects( + () => ingress.insertOrAttach(insertion, async () => { + promptCalls += 1; + return insertionReceipt(); + }), + expected, + ); + assert.equal(promptCalls, 0, `${name} forged authority response must not invoke the callback`); + assert.equal(ingressStorePhase(path), "reserved"); + } + + const recoveryPath = join(root, "late-recovery.json"); + const recoveryStore = new DurableOpenCodeNoticeIngressStore(recoveryPath); + recoveryStore.reserve(reserve, claim); + recoveryStore.beginInsertion(insertion); + assert.throws( + () => recoveryStore.markInserted(claim.deliveryClaimId, { + ...insertionReceipt(), + insertedAt: claim.expiresAt, + }), + /not strictly before/, + ); + assert.equal(recoveryStore.get(claim.deliveryClaimId)?.phase, "inserting"); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("crash after prompt API recovers from authenticated target ledger without replay", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-recover-")); + try { + const path = join(root, "notices.json"); + const first = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority()); + await first.reserveBeforePrompt(reserve); + await assert.rejects(() => first.insertOrAttach(insertion, async () => { + throw new Error("crash after API"); + }), /crash after API/); + assert.equal(new DurableOpenCodeNoticeIngressStore(path).get(claim.deliveryClaimId)?.phase, "inserting"); + + let reinjected = false; + let lookupOperation = ""; + const recovered = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority({ + async lookupTargetLedger(lookup): Promise { + lookupOperation = lookup.operation; + const checkedAt = (lookup.payload as Record).checkedAt as string; + return { + version: TARGET_LEDGER_RESULT_VERSION, + deliveryClaimId: claim.deliveryClaimId, + claimGeneration: claim.claimGeneration, + state: "inserted", + checkedAt, + targetLedgerEntryId: "ledger-a", + insertedAt: checkedAt, + }; + }, + })); + const result = await recovered.insertOrAttach(insertion, async () => { + reinjected = true; + return insertionReceipt(); + }); + assert.equal(lookupOperation, "lookup_target_ledger"); + assert.equal(reinjected, false); + assert.equal(result.phase, "inserted"); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("ambiguous or absent target ledger fails closed without authenticated drain/reissue authority", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-lookup-")); + try { + const path = join(root, "notices.json"); + const first = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority()); + await first.reserveBeforePrompt(reserve); + await assert.rejects(() => first.insertOrAttach(insertion, async () => { + throw new Error("api interrupted"); + }), /api interrupted/); + + let invoked = false; + const ambiguous = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority({ + async lookupTargetLedger(lookup): Promise { + return { + version: TARGET_LEDGER_RESULT_VERSION, + deliveryClaimId: claim.deliveryClaimId, + claimGeneration: claim.claimGeneration, + state: "ambiguous", + checkedAt: (lookup.payload as Record).checkedAt as string, + }; + }, + })); + await assert.rejects(() => ambiguous.insertOrAttach(insertion, async () => { + invoked = true; + return insertionReceipt(); + }), /refusing ambiguous OpenCode replay/); + assert.equal(invoked, false); + + const absent = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority()); + await assert.rejects(() => absent.insertOrAttach(insertion, async () => { + invoked = true; + return insertionReceipt(); + }), /target-drained proof.*generation-incremented.*unavailable/); + assert.equal(invoked, false); + assert.equal(new DurableOpenCodeNoticeIngressStore(path).get(claim.deliveryClaimId)?.phase, "inserting"); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("crash recovery rejects stale claim-generation ledger evidence without reinjection", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-stale-ledger-")); + try { + const path = join(root, "notices.json"); + const first = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority()); + await first.reserveBeforePrompt(reserve); + await assert.rejects(() => first.insertOrAttach(insertion, async () => { + throw new Error("api interrupted"); + }), /api interrupted/); + + let reinjected = false; + const stale = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority({ + async lookupTargetLedger(lookup): Promise { + return { + version: TARGET_LEDGER_RESULT_VERSION, + deliveryClaimId: claim.deliveryClaimId, + claimGeneration: deliveryClaimGeneration(2), + state: "absent", + checkedAt: (lookup.payload as Record).checkedAt as string, + }; + }, + })); + await assert.rejects(() => stale.insertOrAttach(insertion, async () => { + reinjected = true; + return insertionReceipt(); + }), /claimGeneration does not match the winning claim/); + assert.equal(reinjected, false); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("receipt crash remains receipting and idempotently settles after restart", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-receipt-")); + try { + const path = join(root, "notices.json"); + const ingress = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority({ + async recordReceipt() { + throw new Error("receipt response lost"); + }, + })); + await ingress.reserveBeforePrompt(reserve); + await ingress.insertOrAttach(insertion, async () => insertionReceipt()); + await assert.rejects(() => ingress.recordReceipt(receipt), /receipt response lost/); + assert.equal(new DurableOpenCodeNoticeIngressStore(path).get(claim.deliveryClaimId)?.phase, "receipting"); + + const recovered = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority()); + assert.equal((await recovered.recordReceipt(receipt)).phase, "delivered"); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("inserted and receipt replays require the exact persisted envelopes", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-replay-")); + try { + const path = join(root, "notices.json"); + const ingress = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(path), authority()); + await ingress.reserveBeforePrompt(reserve); + await ingress.insertOrAttach(insertion, async () => insertionReceipt()); + await assert.rejects( + () => ingress.insertOrAttach({ ...insertion, requestId: "substituted-request" }, async () => insertionReceipt()), + /Conflicting OpenCode notice insertion replay/, + ); + await ingress.recordReceipt(receipt); + await assert.rejects( + () => ingress.recordReceipt({ ...receipt, idempotencyKey: "substituted-idempotency" }), + /Conflicting OpenCode notice receipt replay/, + ); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("state load revalidates cross-record claim and transition correlation", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-cross-record-")); + try { + const path = join(root, "notices.json"); + const store = new DurableOpenCodeNoticeIngressStore(path); + store.reserve(reserve, claim); + store.beginInsertion(insertion); + const state = JSON.parse(await readFile(path, "utf8")); + state.records[claim.deliveryClaimId].claim.transitionId = "substituted-transition"; + await writeFile(path, JSON.stringify(state)); + assert.throws( + () => new DurableOpenCodeNoticeIngressStore(path), + /transitionIds do not exactly match the winning claim transition/, + ); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("arbitrary delivery claim IDs survive exact-own durable JSON round trips", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-record-keys-")); + try { + const path = join(root, "notices.json"); + const specialKeys = ["__proto__", "constructor", "toString"] as const; + const store = new DurableOpenCodeNoticeIngressStore(path); + for (const [index, deliveryClaimId] of specialKeys.entries()) { + const deliveryGroupId = `special-group-${index}`; + const transitionId = `special-transition-${index}`; + store.reserve( + envelope("reserve_delivery", { + ...(reserve.payload as object), + deliveryGroupId, + }), + { + ...claim, + deliveryClaimId, + deliveryGroupId, + transitionId, + }, + ); + } + + const persisted = JSON.parse(await readFile(path, "utf8")); + for (const deliveryClaimId of specialKeys) { + assert.equal(Object.hasOwn(persisted.records, deliveryClaimId), true); + assert.equal(store.get(deliveryClaimId)?.claim.deliveryClaimId, deliveryClaimId); + } + assert.equal(store.get("valueOf"), undefined, "inherited names must not resolve as records"); + + const reloaded = new DurableOpenCodeNoticeIngressStore(path); + for (const [index, deliveryClaimId] of specialKeys.entries()) { + const deliveryGroupId = `special-group-${index}`; + const transitionId = `special-transition-${index}`; + const begun = reloaded.beginInsertion(envelope("insert_or_attach", { + ...(insertion.payload as object), + deliveryClaimId, + deliveryGroupId, + transitionIds: [transitionId], + })); + assert.equal(begun.claim.deliveryClaimId, deliveryClaimId); + assert.equal(begun.phase, "inserting"); + } + const roundTripped = new DurableOpenCodeNoticeIngressStore(path); + for (const deliveryClaimId of specialKeys) { + assert.equal(roundTripped.get(deliveryClaimId)?.phase, "inserting"); + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("notice ingress poisons every public operation when reconciliation finds a missing, corrupt, or foreign target", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-poison-fallbacks-")); + try { + for (const fallback of ["missing", "corrupt", "foreign"] as const) { + const path = join(root, `${fallback}.json`); + prepareIngressPhase(path, "reserved"); + let persistCalls = 0; + const store = new DurableOpenCodeNoticeIngressStore(path, (target) => { + persistCalls += 1; + if (fallback === "missing") { + rmSync(target, { force: true }); + } else if (fallback === "corrupt") { + writeFileSync(target, "{corrupt", "utf8"); + } else { + writeDurableJson(target, { version: 1, records: {} }); + } + throw new Error(`injected ${fallback} target persist fault`); + }); + + assert.throws( + () => store.beginInsertion(insertion), + new RegExp(`injected ${fallback} target persist fault`), + ); + const targetAfterPoison = existsSync(path) ? await readFile(path, "utf8") : undefined; + let poisonError: unknown; + assert.throws( + () => store.get(claim.deliveryClaimId), + (error: unknown) => { + poisonError = error; + return error instanceof Error + && /Durable OpenCode notice ingress store is unavailable after commit reconciliation failed/.test(error.message); + }, + ); + const publicOperations: Array<() => unknown> = [ + () => store.get(claim.deliveryClaimId), + () => store.reserve(reserve, claim), + () => store.beginInsertion(insertion), + () => store.markInserted(claim.deliveryClaimId, insertionReceipt()), + () => store.beginReceipt(receipt), + () => store.markDelivered(claim.deliveryClaimId, deliveredClaim), + () => store.pending(), + ]; + for (const operation of publicOperations) { + assert.throws( + operation, + (error: unknown) => error === poisonError, + `${fallback} reconciliation must fail closed with one deterministic poison error`, + ); + } + assert.equal(persistCalls, 1, `${fallback} poison must prevent further persistence attempts`); + assert.equal( + existsSync(path) ? await readFile(path, "utf8") : undefined, + targetAfterPoison, + `${fallback} target must remain untouched after poison`, + ); + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("notice state paths hash the exact session ID instead of lossy sanitization", () => { + assert.notEqual( + getOpenCodeNoticeIngressStatePath("Manager/A", "/tmp/intercom"), + getOpenCodeNoticeIngressStatePath("Manager-A", "/tmp/intercom"), + ); + assert.equal(getOpenCodeNoticeIngressStatePath("Manager/A", "/tmp/intercom"), getOpenCodeNoticeIngressStatePath("Manager/A", "/tmp/intercom")); +}); + +test("OpenCode ingress refuses insertion without a winning claim or authenticated authority", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-unreserved-")); + try { + assert.throws( + () => new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(join(root, "missing-authority.json")), undefined as never), + /Authenticated notice authority API is required/, + ); + const ingress = new OpenCodeNoticeRecipientIngress(new DurableOpenCodeNoticeIngressStore(join(root, "notices.json")), authority()); + let invoked = false; + await assert.rejects(() => ingress.insertOrAttach(insertion, async () => { + invoked = true; + return insertionReceipt(); + }), /no durable winning reservation/); + assert.equal(invoked, false); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("production notice ingress is explicitly unavailable without the protected authority API", () => { + assert.throws(createProductionOpenCodeNoticeRecipientIngress, (error: unknown) => { + return error instanceof Error + && "code" in error + && error.code === "OPENCODE_NOTICE_AUTHORITY_UNAVAILABLE"; + }); +}); + +test("OpenCode notice ingress state fails closed on unknown versions", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-version-")); + try { + const path = join(root, "notices.json"); + await writeFile(path, JSON.stringify({ version: 2, records: {} })); + assert.throws(() => new DurableOpenCodeNoticeIngressStore(path), /Unsupported OpenCode notice ingress state version/); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("ingress contracts reject accessor, proxy, sparse, and inexact boundaries", async () => { + const root = await mkdtemp(join(tmpdir(), "opencode-notice-boundaries-")); + try { + const store = new DurableOpenCodeNoticeIngressStore(join(root, "notices.json")); + let getterCalls = 0; + const accessorEnvelope = { ...reserve } as Record; + Object.defineProperty(accessorEnvelope, "operation", { + enumerable: true, + get() { + getterCalls += 1; + return "reserve_delivery"; + }, + }); + assert.throws(() => store.reserve(accessorEnvelope, claim), /enumerable data property/); + assert.equal(getterCalls, 0); + + const sparseMembers = ["notice-a", "notice-b"]; + delete sparseMembers[1]; + assert.throws( + () => store.reserve({ ...reserve, payload: { ...(reserve.payload as object), memberNoticeIds: sparseMembers } }, claim), + /sparse array holes/, + ); + assert.throws( + () => store.reserve({ ...reserve, unsupported: true } as never, claim), + /unsupported.*not supported|unsupported/, + ); + assert.throws( + () => store.reserve(new Proxy({}, { ownKeys() { throw new Error("proxy rejected"); } }), claim), + /proxy rejected/, + ); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/opencode/notice-ingress.ts b/opencode/notice-ingress.ts new file mode 100644 index 0000000..61d3b85 --- /dev/null +++ b/opencode/notice-ingress.ts @@ -0,0 +1,830 @@ +import { createHash, randomUUID } from "node:crypto"; +import { existsSync, readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { + parseDeliveryClaimRecord, + parseNoticeRecipientIngressEnvelope, + parseTargetLedgerLookupResult, + type DeliveryClaimRecord, + type NoticeRecipientIngressEnvelope, + type TargetLedgerLookupResult, +} from "@dataforxyz/agent-intercom-core/boss"; +import { + canonicalJson, + assertExactKeys, + assertRecord, + type DeliveryClaimGeneration, +} from "@dataforxyz/agent-intercom-core/canonical"; +import { ensureIntercomRuntimeDir, getIntercomDirPath } from "../broker/paths.ts"; +import { writeDurableJson } from "../durable-json.ts"; + +export type OpenCodeNoticeIngressPhase = "reserved" | "inserting" | "inserted" | "receipting" | "delivered"; + +export interface OpenCodeNoticeIngressRecord { + reserve: NoticeRecipientIngressEnvelope; + claim: DeliveryClaimRecord; + phase: OpenCodeNoticeIngressPhase; + insertion?: NoticeRecipientIngressEnvelope; + targetLedgerEntryId?: string; + insertedAt?: string; + receipt?: NoticeRecipientIngressEnvelope; + deliveredClaim?: DeliveryClaimRecord; +} + +interface OpenCodeNoticeIngressState { + version: 1; + records: Record; +} + +export interface OpenCodeNoticeInsertionReceipt { + deliveryClaimId: string; + claimGeneration: DeliveryClaimGeneration; + targetLedgerEntryId: string; + insertedAt: string; +} + +export const OPENCODE_NOTICE_ATOMIC_INSERTION_VERSION = "opencode.notice-atomic-insertion.v1" as const; +/** @deprecated Preliminary current-claim evidence does not fence insertion. */ +export const OPENCODE_NOTICE_CURRENT_CLAIM_EVIDENCE_VERSION = "opencode.notice-current-claim-evidence.v1" as const; + +export interface OpenCodeNoticeAtomicInsertionRequest { + version: typeof OPENCODE_NOTICE_ATOMIC_INSERTION_VERSION; + requestNonce: string; + requestedAt: string; + claim: DeliveryClaimRecord; + insertion: NoticeRecipientIngressEnvelope; +} + +export interface OpenCodeNoticeAtomicInsertionResult { + version: typeof OPENCODE_NOTICE_ATOMIC_INSERTION_VERSION; + requestNonce: string; + status: "inserted" | "revoked" | "superseded" | "expired"; + claim?: DeliveryClaimRecord; + receipt?: OpenCodeNoticeInsertionReceipt; +} + +export type OpenCodeNoticeProtectedInsertion = () => Promise; + +/** + * This boundary must be backed by the authenticated Orc/Controller authority + * channel. The ordinary Intercom broker is not an implementation of it. + */ +export interface AuthenticatedOpenCodeNoticeAuthority { + reserveDelivery(envelope: NoticeRecipientIngressEnvelope): Promise; + /** + * This method may only be exposed by an authenticated authority that keeps + * the exact claim current and its deadline open atomically across the + * protected insertion callback and the target-ledger commit. A preliminary + * lookup, an AbortSignal, or a caller-supplied timestamp is not an + * implementation of this operation. + */ + insertOrAttachWhileClaimCurrent?( + request: OpenCodeNoticeAtomicInsertionRequest, + insertion: OpenCodeNoticeProtectedInsertion, + ): Promise; + lookupTargetLedger(envelope: NoticeRecipientIngressEnvelope): Promise; + recordReceipt(envelope: NoticeRecipientIngressEnvelope): Promise; +} + +export const OPENCODE_NOTICE_AUTHORITY_UNAVAILABLE = "OPENCODE_NOTICE_AUTHORITY_UNAVAILABLE" as const; +export const OPENCODE_NOTICE_CURRENT_CLAIM_UNAVAILABLE = "OPENCODE_NOTICE_CURRENT_CLAIM_UNAVAILABLE" as const; +export const INSERTION_FENCING_UNAVAILABLE = "INSERTION_FENCING_UNAVAILABLE" as const; + +export class OpenCodeNoticeAuthorityUnavailableError extends Error { + readonly code = OPENCODE_NOTICE_AUTHORITY_UNAVAILABLE; + + constructor() { + super("OpenCode Boss notice ingress is unavailable until an authenticated Orc/Controller authority client and typed notice-to-prompt entrypoint are provided"); + this.name = "OpenCodeNoticeAuthorityUnavailableError"; + } +} + +export class OpenCodeNoticeCurrentClaimUnavailableError extends Error { + readonly code = OPENCODE_NOTICE_CURRENT_CLAIM_UNAVAILABLE; + readonly retryable = true; + + constructor(reason: string, options?: ErrorOptions) { + super(`OpenCode Boss notice insertion requires a new authenticated reservation before retry: ${reason}`, options); + this.name = "OpenCodeNoticeCurrentClaimUnavailableError"; + } +} + +export class OpenCodeNoticeInsertionFencingUnavailableError extends Error { + readonly code = INSERTION_FENCING_UNAVAILABLE; + readonly retryable = true; + + constructor() { + super("OpenCode Boss notice insertion is unavailable without a protected authenticated atomic current-claim/deadline-bound insertion authority"); + this.name = "OpenCodeNoticeInsertionFencingUnavailableError"; + } +} + +/** + * Current production boundary. Ordinary Message delivery must never be used as + * a substitute authority or as locally manufactured target-ledger evidence. + */ +export function createProductionOpenCodeNoticeRecipientIngress(): never { + throw new OpenCodeNoticeAuthorityUnavailableError(); +} + +function emptyState(): OpenCodeNoticeIngressState { + return { version: 1, records: Object.create(null) as Record }; +} + +function collisionResistantName(value: string): string { + return createHash("sha256").update(value).digest("hex"); +} + +export function getOpenCodeNoticeIngressStatePath(sessionId: string, intercomDir = getIntercomDirPath()): string { + return join(intercomDir, `opencode-notice-ingress-${collisionResistantName(sessionId)}.json`); +} + +function ownRecord(value: unknown, path: string): Record { + assertRecord(value, path); + return value; +} + +function exactKeys(value: Record, required: string[], optional: string[], path: string): void { + assertExactKeys(value, required, optional, path); +} + +function payload(envelope: NoticeRecipientIngressEnvelope): Record { + return envelope.payload as Record; +} + +function timestamp(value: unknown, path: string): string { + if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) throw new Error(`${path} must be a timestamp`); + return value; +} + +function nonEmptyString(value: unknown, path: string): string { + if (typeof value !== "string" || value.length === 0) throw new Error(`${path} must be a non-empty string`); + return value; +} + +function assertSame(actual: unknown, expected: unknown, field: string, context: string): void { + if (actual !== expected) throw new Error(`${context} ${field} does not match the winning claim`); +} + +function assertReservationMatches(envelope: NoticeRecipientIngressEnvelope, claim: DeliveryClaimRecord): void { + if (envelope.operation !== "reserve_delivery") throw new Error("Expected reserve_delivery before prompt injection"); + if (claim.state !== "reserved") throw new Error("Notice delivery claim must be reserved before prompt injection"); + if (claim.recipientContext !== "opencode") throw new Error("Notice delivery claim is not for OpenCode"); + const request = payload(envelope); + const comparisons: Array<[unknown, unknown, string]> = [ + [claim.deliveryGroupId, request.deliveryGroupId, "deliveryGroupId"], + [claim.membershipRevision, request.membershipRevision, "membershipRevision"], + [claim.effectiveDeliveryIntent, request.effectiveDeliveryIntent, "effectiveDeliveryIntent"], + [claim.primaryNoticeId, request.primaryNoticeId, "primaryNoticeId"], + [claim.recipientContext, request.recipientContext, "recipientContext"], + [claim.recipientSessionId, request.recipientSessionId, "recipientSessionId"], + [claim.recipientTargetSessionId, request.recipientTargetSessionId, "recipientTargetSessionId"], + [claim.recipientPrincipalId, request.recipientPrincipalId, "recipientPrincipalId"], + [claim.recipientBindingEpoch, request.recipientBindingEpoch, "recipientBindingEpoch"], + [claim.recipientTransferGeneration, request.recipientTransferGeneration, "recipientTransferGeneration"], + [claim.workerGeneration, request.workerGeneration, "workerGeneration"], + ]; + for (const [actual, expected, field] of comparisons) assertSame(actual, expected, field, "Reserved notice claim"); + if (canonicalJson(claim.memberNoticeIds) !== canonicalJson(request.memberNoticeIds)) { + throw new Error("Reserved notice claim memberNoticeIds do not match the ingress request"); + } + if (Date.parse(timestamp(request.requestedAt, "$.payload.requestedAt")) >= Date.parse(claim.expiresAt)) { + throw new Error("Notice reservation was requested after the winning claim expired"); + } +} + +function assertWallClockFresh(claim: DeliveryClaimRecord, now: number): void { + if (Date.parse(claim.expiresAt) <= now) { + throw new OpenCodeNoticeCurrentClaimUnavailableError("the winning delivery claim is wall-clock expired"); + } +} + +function atomicInsertionRequest( + record: OpenCodeNoticeIngressRecord, + insertion: NoticeRecipientIngressEnvelope, + now: number, +): OpenCodeNoticeAtomicInsertionRequest { + return { + version: OPENCODE_NOTICE_ATOMIC_INSERTION_VERSION, + requestNonce: randomUUID(), + requestedAt: new Date(now).toISOString(), + claim: structuredClone(record.claim), + insertion: structuredClone(insertion), + }; +} + +function parseInsertionReceipt(value: unknown, path: string): OpenCodeNoticeInsertionReceipt { + const receipt = ownRecord(value, path); + exactKeys(receipt, ["deliveryClaimId", "claimGeneration", "targetLedgerEntryId", "insertedAt"], [], path); + const claimGeneration = receipt.claimGeneration; + if (!Number.isSafeInteger(claimGeneration) || (claimGeneration as number) < 0) { + throw new Error(`${path}.claimGeneration must be a non-negative safe integer`); + } + return { + deliveryClaimId: nonEmptyString(receipt.deliveryClaimId, `${path}.deliveryClaimId`), + claimGeneration: claimGeneration as DeliveryClaimGeneration, + targetLedgerEntryId: nonEmptyString(receipt.targetLedgerEntryId, `${path}.targetLedgerEntryId`), + insertedAt: timestamp(receipt.insertedAt, `${path}.insertedAt`), + }; +} + +function parseAtomicInsertionResult(value: unknown): OpenCodeNoticeAtomicInsertionResult { + const result = ownRecord(value, "$atomicInsertionResult"); + exactKeys( + result, + ["version", "requestNonce", "status"], + ["claim", "receipt"], + "$atomicInsertionResult", + ); + if (result.version !== OPENCODE_NOTICE_ATOMIC_INSERTION_VERSION) { + throw new Error("Unsupported OpenCode atomic insertion result version"); + } + const status = result.status; + if (status !== "inserted" && status !== "revoked" && status !== "superseded" && status !== "expired") { + throw new Error("$atomicInsertionResult.status is invalid"); + } + const claim = result.claim === undefined ? undefined : parseDeliveryClaimRecord(result.claim); + const receipt = result.receipt === undefined + ? undefined + : parseInsertionReceipt(result.receipt, "$atomicInsertionResult.receipt"); + if (status === "inserted" ? claim === undefined || receipt === undefined : claim !== undefined || receipt !== undefined) { + throw new Error("$atomicInsertionResult claim and receipt are present exactly for inserted status"); + } + return { + version: OPENCODE_NOTICE_ATOMIC_INSERTION_VERSION, + requestNonce: nonEmptyString(result.requestNonce, "$atomicInsertionResult.requestNonce"), + status, + ...(claim === undefined ? {} : { claim }), + ...(receipt === undefined ? {} : { receipt }), + }; +} + +function assertAtomicInsertionResultMatches( + record: OpenCodeNoticeIngressRecord, + request: OpenCodeNoticeAtomicInsertionRequest, + result: OpenCodeNoticeAtomicInsertionResult, +): asserts result is OpenCodeNoticeAtomicInsertionResult & { + status: "inserted"; + claim: DeliveryClaimRecord; + receipt: OpenCodeNoticeInsertionReceipt; +} { + if (result.requestNonce !== request.requestNonce) { + throw new OpenCodeNoticeCurrentClaimUnavailableError("atomic insertion result does not bind the fresh request nonce"); + } + if (result.status !== "inserted" || !result.claim || !result.receipt) { + throw new OpenCodeNoticeCurrentClaimUnavailableError(`the authenticated winning claim is ${result.status}`); + } + if (canonicalJson(result.claim) !== canonicalJson(record.claim)) { + throw new OpenCodeNoticeCurrentClaimUnavailableError("atomic insertion current-claim proof does not exactly match the durable winner"); + } + assertInsertionReceiptMatches(record, result.receipt); +} + +function assertInsertionMatches(record: OpenCodeNoticeIngressRecord, envelope: NoticeRecipientIngressEnvelope): void { + if (envelope.operation !== "insert_or_attach") throw new Error("Expected insert_or_attach"); + const request = payload(envelope); + const claim = record.claim; + const comparisons: Array<[unknown, unknown, string]> = [ + [claim.deliveryClaimId, request.deliveryClaimId, "deliveryClaimId"], + [claim.claimGeneration, request.claimGeneration, "claimGeneration"], + [claim.deliveryGroupId, request.deliveryGroupId, "deliveryGroupId"], + [claim.membershipRevision, request.membershipRevision, "membershipRevision"], + [claim.effectiveDeliveryIntent, request.effectiveDeliveryIntent, "effectiveDeliveryIntent"], + [claim.primaryNoticeId, request.primaryNoticeId, "primaryNoticeId"], + [claim.recipientPrincipalId, request.recipientPrincipalId, "recipientPrincipalId"], + [claim.recipientBindingEpoch, request.recipientBindingEpoch, "recipientBindingEpoch"], + [claim.workerGeneration, request.workerGeneration, "workerGeneration"], + [claim.ingressMode, request.ingressMode, "ingressMode"], + ]; + for (const [actual, expected, field] of comparisons) assertSame(actual, expected, field, "Notice insertion"); + if (canonicalJson(claim.memberNoticeIds) !== canonicalJson(request.memberNoticeIds)) { + throw new Error("Notice insertion memberNoticeIds do not match the winning claim"); + } + if (canonicalJson(request.transitionIds) !== canonicalJson([claim.transitionId])) { + throw new Error("Notice insertion transitionIds do not exactly match the winning claim transition"); + } + const requestedAt = timestamp(request.requestedAt, "$.payload.requestedAt"); + if (Date.parse(requestedAt) >= Date.parse(claim.expiresAt)) throw new Error("Notice insertion was requested after claim expiry"); + const reserveRequestedAt = timestamp(payload(record.reserve).requestedAt, "$.reserve.payload.requestedAt"); + if (Date.parse(requestedAt) < Date.parse(reserveRequestedAt)) throw new Error("Notice insertion predates its reservation"); +} + +function assertLookupMatches( + record: OpenCodeNoticeIngressRecord, + request: NoticeRecipientIngressEnvelope, + lookup: TargetLedgerLookupResult, +): void { + assertSame(record.claim.deliveryClaimId, lookup.deliveryClaimId, "deliveryClaimId", "Target ledger lookup"); + assertSame(record.claim.claimGeneration, lookup.claimGeneration, "claimGeneration", "Target ledger lookup"); + const requestedAt = timestamp(payload(request).checkedAt, "$.lookup.payload.checkedAt"); + if (Date.parse(lookup.checkedAt) < Date.parse(requestedAt)) { + throw new Error("Target ledger result predates its authenticated lookup request"); + } +} + +function assertInsertionReceiptMatches(record: OpenCodeNoticeIngressRecord, receipt: OpenCodeNoticeInsertionReceipt): void { + assertSame(record.claim.deliveryClaimId, receipt.deliveryClaimId, "deliveryClaimId", "OpenCode insertion receipt"); + assertSame(record.claim.claimGeneration, receipt.claimGeneration, "claimGeneration", "OpenCode insertion receipt"); + if (!receipt.targetLedgerEntryId) throw new Error("OpenCode insertion receipt targetLedgerEntryId is required"); + const insertedAt = timestamp(receipt.insertedAt, "$.insertedAt"); + const requestedAt = timestamp(payload(record.insertion!).requestedAt, "$.insertion.payload.requestedAt"); + if (Date.parse(insertedAt) < Date.parse(requestedAt)) throw new Error("OpenCode insertion receipt predates the insertion attempt"); + if (Date.parse(insertedAt) >= Date.parse(record.claim.expiresAt)) { + throw new OpenCodeNoticeCurrentClaimUnavailableError("the insertion receipt is not strictly before the winning claim expiry"); + } +} + +function assertReceiptMatches(record: OpenCodeNoticeIngressRecord, envelope: NoticeRecipientIngressEnvelope): void { + if (envelope.operation !== "record_receipt") throw new Error("Expected record_receipt"); + if (!record.insertion || !record.targetLedgerEntryId || !record.insertedAt) { + throw new Error("Cannot receipt a notice without durable target-ledger insertion evidence"); + } + const request = payload(envelope); + const claim = record.claim; + const comparisons: Array<[unknown, unknown, string]> = [ + [claim.deliveryClaimId, request.deliveryClaimId, "deliveryClaimId"], + [claim.claimGeneration, request.claimGeneration, "claimGeneration"], + [claim.deliveryGroupId, request.deliveryGroupId, "deliveryGroupId"], + [claim.membershipRevision, request.membershipRevision, "membershipRevision"], + [claim.recipientPrincipalId, request.recipientPrincipalId, "recipientPrincipalId"], + [claim.recipientBindingEpoch, request.recipientBindingEpoch, "recipientBindingEpoch"], + [claim.workerGeneration, request.workerGeneration, "workerGeneration"], + [claim.ingressMode, request.deliveryMode, "deliveryMode"], + [record.targetLedgerEntryId, request.targetLedgerEntryId, "targetLedgerEntryId"], + [record.insertedAt, request.insertedAt, "insertedAt"], + [payload(record.insertion).resultMessageId, request.resultMessageId, "resultMessageId"], + ]; + for (const [actual, expected, field] of comparisons) assertSame(actual, expected, field, "Notice receipt"); +} + +function assertDeliveredClaimMatches(record: OpenCodeNoticeIngressRecord, delivered: DeliveryClaimRecord): void { + if (delivered.state !== "delivered") throw new Error("Receipt authority did not return a delivered claim"); + const receipt = payload(record.receipt!); + const immutableFields: Array = [ + "deliveryClaimId", + "claimGeneration", + "deliveryGroupId", + "membershipRevision", + "effectiveDeliveryIntent", + "primaryNoticeId", + "recipientContext", + "recipientSessionId", + "recipientTargetSessionId", + "recipientPrincipalId", + "recipientBindingEpoch", + "recipientTransferGeneration", + "workerId", + "workerGeneration", + "transitionId", + "transitionVersion", + "assignmentId", + "turnId", + "watchdogGeneration", + "ingressMode", + ]; + for (const field of immutableFields) assertSame(record.claim[field], delivered[field], String(field), "Delivered claim"); + if (canonicalJson(record.claim.memberNoticeIds) !== canonicalJson(delivered.memberNoticeIds)) { + throw new Error("Delivered claim memberNoticeIds changed after reservation"); + } + const settlement: Array<[unknown, unknown, string]> = [ + [record.targetLedgerEntryId, delivered.targetLedgerEntryId, "targetLedgerEntryId"], + [record.insertedAt, delivered.insertedAt, "insertedAt"], + [receipt.deliveryReceiptId, delivered.deliveryReceiptId, "deliveryReceiptId"], + [receipt.deliveredAt, delivered.deliveredAt, "deliveredAt"], + [receipt.resultMessageId, delivered.resultMessageId, "resultMessageId"], + [receipt.coalescedByResult, delivered.coalescedByResult, "coalescedByResult"], + ]; + for (const [actual, expected, field] of settlement) assertSame(actual, expected, field, "Delivered claim"); +} + +function parseRecord(value: unknown, path: string): OpenCodeNoticeIngressRecord { + const record = ownRecord(value, path); + exactKeys( + record, + ["reserve", "claim", "phase"], + ["insertion", "targetLedgerEntryId", "insertedAt", "receipt", "deliveredClaim"], + path, + ); + const reserve = parseNoticeRecipientIngressEnvelope(record.reserve); + const claim = parseDeliveryClaimRecord(record.claim); + const phase = record.phase; + if (phase !== "reserved" && phase !== "inserting" && phase !== "inserted" && phase !== "receipting" && phase !== "delivered") { + throw new Error(`${path}.phase is invalid`); + } + const insertion = record.insertion === undefined ? undefined : parseNoticeRecipientIngressEnvelope(record.insertion); + const targetLedgerEntryId = record.targetLedgerEntryId === undefined ? undefined : String(record.targetLedgerEntryId); + if (record.targetLedgerEntryId !== undefined && (typeof record.targetLedgerEntryId !== "string" || record.targetLedgerEntryId.length === 0)) { + throw new Error(`${path}.targetLedgerEntryId must be a non-empty string`); + } + const insertedAt = record.insertedAt === undefined ? undefined : timestamp(record.insertedAt, `${path}.insertedAt`); + const receipt = record.receipt === undefined ? undefined : parseNoticeRecipientIngressEnvelope(record.receipt); + const deliveredClaim = record.deliveredClaim === undefined ? undefined : parseDeliveryClaimRecord(record.deliveredClaim); + + const parsed: OpenCodeNoticeIngressRecord = { + reserve, + claim, + phase, + ...(insertion === undefined ? {} : { insertion }), + ...(targetLedgerEntryId === undefined ? {} : { targetLedgerEntryId }), + ...(insertedAt === undefined ? {} : { insertedAt }), + ...(receipt === undefined ? {} : { receipt }), + ...(deliveredClaim === undefined ? {} : { deliveredClaim }), + }; + assertReservationMatches(reserve, claim); + if (insertion !== undefined) assertInsertionMatches(parsed, insertion); + const hasInsertionEvidence = targetLedgerEntryId !== undefined || insertedAt !== undefined; + if (hasInsertionEvidence && (targetLedgerEntryId === undefined || insertedAt === undefined)) { + throw new Error(`${path} target ledger evidence must be present together`); + } + if (phase === "reserved" && (insertion !== undefined || hasInsertionEvidence || receipt !== undefined || deliveredClaim !== undefined)) { + throw new Error(`${path} reserved record contains later-phase evidence`); + } + if (phase === "inserting" && (insertion === undefined || hasInsertionEvidence || receipt !== undefined || deliveredClaim !== undefined)) { + throw new Error(`${path} inserting record has invalid evidence`); + } + if (phase === "inserted" && (insertion === undefined || !hasInsertionEvidence || receipt !== undefined || deliveredClaim !== undefined)) { + throw new Error(`${path} inserted record has invalid evidence`); + } + if (phase === "receipting" && (insertion === undefined || !hasInsertionEvidence || receipt === undefined || deliveredClaim !== undefined)) { + throw new Error(`${path} receipting record has invalid evidence`); + } + if (phase === "delivered" && (insertion === undefined || !hasInsertionEvidence || receipt === undefined || deliveredClaim === undefined)) { + throw new Error(`${path} delivered record lacks settlement evidence`); + } + if (receipt !== undefined) assertReceiptMatches(parsed, receipt); + if (deliveredClaim !== undefined) assertDeliveredClaimMatches(parsed, deliveredClaim); + return parsed; +} + +function parseState(value: unknown): OpenCodeNoticeIngressState { + const state = ownRecord(value, "$noticeIngress"); + exactKeys(state, ["version", "records"], [], "$noticeIngress"); + if (state.version !== 1) throw new Error("Unsupported OpenCode notice ingress state version"); + const recordsValue = ownRecord(state.records, "$noticeIngress.records"); + const records = Object.create(null) as Record; + const deliveryGroups = new Set(); + for (const [claimId, value] of Object.entries(recordsValue)) { + const record = parseRecord(value, `$noticeIngress.records[${JSON.stringify(claimId)}]`); + if (record.claim.deliveryClaimId !== claimId) throw new Error("Notice ingress claim key does not match its record"); + if (deliveryGroups.has(record.claim.deliveryGroupId)) throw new Error("Multiple OpenCode notice claims own one delivery group"); + deliveryGroups.add(record.claim.deliveryGroupId); + records[claimId] = record; + } + return { version: 1, records }; +} + +function clone(record: OpenCodeNoticeIngressRecord): OpenCodeNoticeIngressRecord { + return structuredClone(record); +} + +function cloneState(state: OpenCodeNoticeIngressState): OpenCodeNoticeIngressState { + return parseState(structuredClone(state)); +} + +function getOwnRecord( + records: Record, + deliveryClaimId: string, +): OpenCodeNoticeIngressRecord | undefined { + return Object.hasOwn(records, deliveryClaimId) ? records[deliveryClaimId] : undefined; +} + +function serializableState(state: OpenCodeNoticeIngressState): OpenCodeNoticeIngressState { + const records: Record = {}; + for (const [deliveryClaimId, record] of Object.entries(state.records)) { + Object.defineProperty(records, deliveryClaimId, { + value: clone(record), + enumerable: true, + writable: true, + configurable: true, + }); + } + return { version: 1, records }; +} + +function canonicalState(state: OpenCodeNoticeIngressState): string { + return canonicalJson(serializableState(state)); +} + +export function targetLedgerLookupEnvelope(record: OpenCodeNoticeIngressRecord): NoticeRecipientIngressEnvelope { + if (!record.insertion) throw new Error("Cannot look up a notice before insertion begins"); + const claim = record.claim; + const requestNonce = randomUUID(); + const checkedAt = new Date().toISOString(); + return parseNoticeRecipientIngressEnvelope({ + version: "orc.notice-recipient-ingress.v1", + operation: "lookup_target_ledger", + requestId: `${claim.deliveryClaimId}:opencode-ledger:${claim.claimGeneration}:${requestNonce}`, + idempotencyKey: `${claim.deliveryClaimId}:opencode-ledger:${claim.claimGeneration}:${requestNonce}`, + payload: { + deliveryClaimId: claim.deliveryClaimId, + claimGeneration: claim.claimGeneration, + recipientContext: claim.recipientContext, + recipientSessionId: claim.recipientSessionId, + ...(claim.recipientTargetSessionId === undefined ? {} : { recipientTargetSessionId: claim.recipientTargetSessionId }), + checkedAt, + }, + }); +} + +export class DurableOpenCodeNoticeIngressStore { + readonly path: string; + private state: OpenCodeNoticeIngressState; + private readonly persist: (path: string, state: OpenCodeNoticeIngressState) => void; + private poisoned: Error | undefined; + + constructor( + path: string, + persist: (path: string, state: OpenCodeNoticeIngressState) => void = writeDurableJson, + ) { + this.path = path; + ensureIntercomRuntimeDir(dirname(path)); + this.persist = persist; + this.state = this.load(); + } + + get(deliveryClaimId: string): OpenCodeNoticeIngressRecord | undefined { + this.assertUsable(); + const record = getOwnRecord(this.state.records, deliveryClaimId); + return record === undefined ? undefined : clone(record); + } + + reserve(envelopeValue: unknown, claimValue: unknown): OpenCodeNoticeIngressRecord { + this.assertUsable(); + const envelope = parseNoticeRecipientIngressEnvelope(envelopeValue); + const claim = parseDeliveryClaimRecord(claimValue); + assertReservationMatches(envelope, claim); + const existing = getOwnRecord(this.state.records, claim.deliveryClaimId); + if (existing) { + if (canonicalJson(existing.reserve) !== canonicalJson(envelope) || canonicalJson(existing.claim) !== canonicalJson(claim)) { + throw new Error("Conflicting OpenCode notice reservation"); + } + return clone(existing); + } + if (Object.values(this.state.records).some((record) => record.claim.deliveryGroupId === claim.deliveryGroupId)) { + throw new Error("A different OpenCode notice claim already owns this delivery group"); + } + const record: OpenCodeNoticeIngressRecord = { reserve: envelope, claim, phase: "reserved" }; + const next = cloneState(this.state); + next.records[claim.deliveryClaimId] = record; + this.commit(next); + return clone(record); + } + + beginInsertion(envelopeValue: unknown): OpenCodeNoticeIngressRecord { + this.assertUsable(); + const envelope = parseNoticeRecipientIngressEnvelope(envelopeValue); + if (envelope.operation !== "insert_or_attach") throw new Error("Expected insert_or_attach"); + const claimId = payload(envelope).deliveryClaimId; + if (typeof claimId !== "string") throw new Error("Notice insertion omitted deliveryClaimId"); + const record = getOwnRecord(this.state.records, claimId); + if (!record) throw new Error("Notice insertion has no durable winning reservation"); + assertInsertionMatches(record, envelope); + if (record.phase !== "reserved") { + if (canonicalJson(record.insertion) !== canonicalJson(envelope)) throw new Error("Conflicting OpenCode notice insertion replay"); + return clone(record); + } + const updated: OpenCodeNoticeIngressRecord = { ...record, phase: "inserting", insertion: envelope }; + parseRecord(updated, "$record"); + const next = cloneState(this.state); + next.records[claimId] = updated; + this.commit(next); + return clone(updated); + } + + markInserted(deliveryClaimId: string, receipt: OpenCodeNoticeInsertionReceipt): OpenCodeNoticeIngressRecord { + this.assertUsable(); + const record = getOwnRecord(this.state.records, deliveryClaimId); + if (!record || record.phase === "reserved") throw new Error("Cannot receipt an unreserved OpenCode notice insertion"); + assertInsertionReceiptMatches(record, receipt); + if (record.phase !== "inserting") { + if (record.targetLedgerEntryId !== receipt.targetLedgerEntryId || record.insertedAt !== receipt.insertedAt) { + throw new Error("Conflicting OpenCode notice ledger receipt"); + } + return clone(record); + } + const updated: OpenCodeNoticeIngressRecord = { + ...record, + phase: "inserted", + targetLedgerEntryId: receipt.targetLedgerEntryId, + insertedAt: receipt.insertedAt, + }; + parseRecord(updated, "$record"); + const next = cloneState(this.state); + next.records[deliveryClaimId] = updated; + this.commit(next); + return clone(updated); + } + + beginReceipt(envelopeValue: unknown): OpenCodeNoticeIngressRecord { + this.assertUsable(); + const envelope = parseNoticeRecipientIngressEnvelope(envelopeValue); + if (envelope.operation !== "record_receipt") throw new Error("Expected record_receipt"); + const claimId = payload(envelope).deliveryClaimId; + if (typeof claimId !== "string") throw new Error("Notice receipt omitted deliveryClaimId"); + const record = getOwnRecord(this.state.records, claimId); + if (!record || (record.phase !== "inserted" && record.phase !== "receipting" && record.phase !== "delivered")) { + throw new Error("Cannot record a receipt before durable OpenCode insertion"); + } + assertReceiptMatches(record, envelope); + if (record.phase !== "inserted") { + if (canonicalJson(record.receipt) !== canonicalJson(envelope)) throw new Error("Conflicting OpenCode notice receipt replay"); + return clone(record); + } + const updated: OpenCodeNoticeIngressRecord = { ...record, phase: "receipting", receipt: envelope }; + parseRecord(updated, "$record"); + const next = cloneState(this.state); + next.records[claimId] = updated; + this.commit(next); + return clone(updated); + } + + markDelivered(deliveryClaimId: string, claimValue: unknown): OpenCodeNoticeIngressRecord { + this.assertUsable(); + const record = getOwnRecord(this.state.records, deliveryClaimId); + if (!record || (record.phase !== "receipting" && record.phase !== "delivered")) { + throw new Error("Cannot settle an OpenCode notice before recording its receipt request"); + } + const deliveredClaim = parseDeliveryClaimRecord(claimValue); + assertDeliveredClaimMatches(record, deliveredClaim); + if (record.phase === "delivered") { + if (canonicalJson(record.deliveredClaim) !== canonicalJson(deliveredClaim)) throw new Error("Conflicting delivered claim replay"); + return clone(record); + } + const updated: OpenCodeNoticeIngressRecord = { ...record, phase: "delivered", deliveredClaim }; + parseRecord(updated, "$record"); + const next = cloneState(this.state); + next.records[deliveryClaimId] = updated; + this.commit(next); + return clone(updated); + } + + pending(): OpenCodeNoticeIngressRecord[] { + this.assertUsable(); + return Object.values(this.state.records).filter((record) => record.phase !== "delivered").map(clone); + } + + private load(): OpenCodeNoticeIngressState { + if (!existsSync(this.path)) return emptyState(); + return parseState(JSON.parse(readFileSync(this.path, "utf8"))); + } + + private loadExactTarget(): OpenCodeNoticeIngressState { + if (!existsSync(this.path)) throw new Error("Durable OpenCode notice ingress target is missing"); + return parseState(JSON.parse(readFileSync(this.path, "utf8"))); + } + + private commit(next: OpenCodeNoticeIngressState): void { + const stagedCanonical = canonicalState(parseState(structuredClone(next))); + const priorCanonical = canonicalState(this.state); + try { + this.persist(this.path, serializableState(parseState(JSON.parse(stagedCanonical)))); + } catch (persistError) { + try { + const recovered = this.loadExactTarget(); + const recoveredCanonical = canonicalState(recovered); + if (recoveredCanonical === stagedCanonical) { + this.state = parseState(JSON.parse(stagedCanonical)); + } else if (recoveredCanonical === priorCanonical) { + this.state = parseState(JSON.parse(priorCanonical)); + } else { + throw new Error("Durable OpenCode notice ingress state does not match the prior or staged commit"); + } + } catch (reconcileError) { + this.poisoned = new Error("Durable OpenCode notice ingress store is unavailable after commit reconciliation failed", { + cause: reconcileError, + }); + } + throw persistError; + } + this.state = parseState(JSON.parse(stagedCanonical)); + } + + private assertUsable(): void { + if (this.poisoned) throw this.poisoned; + } +} + +export class OpenCodeNoticeRecipientIngress { + constructor( + private readonly store: DurableOpenCodeNoticeIngressStore, + private readonly authority: AuthenticatedOpenCodeNoticeAuthority, + private readonly now: () => number = () => Date.now(), + ) { + if (!authority) throw new Error("Authenticated notice authority API is required"); + } + + async reserveBeforePrompt(envelopeValue: unknown): Promise { + const envelope = parseNoticeRecipientIngressEnvelope(envelopeValue); + if (envelope.operation !== "reserve_delivery") throw new Error("Expected reserve_delivery"); + const claim = await this.authority.reserveDelivery(envelope); + return this.store.reserve(envelope, claim); + } + + async insertOrAttach( + envelopeValue: unknown, + injectPromptOrAttach: (envelope: NoticeRecipientIngressEnvelope) => Promise, + ): Promise { + const envelope = parseNoticeRecipientIngressEnvelope(envelopeValue); + const claimId = payload(envelope).deliveryClaimId; + if (typeof claimId !== "string") throw new Error("Notice insertion omitted deliveryClaimId"); + const prior = this.store.get(claimId); + if (prior?.phase === "reserved") { + assertInsertionMatches(prior, envelope); + const protectedInsertion = this.authority.insertOrAttachWhileClaimCurrent; + if (typeof protectedInsertion !== "function") { + throw new OpenCodeNoticeInsertionFencingUnavailableError(); + } + const requestedAt = this.now(); + assertWallClockFresh(prior.claim, requestedAt); + const request = atomicInsertionRequest(prior, envelope, requestedAt); + let callbackCalls = 0; + let authorityCallOpen = false; + let rawResult: unknown; + const guardedInsertion = async (): Promise => { + if (!authorityCallOpen || callbackCalls !== 0) throw new Error("FENCING_CALLBACK_CLOSED"); + callbackCalls = 1; + // This local check is defense in depth only. The authenticated + // authority's atomic current-claim/deadline fence is the authority. + assertWallClockFresh(prior.claim, this.now()); + const inserting = this.store.beginInsertion(envelope); + if (inserting.phase !== "inserting") throw new Error("Protected OpenCode notice insertion did not begin from its reserved phase"); + return parseInsertionReceipt(await injectPromptOrAttach(envelope), "$protectedInsertionReceipt"); + }; + try { + authorityCallOpen = true; + rawResult = await new Promise((resolve, reject) => { + try { + protectedInsertion.call(this.authority, request, guardedInsertion).then( + (value) => { + authorityCallOpen = false; + resolve(value); + }, + (error: unknown) => { + authorityCallOpen = false; + reject(error); + }, + ); + } catch (error) { + authorityCallOpen = false; + reject(error); + } + }); + } finally { + authorityCallOpen = false; + } + const result = parseAtomicInsertionResult(rawResult); + if (result.status !== "inserted") { + if (callbackCalls !== 0) { + throw new Error(`Protected insertion authority invoked the target but returned ${result.status}`); + } + assertAtomicInsertionResultMatches(prior, request, result); + } + const expectedInserting: OpenCodeNoticeIngressRecord = { + ...prior, + phase: "inserting", + insertion: envelope, + }; + parseRecord(expectedInserting, "$expectedProtectedInsertion"); + assertAtomicInsertionResultMatches(expectedInserting, request, result); + if (callbackCalls !== 1) { + throw new Error("Protected insertion authority claimed insertion without invoking the protected target operation"); + } + const inserting = this.store.get(claimId); + if (!inserting || inserting.phase !== "inserting") { + throw new Error("Protected OpenCode notice insertion lacks its durable inserting phase"); + } + return this.store.markInserted(inserting.claim.deliveryClaimId, result.receipt); + } + const record = this.store.beginInsertion(envelope); + if (record.phase === "inserted" || record.phase === "receipting" || record.phase === "delivered") return record; + if (prior?.phase === "inserting") { + const lookupEnvelope = targetLedgerLookupEnvelope(record); + const lookup = parseTargetLedgerLookupResult(await this.authority.lookupTargetLedger(lookupEnvelope)); + assertLookupMatches(record, lookupEnvelope, lookup); + if (lookup.state === "inserted") { + return this.store.markInserted(record.claim.deliveryClaimId, { + deliveryClaimId: lookup.deliveryClaimId, + claimGeneration: lookup.claimGeneration, + targetLedgerEntryId: lookup.targetLedgerEntryId!, + insertedAt: lookup.insertedAt!, + }); + } + if (lookup.state !== "absent") { + throw new Error(`Authenticated target ledger is ${lookup.state}; refusing ambiguous OpenCode replay`); + } + throw new Error( + "Authenticated target-drained proof and generation-incremented current-claim reissue authority are unavailable; refusing OpenCode reinsertion", + ); + } + throw new Error("Unexpected OpenCode notice insertion state"); + } + + async recordReceipt(envelopeValue: unknown): Promise { + const envelope = parseNoticeRecipientIngressEnvelope(envelopeValue); + const record = this.store.beginReceipt(envelope); + if (record.phase === "delivered") return record; + const deliveredClaim = await this.authority.recordReceipt(envelope); + return this.store.markDelivered(record.claim.deliveryClaimId, deliveredClaim); + } + +} diff --git a/opencode/plugin.ts b/opencode/plugin.ts index 7e628b0..3c6d334 100644 --- a/opencode/plugin.ts +++ b/opencode/plugin.ts @@ -6,6 +6,20 @@ import { invokeAgentFleet, isFleetManagementEnabled } from "./fleet.ts"; import { startOpenCodeControlServer } from "./control.ts"; import { validateAskTimeoutMs } from "../config.ts"; +// Public, bundled contract surface. Production creation intentionally fails +// closed until the protected authority client and typed notice ingress exist. +export { + createProductionOpenCodeNoticeRecipientIngress, + DurableOpenCodeNoticeIngressStore, + getOpenCodeNoticeIngressStatePath, + OpenCodeNoticeAuthorityUnavailableError, + OpenCodeNoticeCurrentClaimUnavailableError, + OpenCodeNoticeRecipientIngress, + OPENCODE_NOTICE_AUTHORITY_UNAVAILABLE, + OPENCODE_NOTICE_CURRENT_CLAIM_EVIDENCE_VERSION, + OPENCODE_NOTICE_CURRENT_CLAIM_UNAVAILABLE, +} from "./notice-ingress.ts"; + const INJECT_LOG_PATH = "/tmp/intercom-inject.log"; interface PendingInjectEntry { diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..40e164f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,409 @@ +{ + "name": "@dataforxyz/agent-intercom-opencode", + "version": "0.10.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@dataforxyz/agent-intercom-opencode", + "version": "0.10.0", + "license": "AGPL-3.0-or-later", + "dependencies": { + "@opencode-ai/plugin": "^1.17.15" + }, + "devDependencies": { + "@dataforxyz/agent-intercom-core": "git+https://github.com/dataforxyz/agent-intercom-core.git#8316cbab548f422ad11c78ed887fabeef94817c1", + "@types/node": "^24.0.0", + "esbuild": "^0.28.1", + "tsx": "^4.20.0", + "typescript": "^6.0.3" + }, + "engines": { + "node": ">=22.19.0" + }, + "peerDependencies": { + "@dataforxyz/agent-intercom-core": "0.1.0" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "3.0.8", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@dataforxyz/agent-intercom-core": { + "version": "0.1.0", + "resolved": "git+https://github.com/dataforxyz/agent-intercom-core.git#8316cbab548f422ad11c78ed887fabeef94817c1", + "integrity": "sha512-tGEdYHG/Zrl/VSkOQMpjZ8LgnxG4O7youbVOEjZVwG+XbjhLVkDOeODa+U3plIUwbCVIXJHodmbJuH0yW5SIRA==", + "dev": true, + "license": "AGPL-3.0-or-later", + "dependencies": { + "@types/node": "^24.10.0" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.4", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opencode-ai/plugin": { + "version": "1.17.15", + "license": "MIT", + "dependencies": { + "@ai-sdk/provider": "3.0.8", + "@opencode-ai/sdk": "1.17.15", + "effect": "4.0.0-beta.83", + "zod": "4.1.8" + }, + "peerDependencies": { + "@opentui/core": ">=0.4.3", + "@opentui/keymap": ">=0.4.3", + "@opentui/solid": ">=0.4.3" + }, + "peerDependenciesMeta": { + "@opentui/core": { + "optional": true + }, + "@opentui/keymap": { + "optional": true + }, + "@opentui/solid": { + "optional": true + } + } + }, + "node_modules/@opencode-ai/sdk": { + "version": "1.17.15", + "license": "MIT", + "dependencies": { + "cross-spawn": "7.0.6" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.3", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/effect": { + "version": "4.0.0-beta.83", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "fast-check": "^4.8.0", + "find-my-way-ts": "^0.1.6", + "ini": "^7.0.0", + "kubernetes-types": "^1.30.0", + "msgpackr": "^2.0.1", + "multipasta": "^0.2.7", + "toml": "^4.1.1", + "uuid": "^14.0.0", + "yaml": "^2.9.0" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/fast-check": { + "version": "4.8.0", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^8.0.0" + }, + "engines": { + "node": ">=12.17.0" + } + }, + "node_modules/find-my-way-ts": { + "version": "0.1.6", + "license": "MIT" + }, + "node_modules/ini": { + "version": "7.0.0", + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "license": "ISC" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/kubernetes-types": { + "version": "1.30.0", + "license": "Apache-2.0" + }, + "node_modules/msgpackr": { + "version": "2.0.4", + "license": "MIT", + "optionalDependencies": { + "msgpackr-extract": "^3.0.4" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.4", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" + } + }, + "node_modules/multipasta": { + "version": "0.2.8", + "license": "MIT" + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pure-rand": { + "version": "8.4.1", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/toml": { + "version": "4.1.2", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/tsx": { + "version": "4.23.0", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "14.0.1", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/which": { + "version": "2.0.2", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/zod": { + "version": "4.1.8", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json index e65cab5..fa09fb3 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "description": "OpenCode plugin for direct local messaging with Pi, Codex, Claude, and OpenCode coding-agent sessions.", "license": "AGPL-3.0-or-later", "type": "module", + "engines": { + "node": ">=22.19.0" + }, "main": "dist/plugin.mjs", "exports": { ".": "./dist/plugin.mjs", @@ -32,7 +35,7 @@ "scripts": { "build": "node scripts/build.mjs", "prepare": "npm run build", - "test": "tsx --test broker/*.test.ts opencode/contact.test.ts opencode/control.test.ts opencode/fleet.test.ts opencode/health.test.ts opencode/inbound-store.test.ts opencode/runtime.test.ts opencode/team.test.ts opencode/tui.test.ts", + "test": "tsx --test broker/*.test.ts opencode/contact.test.ts opencode/control.test.ts opencode/fleet.test.ts opencode/health.test.ts opencode/inbound-store.test.ts opencode/notice-ingress.test.ts opencode/runtime.test.ts opencode/team.test.ts opencode/tui.test.ts test/*.test.ts", "typecheck": "tsc --noEmit" }, "keywords": [ @@ -44,15 +47,18 @@ "claude" ], "dependencies": { - "@dataforxyz/agent-intercom-core": "git+https://github.com/dataforxyz/agent-intercom-core.git#cb5d2212912db0cd8abbb16ab08e4b539424a05d", "@opencode-ai/plugin": "^1.17.15" }, "devDependencies": { + "@dataforxyz/agent-intercom-core": "git+https://github.com/dataforxyz/agent-intercom-core.git#8316cbab548f422ad11c78ed887fabeef94817c1", "@types/node": "^24.0.0", "esbuild": "^0.28.1", "tsx": "^4.20.0", "typescript": "^6.0.3" }, + "peerDependencies": { + "@dataforxyz/agent-intercom-core": "0.1.0" + }, "publishConfig": { "access": "public" }, diff --git a/scripts/build.mjs b/scripts/build.mjs index 8098338..1190c03 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -1,10 +1,12 @@ import { build } from "esbuild"; +import { externalizeCorePlugin } from "./core-external.mjs"; const common = { bundle: true, platform: "node", format: "esm", target: "node20", + plugins: [externalizeCorePlugin], }; await Promise.all([ diff --git a/scripts/core-external.mjs b/scripts/core-external.mjs new file mode 100644 index 0000000..35f30b4 --- /dev/null +++ b/scripts/core-external.mjs @@ -0,0 +1,19 @@ +export const CORE_PACKAGE = "@dataforxyz/agent-intercom-core"; + +// Match the package root and every exported (or future) subpath without +// accidentally externalizing similarly named packages. +export const CORE_IMPORT_PATTERN = /^@dataforxyz\/agent-intercom-core(?:\/.*)?$/; + +export function isCoreImport(specifier) { + return CORE_IMPORT_PATTERN.test(specifier); +} + +export const externalizeCorePlugin = { + name: "externalize-agent-intercom-core", + setup(build) { + build.onResolve({ filter: CORE_IMPORT_PATTERN }, (args) => ({ + path: args.path, + external: true, + })); + }, +}; diff --git a/test/core-externalization.test.ts b/test/core-externalization.test.ts new file mode 100644 index 0000000..0479c53 --- /dev/null +++ b/test/core-externalization.test.ts @@ -0,0 +1,79 @@ +import assert from "node:assert/strict"; +import { cpSync, mkdtempSync, mkdirSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import test from "node:test"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { + CORE_PACKAGE, + isCoreImport, +} from "../scripts/core-external.mjs"; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const bundles = ["plugin.mjs", "tui.mjs", "broker.mjs"] as const; +const exactCoreDevGit = "git+https://github.com/dataforxyz/agent-intercom-core.git#8316cbab548f422ad11c78ed887fabeef94817c1"; + +test("Core source matcher covers the package root and every subpath only", () => { + assert.equal(isCoreImport(CORE_PACKAGE), true); + assert.equal(isCoreImport(`${CORE_PACKAGE}/boss`), true); + assert.equal(isCoreImport(`${CORE_PACKAGE}/boss/policy`), true); + assert.equal(isCoreImport(`${CORE_PACKAGE}/future/nested/export`), true); + assert.equal(isCoreImport(`${CORE_PACKAGE}-lookalike`), false); + assert.equal(isCoreImport("@dataforxyz/agent-intercom"), false); + + const buildSource = readFileSync(join(root, "scripts/build.mjs"), "utf8"); + assert.match(buildSource, /plugins: \[externalizeCorePlugin\]/); + assert.match(buildSource, /external: \["@opencode-ai\/plugin"\]/); + assert.match(buildSource, /external: \["@opencode-ai\/plugin\/tui"\]/); +}); + +test("every dist bundle retains Core imports without embedding a second copy", () => { + for (const bundle of bundles) { + const source = readFileSync(join(root, "dist", bundle), "utf8"); + const coreSpecifiers = Array.from( + source.matchAll(/from\s+["'](@dataforxyz\/agent-intercom-core(?:\/[^"']*)?)["']/g), + match => match[1], + ); + assert.ok(coreSpecifiers.length > 0, `${bundle} must retain at least one external Core import`); + assert.ok(coreSpecifiers.every(isCoreImport), `${bundle} contains an invalid Core import`); + assert.doesNotMatch( + source, + /node_modules\/@dataforxyz\/agent-intercom-core\//, + `${bundle} must not embed Core implementation modules`, + ); + } +}); + +test("package manifest requires one exact Core runtime peer and keeps Git as dev provenance", () => { + const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as Record; + const lock = JSON.parse(readFileSync(join(root, "package-lock.json"), "utf8")) as Record; + + assert.equal(manifest.peerDependencies?.[CORE_PACKAGE], "0.1.0"); + assert.equal(manifest.dependencies?.[CORE_PACKAGE], undefined); + assert.equal(manifest.devDependencies?.[CORE_PACKAGE], exactCoreDevGit); + assert.equal(lock.packages?.[""]?.peerDependencies?.[CORE_PACKAGE], "0.1.0"); + assert.equal(lock.packages?.[""]?.dependencies?.[CORE_PACKAGE], undefined); + assert.equal(lock.packages?.[""]?.devDependencies?.[CORE_PACKAGE], exactCoreDevGit); + assert.ok(manifest.files?.includes("dist/**/*")); + assert.ok(manifest.files?.includes("opencode/**/*.ts")); + assert.ok(manifest.files?.includes("broker/**/*.ts")); +}); + +test("shipped TUI resolves against an explicitly supplied Core package offline", async () => { + const fixture = mkdtempSync(join(tmpdir(), "opencode-intercom-offline-core-")); + try { + const modules = join(fixture, "node_modules"); + const adapterDir = join(modules, "@dataforxyz", "agent-intercom-opencode"); + const coreDir = join(modules, "@dataforxyz", "agent-intercom-core"); + mkdirSync(adapterDir, { recursive: true }); + cpSync(join(root, "package.json"), join(adapterDir, "package.json")); + cpSync(join(root, "dist"), join(adapterDir, "dist"), { recursive: true }); + cpSync(join(root, "node_modules", ...CORE_PACKAGE.split("/")), coreDir, { recursive: true }); + + const entrypoint = pathToFileURL(join(adapterDir, "dist", "tui.mjs")).href; + const loaded = await import(`${entrypoint}?offline-explicit-core=${Date.now()}`); + assert.equal(loaded.default?.id, "opencode-intercom"); + } finally { + rmSync(fixture, { recursive: true, force: true }); + } +}); diff --git a/types.ts b/types.ts index 794891a..75f6d98 100644 --- a/types.ts +++ b/types.ts @@ -1,3 +1,22 @@ +import type { + BossControlEnvelope, + BossFeatureRegistration, + BossParticipantBinding, + BossPrivatePrincipal, +} from "@dataforxyz/agent-intercom-core/boss"; + +/** + * Broker-authenticated Boss metadata. It is intentionally absent from + * SessionRegistration so an ordinary client cannot self-assert a run, role, + * or binding epoch. + */ +export interface BossSessionMetadata { + registration: BossFeatureRegistration; + principal: BossPrivatePrincipal; + /** Controller principals have no participant binding; all other roles do. */ + binding?: BossParticipantBinding; +} + export interface SessionInfo { id: string; name?: string; @@ -18,6 +37,7 @@ export interface SessionInfo { depth?: number; maxDepth?: number; maxChildren?: number; + boss?: BossSessionMetadata; } export interface Message { @@ -40,7 +60,7 @@ export interface Attachment { export type SessionRegistration = Omit< SessionInfo, - "id" | "peerUid" | "trustedLocal" | "origin" | "remoteHostId" | "parentSessionId" | "rootSessionId" | "generation" | "canDelegate" | "depth" | "maxDepth" | "maxChildren" + "id" | "peerUid" | "trustedLocal" | "origin" | "remoteHostId" | "parentSessionId" | "rootSessionId" | "generation" | "canDelegate" | "depth" | "maxDepth" | "maxChildren" | "boss" > & { /** Ephemeral identity shared only by reconnects from one live runtime. */ runtimeInstanceId?: string; @@ -110,7 +130,9 @@ export type DeliveryFailureCode = | "TOO_MANY_PENDING_ASKS" | "RECIPIENT_DISCONNECTED" | "SENDER_DISCONNECTED" - | "DELIVERY_TIMEOUT"; + | "DELIVERY_TIMEOUT" + | "INVALID_BOSS_CONTROL" + | "BOSS_CONTROL_DENIED"; export type BrokerErrorCode = | "PROTOCOL_MISMATCH" @@ -140,6 +162,8 @@ export type ClientMessage = | { type: "unregister"; preserveAsks?: boolean } | { type: "list"; requestId: string } | { type: "send"; to: string; message: Message } + | { type: "boss_control_send"; to: string; envelope: BossControlEnvelope } + | { type: "boss_control_received"; deliveryId: string } | { type: "message_received"; deliveryId: string } | { type: "message_rejected"; deliveryId: string; code: "CONFLICTING_MESSAGE_ID"; reason: string } | { type: "defer_ask"; requestId: string; messageId: string } @@ -156,6 +180,11 @@ export type BrokerMessage = | { type: "access_control_result"; requestId: string; action: "issue_child_enrollment"; enrollmentToken: string; expiresAt: number; parentSessionId: string } | { type: "sessions"; requestId: string; sessions: SessionInfo[] } | { type: "message"; deliveryId: string; from: SessionInfo; message: Message } + | { type: "boss_control"; deliveryId: string; from: SessionInfo; envelope: BossControlEnvelope } + | { type: "boss_control_accepted"; messageId: string; deliveryId: string } + | { type: "boss_control_delivered"; messageId: string; deliveryId: string } + | { type: "boss_control_failed"; messageId: string; accepted: false; code: DeliveryFailureCode; reason: string } + | { type: "boss_control_failed"; messageId: string; deliveryId: string; accepted: true; code: DeliveryFailureCode; reason: string } | { type: "presence_update"; session: SessionInfo } | { type: "session_joined"; session: SessionInfo } | { type: "session_left"; sessionId: string }