Commit Graph
5 Commits
Author SHA1 Message Date
l0ng-ai 382a46ac29 refactor(server): retire the opaque workspace record store
Clients stopped sending WorkspaceList/Get/Put/Delete when the tree
migration landed, so the coexistence scaffolding comes out:

- core::workspace_store is deleted. Attachment and the data-directory
  resolution (TTY7_DATA_DIR, XDG fallback chain) move into
  core::machine, which was already their only consumer; Attachment
  loses its vestigial serde derives (it never crosses disk or wire).
- The control dialect drops the four record verbs, the ReplyOk::Json
  payload they answered with, and the WorkspaceChanged event. Their
  serde names (and the workspace-store capability bit) are recorded as
  burned rather than reserved by any mechanism — the dialect has no
  numbered slots to hold, so a comment at each site is the guard, plus
  the handshake test asserting the bit never reappears.
- host::server loses Services.workspaces, the verb arms, the
  per-connection store subscription and its WorkspaceChanged forwarder,
  and the store half of attach/detach/teardown. Attachment data now
  lives solely in the tree: a workspace the tree does not list records
  no data half (the registry's live handles still move, so takeover
  behaviour is unchanged), and it appears the moment the workspace
  does. Services::with_workspaces/and_machine collapse into
  with_machine; control_services becomes a single match.
- The attach/takeover tests move onto MachineStore wholesale, attaching
  to workspaces created in a real tree; the record-store round-trip and
  fan-out tests go (tests/machine_tree.rs has carried the tree
  equivalents since the verbs landed), and tests/workspace_store.rs is
  deleted with the serde_json dev-dependency that existed only for it.
  machine.rs gains the two guarantees the old suite held uniquely: an
  attachment dies with its workspace structurally, and the default path
  resolution ends at the documented file.
- The GUI's dead WorkspaceChanged arm and every stale doc reference go.
2026-07-30 00:35:49 +08:00
l0ng-ai 7c3e42999f feat(control): let clients mint workspace and tab identities on create
A window names its workspace — in the registry, the view file, and any
operation it queues — before its first round trip completes, and the same
holds for a tab the moment the user opens it. Making the daemon the only
minter would force every client to hold its edits until a reply carried
the real id back. Ids are uuids, so a client-minted one is as unique as a
daemon-minted one; WorkspaceCreate and TabCreate now carry an optional
client id, keep it when it is free, and refuse a duplicate rather than
adopt it. Absent (older callers, tests) the daemon mints as before.
2026-07-29 22:08:45 +08:00
l0ng-ai 324e1d1543 fix(core): review hardening for the machine-tree foundation
Findings from a correctness review of the new daemon-owned tree,
applied together:

- A dead pane can no longer be resurrected in the tree by its own last
  output. On Windows the exit monitor reports the death while the
  reader is still draining ConPTY's buffered bytes, and the death
  report is latched; the reader's 'output is proof of life' publish now
  asserts liveness only while the pane state still says alive.
- Delta delivery is ordered. Mutations were serialized by the state
  lock but delivered after releasing it, so one writer's deltas could
  overtake another's and leave every mirroring client on the losing
  state with no cue to re-pull. A notify-order mutex now spans each
  mutation and its own fan-out; cheap, because subscriber callbacks are
  enqueue-only by contract.
- Implicit active-tab changes broadcast. tab_create's activation and
  the close paths' heal now emit ActiveTabChanged, so a client applying
  deltas never re-implements the server's heal rule; the one
  inexpressible case (no tabs) needs no delta because it is a fact,
  not surgery.
- The coarse agent status no longer drives disk writes: it flips per
  hook event and is display-only, so it is outside the changed-facts
  gate and merely rides along when a load-bearing fact changes.
- control_services reports which stores it serves on stderr again —
  tty7-server configures no log sink, and 'no machine tree' was
  invisible exactly where it matters, on a headless box.
- The local link's first connect attempt is immediate instead of one
  backoff step late; the observation-slot test withdraws its store so
  it cannot swallow later tests' observations; and locked()'s poison
  rationale now says what is actually guaranteed.
2026-07-29 21:49:22 +08:00
l0ng-ai 0d58eef4bc feat(control): attachment and takeover ride the machine tree too
WorkspaceAttach / WorkspaceDetach (and the hello-names-a-workspace
shorthand) now record their data half on whichever workspace stores the
server carries: the retired record store, the machine tree, or — on a
full daemon while clients migrate — both, since they describe the same
workspace. The behavioural contract is untouched and now survives the
record store's retirement: newcomer always wins, the displaced session
is pushed Preempted (and closed only when its link was dedicated), and
a preempted session's tidy-up detach cannot evict the usurper — the
token check lives in the tree's runtime-only attachment exactly as it
did in the store's. A server carrying neither store answers the same
refusal a store-less server always has.

WorkspaceId gains FromStr (the inverse of its Display) because the
attach verbs predate the typed tree and carry the id as a string. The
end-to-end test drives a takeover on a server serving the tree and no
record store at all, asserting the tree's own attachment record moves
with it.
2026-07-29 21:21:48 +08:00
l0ng-ai 57d72c91ad feat(control): machine-tree verbs and incremental Layout deltas
Teach the control dialect the semantic operations the machine tree
serves: MachineGet / WorkspaceTree pulls, WorkspaceCreate / Rename /
Remove / Touch / SetActiveTab, TabCreate / Close / Rename / Move /
SetGroup, and PaneSplit / Close / SetRatio / Move / Replace. Replies
carry the daemon's own tree types (a created workspace or tab comes
back whole; close operations answer the pane ids that left the tree so
the caller can kill their PTYs), and every operation broadcasts a
ControlEvent::Layout delta to every connection but the writer's — the
same origin-exclusion mechanism the record store uses, one delta at a
time instead of whole-record last-writer-wins.

The server advertises a new 'machine-tree' capability bit only when it
actually carries a MachineStore; both daemons now do, alongside the
retired opaque record store, which keeps serving unchanged while
clients migrate. Delta fan-out rides its own bounded queue and
forwarder thread per connection, so a peer that stopped reading stalls
nobody's edit; the drop-on-overflow tradeoff is documented against the
keepalive that reaps such a peer and the full pull every reconnect
starts with.

The request/reply/event enums lose their Eq derive: split ratios are
f32. End-to-end tests drive the shipped tty7-server binary over real
pipes: capability advertisement, tree ops landing in the server's own
file, dead-pane revival across a real process restart, and delta
delivery between two live clients.
2026-07-29 21:01:58 +08:00