chore: bump to 0.8.2, changelog for the PTY exit-signal fidelity fix

- version 0.8.1 -> 0.8.2 (patch: behavior-only bug fix in the local
  feature's PTY waiter from db0b1fc; ADR-004 decision 1's negative-code
  convention now holds on the PTY path; no API, wire-format, or
  dependency change)
- CHANGELOG entry for 0.8.2 (review #004 resolution: the waitpid reap
  mechanism, the signal round-trip notes, the tests, the docs)
- finish the em-dash sweep in prose 58da42a missed: doc comments in
  tests/ (negotiation, pipe, pty, common) and fuzz/shared
  (control_json, negotiation_frame, session_opseq); prose rewrites
  only, no code, JSON literals, or wire shapes touched
- README checked: no version pins, nothing to update

Verification: cargo test; cargo test --all-features; cargo clippy
--all-targets -D warnings (default and --all-features); cargo fmt
--check; cargo doc --no-deps; fuzz/shared corpus replay (9 seeds pass);
cargo check + clippy for wasm32-unknown-unknown -D warnings; cargo
publish --dry-run --allow-dirty (43 files, 194.4 KiB); detached
campaigns over the four targets (chunk_frame 632s, negotiation_frame
602s, control_json 610s, session_opseq 608s) all exiting 0 with
0 crash/oom/timeout findings.
This commit is contained in:
glm-5.3-flash committed 2026-11-02 08:27:32 +00:00
1 parent 62f04c6856
commit a701247ca6
11 files changed
+79 -28

No files matched your search

