Files
alknet/tasks/tty/review-tty-final.md
T
glm-5.2 5873fa22d6 chore: mark tty/review-tty-final as completed — all 15 tasks done
Final review verified:
- 103 tests pass (61 alknet-tty + 42 alknet-tty-local), clippy clean, fmt clean
- Cross-crate seam: portable_pty only in alknet-tty-local, not in alknet-tty default
- ADR-055 exit-chunk-is-last enforced in adapter, validated in integration tests
- ADR-056 cancel-cleanup guards in both PTY and pipe exit futures, integration tests confirm no orphans
- REQ-TTY-01 three-thread bridge, REQ-TTY-02 process-group signal forwarding
- No bollard/russh/alknet-call deps; alknet-tty depends on alknet-core only
- Deviation: local feature re-export deferred to assembly layer (cargo cycle constraint)
2026-07-07 23:49:40 +00:00

145 lines
7.2 KiB
Markdown

---
id: tty/review-tty-final
name: Final review of alknet-tty + alknet-tty-local for merge readiness
status: completed
depends_on: [tty/integration-test]
scope: broad
risk: low
impact: project
level: review
---
## Description
Final review of the `alknet-tty` and `alknet-tty-local` crates for merge
readiness. This is the last quality checkpoint before the tty work is
considered complete. It validates the two crates as a unit: the core crate's
wire format + adapter + trait, the local backend's PTY/pipe implementations,
the feature-gate seam, and the end-to-end integration tests.
### Review Checklist
1. **Cross-crate seam**:
- `alknet-tty` `local` feature gate activates `dep:alknet-tty-local`
- `alknet_tty::local::LocalTtyBackend` re-export works with `--features local`
- `cargo tree -p alknet-tty` (default) does NOT include `portable_pty`
- `cargo tree -p alknet-tty --features local` includes `portable_pty` via `alknet-tty-local`
- `LocalTtyBackend` implements `alknet_tty::TtyBackend` (the trait compiles against the impl)
- Assembly-layer pattern documented
2. **Wire format invariants (ADR-052)**:
- Two-carriage model: JSON negotiation frame, then raw chunks
- Fixed channel set (0-3), no extension escape hatch
- `MAX_CHUNK_LEN` = 16 MiB shared between negotiation and raw chunks
- Framing disambiguation: error frame first byte `0x00`, raw chunk first byte 1-3
- Self-contained framing (no alknet-call dependency, ADR-057)
3. **Exit-chunk ordering (ADR-055)**:
- Exit chunk is the last chunk before stream close
- Adapter waits for stdout/stderr pumps AND exit_code resolve before exit chunk
- Exit error → `{"type":"exit","code":-1}`
- Integration test confirms exit-chunk-is-last with a real backend
4. **Cancel-cleanup (ADR-056)**:
- `LocalExitFuture` (PTY) has kill-on-Drop guard (`ChildKiller::kill(SIGHUP)`)
- `PipeExitFuture` (pipe) has kill-on-Drop guard (`Child::start_kill()`)
- Both disarm on resolve (no-op Drop on happy path)
- Neither is a bare `oneshot::Receiver` / bare `Child::wait()`
- Integration tests confirm no orphaned processes on connection drop (PTY + pipe)
5. **REQ-TTY-01 (backends need not be natively async)**:
- PTY mode uses three std threads (reader, writer, waiter) + tokio mpsc/oneshot
- Trait's adapter-facing types (`AsyncWrite`, `Stream`, `BoxFuture`, `TtyControl`) are the contract
- Documented as a supported implementation strategy
6. **REQ-TTY-02 (signal forwarding to process group)**:
- PTY mode: `kill(-pgid, sig)` with `kill(pid, sig)` fallback
- `portable_pty` child is a session leader (`controlling_tty = true`)
- Pipe mode: `kill(pid, sig)` only (documented limitation, no process group)
- Unknown names fall back to default kill (SIGHUP for PTY, SIGKILL for pipe)
- Integration test confirms process-group targeting (shell's child receives signal)
7. **Access control (ADR-050)**:
- Scope-gate at negotiation (`tty:open` or deployment-configured)
- Ownership check via `backend.resource_id()` + `OwnershipProvider::owns()` when wired
- `LocalTtyBackend::resource_id()` returns `None` (creates its own resource)
8. **Dependency hygiene**:
- `alknet-tty` depends on `alknet-core` only (no `alknet-call`, no backend deps)
- `alknet-tty-local` depends on `alknet-tty` + `portable_pty` + `libc`
- No `alknet-core` direct dep in `alknet-tty-local`
- No `bollard`/`russh` (future backend crates)
9. **Pattern consistency**:
- `thiserror` for error enums (`TtyError`, `RawError`, `NegotiationError`)
- `async-trait` for `TtyBackend` and `ProtocolHandler`
- `tracing` for structured logging
- `TtyControlHandle::new(Arc::new(...))` wrapping (OQ-43)
- `#[cfg(unix)]` gates on `libc::kill` paths
- `#[non_exhaustive]` on `TtyError`
10. **Test coverage**:
- Wire codec unit tests (round-trip, error cases)
- Control message unit tests (round-trip, signal_from_name)
- Negotiation unit tests (round-trip, serde(flatten), error framing, disambiguation)
- Backend trait unit tests (MockBackend, TtyControlHandle delegation)
- Adapter unit tests (MockBackend over duplex, all scenarios)
- PTY mode integration tests (happy, interactive, resize, signal, process-group, cancel, exit-last)
- Pipe mode integration tests (happy, stderr, signal, cancel, resize)
- Negotiation error integration tests (unknown_backend, malformed, allocate_failed)
- All tests pass with `--features local`
11. **Documentation**:
- Crate-level doc comments on both crates
- Module-level doc comments
- ADR-056 kill-on-Drop contract documented on `TtyHandle.exit_code`
- REQ-TTY-01 documented on `TtyBackend` trait
- REQ-TTY-02 documented on `PtyControl::signal`
- Pipe-mode signal limitation documented on `PipeControl::signal`
- Assembly-layer pattern documented
## Acceptance Criteria
- [ ] Cross-crate seam: `local` feature re-export works; `portable_pty` gated correctly
- [ ] Wire format invariants (ADR-052) hold: two-carriage, fixed channels, disambiguation
- [ ] Exit-chunk-is-last (ADR-055) enforced in adapter, validated in integration tests
- [ ] Cancel-cleanup (ADR-056) guards present in both PTY and pipe exit futures; integration tests confirm no orphans
- [ ] REQ-TTY-01 satisfied: PTY mode uses three-thread bridge; trait contract documented
- [ ] REQ-TTY-02 satisfied: PTY process-group signal forwarding; integration test passes
- [ ] Access control (ADR-050): scope-gate + ownership check wired
- [ ] Dependency hygiene: alknet-tty lean (core only); alknet-tty-local has portable_pty/libc
- [ ] Pattern consistency: thiserror, async-trait, tracing, TtyControlHandle wrapping, cfg(unix)
- [ ] All unit + integration tests pass with `--features local`
- [ ] `cargo fmt --check` passes for both crates
- [ ] `cargo clippy` passes with no warnings for both crates (default + `--features local`)
- [ ] Documentation complete (crate, module, contract doc comments)
## References
- docs/architecture/crates/tty/README.md
- docs/architecture/crates/tty/overview.md
- docs/architecture/crates/tty/tty-wire.md
- docs/architecture/crates/tty/tty-backend.md
- docs/architecture/crates/tty/tty-adapter.md
- docs/architecture/crates/tty/tty-local.md
- docs/architecture/decisions/052-alknet-tty-wire-format-and-two-carriage.md
- docs/architecture/decisions/053-ttybackend-trait-and-ttyhandle.md
- docs/architecture/decisions/054-local-tty-backend-sibling-crate.md
- docs/architecture/decisions/055-exit-code-on-control-chunk.md
- docs/architecture/decisions/056-backend-cleanup-on-session-cancel.md
- docs/architecture/decisions/057-alknet-tty-no-alknet-call-dep.md
## Notes
> This is the final review before the tty work is considered complete. It
> validates the two crates as a unit — the core crate's invariants
> (exit-chunk-is-last, kill-on-Drop, framing disambiguation) must hold with
> the real local backend, not just the MockBackend. The cancel-cleanup
> integration tests are the most critical: an orphaned process on connection
> drop is the bug ADR-056 exists to prevent. The process-group signal test
> (REQ-TTY-02) is the other critical validation — Ctrl-C must reach a
> shell's children. If any invariant fails, document and fix before merge.
## Summary
> To be filled on completion