Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/host-beads-door-priming.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@bounded-systems/prx": minor
---

Host-shell read routing (prx-82b Slice 2e.1): `primeHostBeadsDoor` — on a host
shell in a repo whose pod is up, prx now points `PRX_BEADS_DOOR`/`PRX_BEADS_SOCKET`
at that pod's beadsd socket at startup, so the door-gated bd READ sites
(`bdDoorGate` / `bdCommandRunner`) route to the pod instead of spawning host bd.
No-op in a pod/room profile (the pod already projects these) or when no pod is up
(the host-native daemon stays the fallback until 2e.4). Reuses `podFor` (2a) +
the pod-socket probe (2b); env writes go through `@bounded-systems/env` `setEnv`.
29 changes: 28 additions & 1 deletion packages/prx/src/beadsd/client-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import { existsSync } from "node:fs";
import { join } from "node:path";

import { getEnv } from "@bounded-systems/env";
import { getEnv, setEnv } from "@bounded-systems/env";
import { spawnDetached } from "@bounded-systems/proc";

import { IsolatedBeadsClient } from "./client.ts";
Expand Down Expand Up @@ -69,6 +69,33 @@ export function isHostNativeSocket(socket: string): boolean {
return socket === DEFAULT_LOCAL_BEADS_SOCKET;
}

/** Deps for {@link primeHostBeadsDoor} (injectable for tests). */
export interface PrimeHostBeadsDoorDeps {
env?: typeof getEnv;
setEnvVar?: typeof setEnv;
/** The cwd's pod beadsd socket if its pod is up, else null (default: probe `podFor`). */
podSocket?: (() => string | null) | undefined;
}

/**
* Host-shell read routing (prx-82b Slice 2e.1): when host `prx` runs in a repo
* whose pod is up, point `PRX_BEADS_DOOR`/`PRX_BEADS_SOCKET` at that pod so the
* door-gated bd READ sites route to the pod instead of spawning host bd. No-op
* when already in a profile (the pod projects these into rooms) or when no pod is
* up (the host-native daemon stays the fallback until 2e.4). Returns true iff it
* primed the door. Call once at startup before the door dialer is consulted.
*/
export function primeHostBeadsDoor(deps: PrimeHostBeadsDoorDeps = {}): boolean {
const env = deps.env ?? getEnv;
const set = deps.setEnvVar ?? setEnv;
if (env("PRX_BEADS_DOOR")) return false; // already in a pod/room profile
const socket = (deps.podSocket ?? defaultPodBeadsSocket)();
if (!socket) return false; // no live pod → host-native fallback
set("PRX_BEADS_DOOR", "beadsd");
set("PRX_BEADS_SOCKET", socket);
return true;
}

/** Deps for {@link resolveBeadsEndpoint} (injectable for tests). */
export interface ResolveBeadsEndpointDeps {
/** The cwd's pod beadsd socket if its pod is up, else null (default: probe `podFor`). */
Expand Down
10 changes: 9 additions & 1 deletion packages/prx/src/pr-state/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,11 @@ import { runBeadsServe, type BeadsDaemonDeps } from "../beadsd/daemon.ts";
// GH-228: beads workspace self-heal (`prx beads doctor [--fix]`).
import { diagnoseBeads, healBeads } from "../beads/doctor.ts";
// GH-296: the host read-door — route `prx beads ready|list|show` through beadsd.
import { withBeadsClient, defaultCanonicalBeadsCwd } from "../beadsd/client-factory.ts";
import {
withBeadsClient,
defaultCanonicalBeadsCwd,
primeHostBeadsDoor,
} from "../beadsd/client-factory.ts";
import {
resolveWorkspaceAffinity,
WorkspaceAffinityError,
Expand Down Expand Up @@ -14772,6 +14776,10 @@ export function runCli(
// instead of spawning a local `bd`; off-profile it is never consulted
// (prx-asr / prx-634).
registerBdDoorDialer(prxBeadsDoorDialer);
// prx-82b Slice 2e.1: on a host shell in a repo whose pod is up, point the bd
// door at that pod so the door-gated read sites route to it instead of host
// bd. No-op in a pod/room profile or when no pod is up (host-native fallback).
primeHostBeadsDoor();
try {
// Spec-driven verbs (pilot/fleet/health) are handled by the canonical
// VerbSpec dispatch, ahead of the legacy typed-command union/executor
Expand Down
37 changes: 37 additions & 0 deletions packages/prx/test/beadsd/client-factory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
resolveLocalBeadsCwd,
withBeadsClient,
isHostNativeSocket,
primeHostBeadsDoor,
DEFAULT_LOCAL_BEADS_SOCKET,
} from "../../src/beadsd/client-factory.ts";

Expand Down Expand Up @@ -201,3 +202,39 @@ describe("ensureLocalBeadsd", () => {
).rejects.toThrow(BeadsUnavailableError);
});
});

describe("primeHostBeadsDoor — host-shell read routing (prx-82b 2e.1)", () => {
function harness(overrides: { door?: string; podSocket?: string | null }) {
const set: Record<string, string> = {};
const env = (k: string) => (k === "PRX_BEADS_DOOR" ? overrides.door : undefined);
const result = primeHostBeadsDoor({
env: env as never,
setEnvVar: ((k: string, v: string) => {
set[k] = v;
}) as never,
podSocket: () => overrides.podSocket ?? null,
});
return { result, set };
}

test("primes the door to the cwd's pod socket when a pod is up", () => {
const { result, set } = harness({ podSocket: "/run/prx/doors/slug/beadsd.sock" });
expect(result).toBe(true);
expect(set).toEqual({
PRX_BEADS_DOOR: "beadsd",
PRX_BEADS_SOCKET: "/run/prx/doors/slug/beadsd.sock",
});
});

test("no-op when no pod is up (host-native fallback stays)", () => {
const { result, set } = harness({ podSocket: null });
expect(result).toBe(false);
expect(set).toEqual({});
});

test("no-op when already in a pod/room profile (PRX_BEADS_DOOR set)", () => {
const { result, set } = harness({ door: "beadsd", podSocket: "/run/prx/doors/x/beadsd.sock" });
expect(result).toBe(false);
expect(set).toEqual({});
});
});
Loading