tasks(tty): decompose alknet-tty + alknet-tty-local into 15 implementation tasks
Break the tty spec set (docs/architecture/crates/tty/) into atomic,
dependency-ordered tasks across two crates:
alknet-tty (lean core, depends on alknet-core only — ADR-057):
- crate-init, wire-codec, control-messages, negotiation, backend-trait,
adapter, review-tty
alknet-tty-local (sibling, portable_pty + libc — ADR-054):
- crate-init, pty-mode, pipe-mode, backend-impl, review-tty-local
integration:
- local-feature-reexport, integration-test, review-tty-final
Three review checkpoints at critical points: after the core crate (before
the local backend builds on the one-way-door TtyBackend trait — ADR-053),
after the local backend (validating REQ-TTY-01/02 and ADR-056), and a final
merge-readiness review. The high-risk tasks (backend-trait, adapter,
pty-mode) are flagged; the ADR-056 kill-on-Drop guard the POC lacked is
called out in both pty-mode and pipe-mode.
Validated with taskgraph: 115 tasks, no cycles, 10-generation parallel
structure with 3-way parallelism after crate-init and 2-way after the
backend trait.
This commit is contained in:
1 parent
dbac972aaa
commit
8d20f89902
15 files changed
+2409
No files matched your search
@@ -0,0 +1,114 @@
|
||||
---
|
||||
id: tty-local/backend-impl
|
||||
name: Implement LocalTtyBackend (TtyBackend) branching on terminal Some/None
|
||||
status: pending
|
||||
depends_on: [tty-local/pty-mode, tty-local/pipe-mode]
|
||||
scope: narrow
|
||||
risk: low
|
||||
impact: component
|
||||
level: implementation
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Implement `LocalTtyBackend` in `src/backend.rs`: the `TtyBackend` implementation
|
||||
that branches on `TtyParams.terminal` to dispatch to PTY mode (task
|
||||
`tty-local/pty-mode`) or pipe mode (task `tty-local/pipe-mode`).
|
||||
|
||||
### LocalTtyBackend
|
||||
|
||||
```rust
|
||||
pub struct LocalTtyBackend;
|
||||
|
||||
impl LocalTtyBackend {
|
||||
pub fn new() -> Self;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TtyBackend for LocalTtyBackend {
|
||||
async fn allocate(&self, params: &TtyParams) -> Result<TtyHandle, TtyError> {
|
||||
match ¶ms.terminal {
|
||||
Some(terminal) => pty::allocate_pty(
|
||||
terminal.clone(),
|
||||
params.cmd.clone(),
|
||||
params.cwd.clone(),
|
||||
params.env.clone(),
|
||||
).await,
|
||||
None => pipe::allocate_pipe(
|
||||
params.cmd.clone(),
|
||||
params.cwd.clone(),
|
||||
params.env.clone(),
|
||||
).await,
|
||||
}
|
||||
}
|
||||
|
||||
fn resource_id(&self, _params: &TtyParams) -> Option<(&'static str, String)> {
|
||||
None // local backend creates its own resource (process); no pre-existing resource
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`LocalTtyBackend` takes no constructor dependencies (unlike
|
||||
`DockerTtyBackend` which wraps a `bollard::Docker` client). The
|
||||
`portable_pty` system is process-global. The assembly layer constructs one
|
||||
`LocalTtyBackend` and registers it as `"local"`.
|
||||
|
||||
### Validation
|
||||
|
||||
`allocate()` validates `params.cmd` is non-empty (the adapter already checks
|
||||
this at negotiation, but the backend should fail gracefully if called
|
||||
directly with an empty cmd — return `TtyError::AllocFailed`). The
|
||||
`backend_params` map is ignored by the local backend (it has no
|
||||
backend-specific selector fields); if a caller passes unexpected
|
||||
`backend_params`, they are silently ignored (the local backend doesn't
|
||||
define a typed params struct).
|
||||
|
||||
### Default trait methods
|
||||
|
||||
`resource_id()` returns `None` (the local backend creates its own resource —
|
||||
a process — so there's no pre-existing resource for the ownership check). The
|
||||
default impl on `TtyBackend` already returns `None`, so this can be omitted
|
||||
unless an explicit impl is preferred for clarity.
|
||||
|
||||
### Tests
|
||||
|
||||
- **PTY dispatch**: `allocate` with `terminal: Some` → returns a `TtyHandle`
|
||||
with `stderr: None` and a `PtyControl`.
|
||||
- **Pipe dispatch**: `allocate` with `terminal: None` → returns a `TtyHandle`
|
||||
with `stderr: Some` and a `PipeControl`.
|
||||
- **Empty cmd**: `allocate` with empty `cmd` → `TtyError::AllocFailed`.
|
||||
- **resource_id**: returns `None`.
|
||||
- These can be lightweight (the heavy tests are in `pty-mode` and `pipe-mode`);
|
||||
this task verifies the dispatch wiring.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `LocalTtyBackend` struct with `new()` constructor (no deps)
|
||||
- [ ] `impl TtyBackend for LocalTtyBackend` with `allocate()` branching on `params.terminal`
|
||||
- [ ] `terminal: Some` → dispatches to `pty::allocate_pty`
|
||||
- [ ] `terminal: None` → dispatches to `pipe::allocate_pipe`
|
||||
- [ ] `resource_id()` returns `None` (or uses the default)
|
||||
- [ ] Empty `cmd` → `TtyError::AllocFailed`
|
||||
- [ ] `LocalTtyBackend` re-exported from `lib.rs` (`pub use backend::LocalTtyBackend`)
|
||||
- [ ] Unit tests: PTY dispatch, pipe dispatch, empty cmd, resource_id
|
||||
- [ ] `cargo test -p alknet-tty-local` succeeds
|
||||
- [ ] `cargo clippy -p alknet-tty-local` succeeds with no warnings
|
||||
|
||||
## References
|
||||
|
||||
- docs/architecture/crates/tty/tty-local.md — §"PTY Mode", §"Pipe Mode", §"LocalTtyBackend takes no constructor dependencies"
|
||||
- docs/architecture/crates/tty/tty-backend.md — `TtyBackend` trait, `resource_id()`
|
||||
- docs/architecture/decisions/054-local-tty-backend-sibling-crate.md — ADR-054 (per-session PTY vs pipe choice)
|
||||
|
||||
## Notes
|
||||
|
||||
> This is the wiring task — the heavy lifting is in `pty-mode` and
|
||||
> `pipe-mode`. The branch on `TtyParams.terminal` is the per-session choice
|
||||
> (ADR-054): one `LocalTtyBackend` serves both terminal and runner use cases.
|
||||
> The local backend ignores `backend_params` (no backend-specific selector
|
||||
> fields); a docker/SSH backend would deserialize its own typed params from
|
||||
> it.
|
||||
|
||||
## Summary
|
||||
|
||||
> To be filled on completion
|
||||
Reference in new issue
Block a user