+1 -1
View File
@@ -48,7 +48,7 @@ dependencies = [
[[package]]
name = "alktty"
version = "0.8.1"
version = "0.8.2"
dependencies = [
"alkcall",
"async-trait",
+1 -1
View File
@@ -10,7 +10,7 @@ pub fn fuzz_control_json(data: &[u8]) {
Err(e) => {
// Empty input yields the EOF-class serde error; non-empty
// garbage may be either EOF-class (truncated JSON) or a
// syntax/class error — both are clean rejections.
// syntax/class error; both are clean rejections.
if e.is_eof() {
// fine on any input shape
} else {
+4 -4
View File
@@ -67,7 +67,7 @@ pub fn fuzz_negotiation_frame(data: &[u8]) {
// The framing layer is length-prefix-only: an extra byte
// counted in the prefix still reads fine (the reader returns
// raw bytes; the *adapter's* serde parse of the body is what
// would reject trailing junk — from_slice on a longer buffer
// would reject trailing junk (from_slice on a longer buffer
// including the extra byte is the actual rejector). Assert the
// reader consumes exactly the claimed body: the extra byte is
// part of `frame`, not trailing garbage the reader ignores.
@@ -89,7 +89,7 @@ fn claimed_u32(bytes: [u8; 4]) -> u32 {
/// or negotiation frame under `MAX_CHUNK_LEN` starts with `0x00`, which is
/// never a server-sent `stream_type`, so a `ChunkReader` peek
/// distinguishes framing. A frame of exactly `MAX_CHUNK_LEN` has prefix
/// `[0x01, …]` — colliding with `STREAM_STDOUT`; the read paths admit it
/// `[0x01, …]`, colliding with `STREAM_STDOUT`; the read paths admit it
/// (`>` rejects, `==` passes) and only tiny error frames are ever sent, so
/// the boundary is pinned here rather than "fixed" (the framing shape is
/// wire-stable).
@@ -122,7 +122,7 @@ pub fn assert_error_frame_peek_disambiguation(frame: &[u8]) {
/// Rebuild an error-object frame through `error_response_bytes` and assert
/// the disambiguation invariant + fidelity. Only runs when the fuzz input
/// parses as a JSON object with an `"error"` string — the fuzzer's path
/// parses as a JSON object with an `"error"` string: the fuzzer's path
/// into `error_response_bytes`.
fn maybe_check_error_response(frame: &[u8]) {
let Ok(value) = serde_json::from_slice::<serde_json::Value>(frame) else {
@@ -278,7 +278,7 @@ fn assert_valid_shape(data: &[u8], frame: &Bytes) {
let len = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
assert!(
len <= MAX_CHUNK_LEN,
"accepted frame must be bounded (a zero-length frame is admitted by this reader — the adapter's serde parse rejects it — unlike alkcall's envelope framing)"
"accepted frame must be bounded: a zero-length frame is admitted by this reader, and the adapter's serde parse rejects it, unlike alkcall's envelope framing"
);
assert_eq!(frame.len(), len as usize);
assert_eq!(
+9 -9
View File
@@ -1,5 +1,5 @@
//! Invariants for the producer session pump (`pump_session`, driven via
//! `drive_session_pre_negotiated`) under adversarial op sequences — the
//! `drive_session_pre_negotiated`) under adversarial op sequences: the
//! alktty analog of alkcall's `manager_routing` target (the stateful
//! interleaving bug class, alkcall §7.8). The negotiation framing is out
//! of scope here (target `negotiation_frame` covers it); the request is a
@@ -7,10 +7,10 @@
//!
//! Encoded invariants:
//!
//! 1. **No panic** — anywhere: in the driver, in the pump tasks (asserted
//! 1. **No panic** anywhere: in the driver, in the pump tasks (asserted
//! via the session task's `JoinError`), or in the harness model.
//! 2. **Kill-on-Drop (ADR-005):** `kill_fired == !exit_resolved` after
//! teardown — the `exit_code` future is either driven to completion
//! teardown: the `exit_code` future is either driven to completion
//! (no kill) or dropped unresolved (kill attempted). With no
//! exit-resolution op issued the session is cancelled unresolved and
//! the kill MUST have fired; a resolved future MUST NOT fire the kill
@@ -31,19 +31,19 @@
//! (exact prefix, never a stray byte outside it); chunks issued after
//! the stdin shutdown (zero-length stdin chunk, `Eof` control, or
//! write-half close) are never delivered. Byte-exactness on the
//! completed path is deliberately NOT asserted — the adapter aborts
//! completed path is deliberately NOT asserted: the adapter aborts
//! the input pump after the exit chunk is enqueued, so in-flight
//! duplex bytes may be unprocessed (a timing window, not a bug);
//! unit tests own the exact-delivery cases.
//! 6. **Control dispatch:** resize/signal counters never exceed the
//! issued count; each issued message is dispatched promptly while no
//! exit resolution has raced the pump (the dispatch poll is skipped
//! once an exit op is issued — the pump may be aborted any time
//! once an exit op is issued; the pump may be aborted any time
//! after).
//! 7. **Take-once allocation:** the pre-negotiated driver allocates the
//! backend exactly once per session.
//! 8. **Termination:** teardown (abort + join) completes within the
//! harness bound — a wedged pump is a finding, not a hang.
//! harness bound: a wedged pump is a finding, not a hang.
//!
//! Harness bounds keep exec/s high: 128 ops, 4 KiB per chunk, 32 KiB
//! total produced and total client-written bytes (the 64 KiB duplex per
@@ -386,7 +386,7 @@ impl Harness {
Ok(Err(_)) => {
// The server half of the duplex is dropped only when the
// session task returns, and the session returns only after
// the drainer flushed the exit chunk — so a failed write
// the drainer flushed the exit chunk, so a failed write
// means the session completed while this write was in
// flight. Drain and verify; a close without the exit chunk
// is a premature close (a bug).
@@ -1079,7 +1079,7 @@ mod corpus_replay {
}
/// Semantic fixture (alkcall §7.7 pattern): cancel mid-session with an
/// unresolved exit — the kill guard MUST fire (ADR-005).
/// unresolved exit; the kill guard MUST fire (ADR-005).
#[test]
fn cancel_with_unresolved_exit_fires_the_kill_guard() {
fuzz_session_opseq(&SessionSequence {
@@ -1095,7 +1095,7 @@ mod corpus_replay {
}
/// Semantic fixture: stdin data, then the EOF sentinel, then an `Eof`
/// control, then a further stdin chunk (the trap) — the post-EOF chunk
/// control, then a further stdin chunk (the trap); the post-EOF chunk
/// must never reach the backend.
#[test]
fn stdin_after_eof_never_reaches_the_backend() {