9-Implementation: document the auth package (the role axis)
The role axis shipped in CB-548/CB-560/CB-561 but landed nowhere on this page: Role, Principal, CallerResolver, Authz, AuditLog, MemberLifecycle and MemberRegistry were all undocumented, and so was the decision table that says an architect may send and a worker may not. Also fix three stale facts found while writing it: the launcher package is member, not worker; the daemon speaks herdr protocol 19 (0.8.0), not 14 (0.7.0); and there are fourteen packages, not ten. The package list now says plainly which ones this page still does not cover.
+43
-9
@@ -9,7 +9,7 @@
|
||||
|
||||
`bridged` is a single-host Java 25 / Maven daemon: the sole gateway between an on-subscription
|
||||
**primary** (Opus) and off-subscription **workers**, speaking to the **herdr** PTY manager
|
||||
(protocol 14, herdr 0.7.0) over a Unix-domain socket. It presents two equivalent faces — a REST
|
||||
(protocol 19, herdr 0.8.0 — ported in CB-521) over a Unix-domain socket. It presents two equivalent faces — a REST
|
||||
server (the testability seam) and an MCP server — over one shared service core.
|
||||
|
||||
## Component map
|
||||
@@ -32,7 +32,7 @@ flowchart TB
|
||||
msg["msg.MessageService + Rendezvous + ReplyInbox<br/>service core · rendezvous + held-reply inbox"]
|
||||
inject["inject.Injector + StatusPoller<br/>single-writer, status-gated delivery"]
|
||||
guard["guard.SubscriptionGuard<br/>boundary enforcement"]
|
||||
ccl["worker.ClaudeCodeLauncher<br/>first Claude adapter · implements PeerLauncher"]
|
||||
ccl["member.ClaudeCodeLauncher<br/>first Claude adapter · implements PeerLauncher"]
|
||||
herdr["herdr.AgentControl / WorkspaceControl<br/>JSON-RPC over UNIX socket"]
|
||||
end
|
||||
|
||||
@@ -64,8 +64,11 @@ the adapter and gates spawn.*
|
||||
|
||||
## Package & class reference
|
||||
|
||||
Ten packages under `dev.ltms.bridged`. Below, per layer: the classes, their kind, and their
|
||||
role. Method signatures are abbreviated; see source for full contracts.
|
||||
Fourteen packages under `dev.ltms.bridged`: `auth`, `config`, `guard`, `herdr`, `inject`, `lead`,
|
||||
`mcp`, `member`, `metrics`, `msg`, `peer`, `placement`, `rest`, `session`. Below, per layer: the
|
||||
classes, their kind, and their role. Method signatures are abbreviated; see source for full
|
||||
contracts. The sections below do not yet cover every package — `auth`, `herdr`, `inject`, `msg`,
|
||||
`peer`, `mcp` and the edge are written up; `lead`, `metrics`, `placement` and `session` are not.
|
||||
|
||||
### `herdr` — the wire layer (11 classes)
|
||||
|
||||
@@ -127,11 +130,42 @@ holds a worker's terminal reply when *no* forward send is open (instead of dropp
|
||||
`TIMED_OUT_WORKING`, `TIMED_OUT_QUEUED`, `BUSY`, `STALE_TURN`. `AskOutcome`: `ANSWERED`,
|
||||
`NO_WAITER`, `TIMED_OUT`. `Phase`: `PENDING`, `DONE`, `FAILED`.
|
||||
|
||||
### `auth` — who is calling, and what may they do (7 classes)
|
||||
|
||||
The role axis. Every request arrives on a connection, and this package turns that connection into
|
||||
a `Principal` and then decides. Identity is **never** taken from a tool argument, so a caller
|
||||
cannot claim to be someone else.
|
||||
|
||||
| Class | Kind | Role |
|
||||
|---|---|---|
|
||||
| `Role` | enum | `PRIMARY` (a lead orchestrator), `WORKER` (a spawned member with its own pane), `ARCHITECT` (a spawned member bound to a configured slot). Anything unrecognised is anonymous, not a role. |
|
||||
| `Principal` | record | `(role, terminal, pid, name)` plus the factories `anonymous`, `primary`, `leader`, `worker`, `architect`. Predicates: `isPrimary`, `isWorker`, `isArchitect`, `isSpawnedMember` (worker **or** architect — CB-560), `isAnonymous`, `ownsSession`. |
|
||||
| `CallerResolver` | class | Connection → `Principal`. Since CB-561 the **only** public way to build one is `withLeadsAndMembers(identity, tokenMode, token, leadTerminals, memberRegistry)`; the older map- and supplier-form constructors were deleted because they produced a resolver that could never return `ARCHITECT`, which failed silently. The remaining constructors are package-private and exist for tests. |
|
||||
| `Authz` | class | The one decision table, `permits(caller, action, targetSession)`. |
|
||||
| `AuditLog` | class | `allowed` / `denied` / `failed` — one line per decision, so a refusal is visible rather than a mystery. |
|
||||
| `MemberLifecycle` | interface | `acquired(role, profile, terminal)` / `released(terminal)`. The seam the session lifecycle calls; a no-op default keeps tests free of the registry. |
|
||||
| `MemberRegistry` | class | Flattens every `fleet:` pool into slots keyed `architect:opus`, and owns the live `terminal → slot` bindings. `acquired` binds **only** when `role == ARCHITECT`, to the first free slot with a matching profile; `bind`/`unbind` are compare-safe, so a stale unbind cannot remove a replacement. |
|
||||
|
||||
**The decision table** (`Authz.Action` → who):
|
||||
|
||||
| Action | Permitted to |
|
||||
|---|---|
|
||||
| `SPAWN`, `STOP`, `DRAIN` | the primary only |
|
||||
| `SEND` | the primary **or** an architect |
|
||||
| `REPLY`, `ASK` | the caller that owns the target session — only ever itself |
|
||||
| `READ`, `METRICS` | primary, worker, or architect |
|
||||
|
||||
**Two consequences worth stating.** First, a member's role reaches the principal only through
|
||||
`MemberRegistry`, and only architects bind; a developer and a reviewer are both `Role.WORKER` at
|
||||
this layer, and the difference between them lives in the roster (`bridge_list`), not in the
|
||||
principal. Second, `SEND` is the one action an architect gains over a worker, which is what lets
|
||||
two architects talk to each other without the lead relaying every message.
|
||||
|
||||
### `peer` — the launcher SPI (4 classes)
|
||||
|
||||
The seam that keeps the core peer-neutral. The bus delegates spawn/teardown to a launcher
|
||||
implementation while the core owns transport, session lifecycle, and routing. The first adapter
|
||||
is `worker.ClaudeCodeLauncher`; future adapters (e.g. Codex) implement the same SPI.
|
||||
is `member.ClaudeCodeLauncher`; future adapters (e.g. Codex) implement the same SPI.
|
||||
|
||||
| Class | Kind | Role |
|
||||
|---|---|---|
|
||||
@@ -166,12 +200,12 @@ transport context. **CB-307 scope:** only a *terminal* `bridge_reply` with no op
|
||||
`bridge_ask` (interactive; the worker blocks and can't consume a late answer) and the injector's
|
||||
completion/failure fallbacks (they target a *captured* waiter, CB-116) are **never** queued.
|
||||
|
||||
### `rest` · `worker` · `guard` · `config` — the edge (6 classes)
|
||||
### `rest` · `member` · `lead` · `guard` · `config` — the edge
|
||||
|
||||
| Class | Kind | Role |
|
||||
|---|---|---|
|
||||
| `rest.BridgedApp` | class | Javalin routes; validates bodies, maps `Outcome` → HTTP status. |
|
||||
| `worker.ClaudeCodeLauncher` | class | Guard-checked spawn, orphan-pane reaping at boot, teardown. The first-class `PeerLauncher` adapter for Claude Code over herdr (`spawn`, `reapOrphanWorkers`, `stop`, `list`, `profiles`, `capabilities`). |
|
||||
| `member.ClaudeCodeLauncher` | class | Guard-checked spawn, orphan-pane reaping at boot, teardown. The first-class `PeerLauncher` adapter for Claude Code over herdr (`spawn`, `reapOrphanWorkers`, `stop`, `list`, `profiles`, `capabilities`). |
|
||||
| `guard.SubscriptionGuard` | class | Host-allowlist + primary-cleanliness enforcement (`assertWorker`, `assertPrimaryClean`). |
|
||||
| `guard.GuardException` | class | Thrown on any subscription-boundary violation. |
|
||||
| `config.BridgedConfig` | record | YAML config with defaults; single legacy worker or named `workers` map (`load`, `workerProfiles`, `defaultProfile`). |
|
||||
@@ -202,7 +236,7 @@ completion/failure fallbacks (they target a *captured* waiter, CB-116) are **nev
|
||||
flowchart LR
|
||||
cfg["load config<br/>+ assertPrimaryClean"] --> guard["SubscriptionGuard"]
|
||||
guard --> herdr["connect UnixSocketHerdrClient<br/>→ AgentControl / WorkspaceControl"]
|
||||
herdr --> ccl["worker.ClaudeCodeLauncher<br/>behind PeerLauncher SPI<br/>→ reapOrphanWorkers()"]
|
||||
herdr --> ccl["member.ClaudeCodeLauncher<br/>behind PeerLauncher SPI<br/>→ reapOrphanWorkers()"]
|
||||
ccl --> rv["Rendezvous → CompletionResolver<br/>→ Injector + StatusPoller"]
|
||||
rv --> ms["MessageService"]
|
||||
ms --> mcp["BridgeMcp<br/>(connection identity)"]
|
||||
@@ -395,7 +429,7 @@ startup) hard-stops if the primary env carries any `ANTHROPIC_BASE_URL`. Spawn i
|
||||
turn per session (coalesced via `openAsksBySession`).
|
||||
- **`mcp`** — identity resolution shells out to `lsof` once per worker-identity request; shared
|
||||
services own their own concurrency.
|
||||
- **`worker`** — `AtomicLong` name sequence + per-process nonce avoid herdr name collisions
|
||||
- **`member`** — `AtomicLong` name sequence + per-process nonce avoid herdr name collisions
|
||||
without locks; the orphan reaper is best-effort and never aborts startup.
|
||||
|
||||
## Related pages
|
||||
|
||||
Reference in New Issue
Block a user