[v0.1/IPC Review] Resolve attach targets on the daemon instead of a roster snapshot #310

Closed
opened 2026-09-21 12:18:11 +09:00 by natsukium · 1 comment
Owner

Why

The design documentation says that session prefixes are resolved by the daemon, so a caller does not need to fetch a roster before operating on a session. The attach-scoped paths do not currently satisfy that contract.

The CLI and bridge perform:

Ops::List -> client-side prefix resolution -> Session::Attach(full id)

Relevant paths include:

  • crates/felis-cli/src/cli_sessions.rs:96-107
  • crates/felis-cli/src/cli_sessions.rs:1968-1976
  • crates/felis-cli/src/cli_bridge.rs:1569-1575
  • crates/felis-client-core/src/dial.rs:458-507

SessionMsg::Attach accepts a u128 full ID and documents that the ID came from an Ops::Listed roster. This adds a round trip and makes prefix resolution and attach a non-atomic pair. A session can disappear between the list and attach, and info can report a stale roster row after the session has already changed or been destroyed.

IDs are not reused, so this is primarily a consistency and latency problem rather than an accidental-attachment vulnerability. It nevertheless contradicts the documented IPC design and makes every non-Rust bridge consumer depend on the same client-side resolver.

Decision required before v0.1.0

Choose and freeze one of these designs:

  1. Change SessionMsg::Attach to carry a validated id_prefix, resolve it in the daemon at attach time, and return the full ID in SessionMsg::Attached.
  2. Keep exact-ID attach, but explicitly narrow the daemon-side-resolution claim and document the roster snapshot semantics for attach-scoped operations.

The first option is preferred while the wire is still pre-release. Internal reconnect paths may retain an exact-ID attach arm if they need to avoid a roster query.

Consider an atomic Ops::Info or equivalent query for sessions info, rather than deriving info entirely from a client-side roster snapshot.

Acceptance criteria

  • The reference and explanation IPC documents agree with the actual resolution boundary.
  • Prefix resolution and attach are covered by a daemon-side race test.
  • CLI and bridge capture/search/send/attach paths use the selected contract consistently.
  • If the wire changes, felis.proto, conversion code, minor ledger, compatibility acknowledgment, schemas, and golden conversations are updated together.
  • Full IDs remain available for durable machine output and reconnect paths.

Related: #6, #52.

## Why The design documentation says that session prefixes are resolved by the daemon, so a caller does not need to fetch a roster before operating on a session. The attach-scoped paths do not currently satisfy that contract. The CLI and bridge perform: ```text Ops::List -> client-side prefix resolution -> Session::Attach(full id) ``` Relevant paths include: - `crates/felis-cli/src/cli_sessions.rs:96-107` - `crates/felis-cli/src/cli_sessions.rs:1968-1976` - `crates/felis-cli/src/cli_bridge.rs:1569-1575` - `crates/felis-client-core/src/dial.rs:458-507` `SessionMsg::Attach` accepts a `u128` full ID and documents that the ID came from an `Ops::Listed` roster. This adds a round trip and makes prefix resolution and attach a non-atomic pair. A session can disappear between the list and attach, and `info` can report a stale roster row after the session has already changed or been destroyed. IDs are not reused, so this is primarily a consistency and latency problem rather than an accidental-attachment vulnerability. It nevertheless contradicts the documented IPC design and makes every non-Rust bridge consumer depend on the same client-side resolver. ## Decision required before v0.1.0 Choose and freeze one of these designs: 1. Change `SessionMsg::Attach` to carry a validated `id_prefix`, resolve it in the daemon at attach time, and return the full ID in `SessionMsg::Attached`. 2. Keep exact-ID attach, but explicitly narrow the daemon-side-resolution claim and document the roster snapshot semantics for attach-scoped operations. The first option is preferred while the wire is still pre-release. Internal reconnect paths may retain an exact-ID attach arm if they need to avoid a roster query. Consider an atomic `Ops::Info` or equivalent query for `sessions info`, rather than deriving `info` entirely from a client-side roster snapshot. ## Acceptance criteria - The reference and explanation IPC documents agree with the actual resolution boundary. - Prefix resolution and attach are covered by a daemon-side race test. - CLI and bridge capture/search/send/attach paths use the selected contract consistently. - If the wire changes, `felis.proto`, conversion code, minor ledger, compatibility acknowledgment, schemas, and golden conversations are updated together. - Full IDs remain available for durable machine output and reconnect paths. Related: #6, #52.
Author
Owner

Direction agreed 2026-09-21 after three sol design-review rounds (final verdict PASS). Full plan and batch order: see the comment on #12.

  • #310 — option 1: SessionMsg::Attach gains an id_prefix alternative
    to the exact id (reconnect and roster-driven switch keep exact ids via
    Landing::AttachExact), resolved daemon-side with the existing
    resolve_session_prefix; Attached.info.id already returns the full id.
    NoMatch / Ambiguous map to new AttachFailure variants (attach
    refusals already close the connection through SessionAttachFailed, which
    is the right outcome for a prefix that names no single session). Add
    Ops::Info { id_prefix } with a closed outcome union in the OpsSpawned
    style: found { SessionInfo session; string short_id } | no_match {} |
    ambiguous { uint32 matches }; only the successful arm carries session
    data. The daemon computes short_id with short_session_prefix against
    its pool at reply time. This is a deliberate narrowing of the
    control-surfaces decision "no shortening against the daemon's whole
    roster": that rejection was about the CLI owing a second round trip and
    emitting an unchecked prefix; a prefix the daemon checked against the
    authoritative pool in the same reply costs no round trip and is checked,
    so sessions info keeps its documented always-present short_id without
    an Ops::List. The decision record is rewritten in place to say so.
    sessions info and the bridge sessions.info use Ops::Info; capture,
    search, send attach by prefix. The bridge comment that attach-scoped
    verbs cannot use daemon-side resolution is retired.
Direction agreed 2026-09-21 after three `sol` design-review rounds (final verdict PASS). Full plan and batch order: see the comment on #12. - **#310** — option 1: `SessionMsg::Attach` gains an `id_prefix` alternative to the exact `id` (reconnect and roster-driven switch keep exact ids via `Landing::AttachExact`), resolved daemon-side with the existing `resolve_session_prefix`; `Attached.info.id` already returns the full id. `NoMatch` / `Ambiguous` map to new `AttachFailure` variants (attach refusals already close the connection through `SessionAttachFailed`, which is the right outcome for a prefix that names no single session). Add `Ops::Info { id_prefix }` with a closed outcome union in the `OpsSpawned` style: `found { SessionInfo session; string short_id }` | `no_match {}` | `ambiguous { uint32 matches }`; only the successful arm carries session data. The daemon computes `short_id` with `short_session_prefix` against its pool at reply time. This is a deliberate narrowing of the control-surfaces decision "no shortening against the daemon's whole roster": that rejection was about the CLI owing a second round trip and emitting an unchecked prefix; a prefix the daemon checked against the authoritative pool in the same reply costs no round trip and is checked, so `sessions info` keeps its documented always-present `short_id` without an `Ops::List`. The decision record is rewritten in place to say so. `sessions info` and the bridge `sessions.info` use `Ops::Info`; `capture`, `search`, `send` attach by prefix. The bridge comment that attach-scoped verbs cannot use daemon-side resolution is retired.
Commenting is not possible because the repository is archived.
No description provided.