Files
wz-phone/docs/PRD/TASKS.md
2026-05-12 06:25:17 +04:00

55 KiB
Raw Blame History

Haiku-Ready Task Breakdown

Companion to docs/PRD/README.md. Every task here is sized for an agent with limited context to pick up cold: it names exact files, exact symbols, and exact verification commands. Do tasks in order within a wave; waves are dependency-ordered.


Agent operating instructions — read first

You are an implementing agent. The human is the reviewer. Your job is not done when the code compiles; it is done when the reviewer has approved your report. Read this section before touching any task.

Workflow per task

  1. Claim the task. Move its status in the Status board at the bottom of this file from OpenIn Progress. Add your handle / model name and a UTC timestamp.
  2. Implement. Follow the steps in the task block exactly. If the steps don't fit reality (e.g. line numbers shifted, a referenced symbol doesn't exist, the API has evolved), stop and surface the mismatch in your report — do not improvise silently.
  3. Verify. Run the exact commands in the task's Verify block. Capture their output verbatim — the reviewer will read it.
  4. Write the report. Create docs/PRD/reports/T<id>-report.md using the template below. One report per task. No exceptions.
  5. Commit. One commit per task. Message: T<id>: <one-line summary>. The report file is part of the same commit.
  6. Move to review. Update the Status board: In ProgressPending Review. Add a link to the report path.
  7. Stop. Do NOT start the next task until the reviewer marks the previous one Approved. If they mark it Changes Requested, address the feedback in a follow-up commit, update the report, and move back to Pending Review.

Follow-up tasks (T<id>.<n>)

When the reviewer approves a task but finds small non-blocking issues (missing docs, stale comments, minor cleanups), they spawn new follow-up tasks instead of carrying the work forward into an unrelated task. The parent task stays Approved and closed.

Follow-up IDs extend the parent: T1.1.1, T1.1.2, etc. They are first-class tasks — full block in this file with Files, Steps, Verify, Done when — and they show up in the status board between the parent and the next sibling (T1.1.1 sits between T1.1 and T1.2).

Agents pick up follow-ups in the same order they pick up wave tasks. A follow-up never blocks the next wave task: e.g. T1.2 is claimable even if T1.1.1 is still Open, unless the follow-up's body explicitly says otherwise (it usually doesn't).

Reviewers, when spawning a follow-up:

  1. Add a numbered task block in the right section of this file (just below the parent).
  2. Add a status-board row between the parent and the next sibling.
  3. Reference the follow-up in the parent report's reviewer notes (e.g. "Spawned T1.1.1, T1.1.2 to track follow-ups.").

Report template

Every report lives at docs/PRD/reports/T<id>-report.md and uses this template:

# T<id> — <task title>

**Status:** Pending Review
**Agent:** <model/handle>
**Started:** <UTC ISO-8601>
**Completed:** <UTC ISO-8601>
**Commit:** <git sha>
**PRD:** ../<prd-filename>.md

## What I changed

- `<file path>:<line range>` — <one-line description of the change>
- `<file path>:<line range>` — <one-line description>
- (etc.)

## Why these choices

<2-6 sentences explaining any non-obvious decision: why this signature, why
this default, why this error type, why a deviation from the task steps if any.
If you followed the steps verbatim, say "Followed steps T<id>.1 through T<id>.N
without deviation." and that's enough.>

## Deviations from the task spec

<If you deviated from a numbered step, list each deviation with: which step,
what you did instead, why. If none, write "None.">

## Verification output

For each `Verify` command in the task block, paste the actual output. Trim
benign noise (warnings already present on main) but never trim test failure
output.

$ cargo test -p wzp-proto media_header_v2_roundtrip running 1 test test packet::tests::media_header_v2_roundtrip ... ok

test result: ok. 1 passed; 0 failed; ...


## Test summary

- Tests added: <count + names>
- Tests modified: <count + names>
- Workspace test count before: <N> / after: <M>
- `cargo clippy --workspace --all-targets -- -D warnings`: pass / fail (or N known-debt errors in <crate>; see PROTOCOL-AUDIT.md)
- `cargo fmt --all -- --check`: pass / fail

## Risks / follow-ups

<Anything the reviewer should know that isn't a bug: a TODO I left, a test I
couldn't write because of a missing fixture, a downstream task this enables
or blocks, an assumption I made that should be confirmed. If there are none,
write "None.">

## Reviewer checklist (filled in by reviewer)

- [ ] Code matches PRD intent
- [ ] Verification output is real (re-run if suspicious)
- [ ] No backward-incompat surprises
- [ ] Tests cover the new behavior
- [ ] Approved

Coding standards — non-negotiable

These apply to every task. They are NOT repeated in each task block. Violating them is grounds for Changes Requested even if the code works.

  1. Rust edition 2024 (set in workspace root). No exceptions.
  2. cargo fmt --all must produce a clean diff before commit. CI will reject otherwise.
  3. cargo clippy --workspace --all-targets -- -D warnings must pass in crates you touch. Do not #[allow(...)] to silence — fix the root cause. If a lint is genuinely wrong, justify the allow in the report. Pre-existing debt in other crates (documented in PROTOCOL-AUDIT.md) is not your problem.
  4. No unwrap() / expect() in production code paths. Tests are fine. Production: return a typed error.
  5. No println! / eprintln!. Use tracing::{debug,info,warn,error}!. The crates are already wired for tracing.
  6. No new dependencies without justification. If a task forces a new crate, list it under "Risks / follow-ups" in the report so the reviewer can sanity-check the supply chain.
  7. One commit per task — see workflow. Don't squash multiple tasks. Don't split a task across commits unless the task itself instructs you to.
  8. Never modify Cargo.lock by hand. Run a real build; commit the resulting lockfile delta.
  9. Public API changes need rustdoc. Every new pub fn, pub struct, pub enum, or pub trait gets a /// doc comment. Private items: doc only when non-obvious.
  10. Tests live with code. #[cfg(test)] mod tests { ... } next to the code under test. Integration tests in crates/<x>/tests/<name>.rs only when they exercise multiple modules end-to-end.
  11. Async: tokio only. Do not introduce async-std or smol. Spawn via tokio::spawn, not raw futures.
  12. Wire format types live in wzp-proto. Do not redefine MediaHeader, SignalMessage, or codec/quality types in another crate. Re-export if needed.
  13. No emoji in code or commit messages unless the surrounding context already uses them.
  14. No AI-attribution lines in commit messages. Plain T<id>: <summary> body, that's it.
  15. Comments: comment WHY, never WHAT. If the code needs a WHAT comment, rename the symbol instead. See repo-root CLAUDE.md (if present) for global guidance.
  16. Don't take destructive actions. Specifically: never git reset --hard, git push --force, drop database tables, delete branches, or touch CI configs without the reviewer asking. If you think you need to, stop and ask in your report.
  17. Auto mode is not a license to skip these. Even when the harness is set to autonomous execution, the workflow (report → Pending Review → wait for Approved) is mandatory.

When to stop and ask

Stop and write a report with status Blocked (not Pending Review) if any of these happen:

  • A task step references code that doesn't exist.
  • A test fails for reasons unrelated to your change.
  • The workspace doesn't build at HEAD before you started (the baseline is dirty).
  • You need to make a meaningful design decision the task didn't anticipate.
  • A "Verify" command produces output you don't understand.

A Blocked report is not a failure — it is the correct outcome when the task spec is wrong or incomplete.


How to read a task

Each task block has:

  • ID & titleT<wave>.<n> like T1.1.
  • PRD — link to the parent PRD for the "why".
  • Effort — rough hours for a junior dev with this doc + the repo.
  • Files — exact paths you will edit.
  • Context — 2-4 lines on what's there today.
  • Steps — numbered, do them in order.
  • Verify — exact commands; output must match.
  • Done when — single-line acceptance.

Environment setup (do this once)

# All commands assume CWD = /Users/manwe/CascadeProjects/warzonePhone
cargo build --workspace                 # baseline: must succeed
cargo test --workspace --no-fail-fast   # baseline: should be 571 pass / 0 fail (non-Android subset)

If either fails before you start a task, stop and report — the tree is dirty.

Conventions

  • Format on save: cargo fmt --all after any code change.
  • Lints: cargo clippy --workspace --all-targets -- -D warnings must pass in crates you touch before commit. Pre-existing debt in other crates is documented in PROTOCOL-AUDIT.md.
  • Tests live next to code under #[cfg(test)] modules, or in crates/<x>/tests/.
  • Wire format types: crates/wzp-proto/src/packet.rs is authoritative. Do not duplicate field semantics elsewhere.
  • Commit one task per commit. Reference task ID in commit message: T1.1: widen MediaHeader to v2.

Useful greps

grep -rn "MediaHeader::" --include="*.rs"    # 6 files outside tests
grep -rn "MiniHeader::" --include="*.rs"
grep -rn "SignalMessage::" --include="*.rs"
grep -rn "CodecId::" --include="*.rs"

Wave 1 — Foundation (target: 1 week)

Goal: v2 wire format lands cleanly. Audio works under v2. Old clients are politely rejected.


T1.1 — Add v2 MediaHeader type

  • PRD: PRD-wire-format-v2.md
  • Effort: 3 h
  • Files:
    • crates/wzp-proto/src/packet.rs

Context

Today MediaHeader is defined at line 20 of packet.rs with WIRE_SIZE = 12 (line 47). Fields are bit-packed across the first two bytes. It is constructed in tests starting around line 1229.

Steps

  1. Open crates/wzp-proto/src/packet.rs.

  2. Do not delete the existing MediaHeader. Rename it in-place to MediaHeaderV1 (also rename WIRE_SIZE consts only on that struct). Keep all impls.

  3. Below the MediaHeaderV1 block, add a new MediaHeader struct (16 bytes, byte-aligned):

    /// 16-byte v2 media header. See docs/PRD/PRD-wire-format-v2.md.
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub struct MediaHeader {
        pub version: u8,           // always 2
        pub flags: u8,             // bit 7 T, bit 6 Q, bit 5 KeyFrame, bit 4 FrameEnd
        pub media_type: MediaType, // u8 wire repr
        pub codec_id: CodecId,
        pub stream_id: u8,
        pub fec_ratio: u8,         // 0..200 → 0.0..2.0
        pub seq: u32,
        pub timestamp: u32,
        pub fec_block: u16,
    }
    
    impl MediaHeader {
        pub const WIRE_SIZE: usize = 16;
        pub const VERSION: u8 = 2;
    
        pub fn write_to(&self, buf: &mut impl BufMut) {
            buf.put_u8(self.version);
            buf.put_u8(self.flags);
            buf.put_u8(self.media_type.to_wire());
            buf.put_u8(self.codec_id.to_wire());
            buf.put_u8(self.stream_id);
            buf.put_u8(self.fec_ratio);
            buf.put_u32(self.seq);
            buf.put_u32(self.timestamp);
            buf.put_u16(self.fec_block);
        }
    
        pub fn read_from(buf: &mut impl Buf) -> Option<Self> {
            if buf.remaining() < Self::WIRE_SIZE { return None; }
            let version = buf.get_u8();
            if version != Self::VERSION { return None; }
            let flags = buf.get_u8();
            let media_type = MediaType::from_wire(buf.get_u8())?;
            let codec_id = CodecId::from_wire(buf.get_u8())?;
            let stream_id = buf.get_u8();
            let fec_ratio = buf.get_u8();
            let seq = buf.get_u32();
            let timestamp = buf.get_u32();
            let fec_block = buf.get_u16();
            Some(Self { version, flags, media_type, codec_id, stream_id, fec_ratio, seq, timestamp, fec_block })
        }
    
        pub const FLAG_REPAIR: u8     = 0b1000_0000;
        pub const FLAG_QUALITY: u8    = 0b0100_0000;
        pub const FLAG_KEYFRAME: u8   = 0b0010_0000;
        pub const FLAG_FRAME_END: u8  = 0b0001_0000;
    
        pub fn is_repair(&self) -> bool  { self.flags & Self::FLAG_REPAIR != 0 }
        pub fn has_quality(&self) -> bool { self.flags & Self::FLAG_QUALITY != 0 }
        pub fn is_keyframe(&self) -> bool { self.flags & Self::FLAG_KEYFRAME != 0 }
        pub fn is_frame_end(&self) -> bool { self.flags & Self::FLAG_FRAME_END != 0 }
    }
    
  4. MediaType and CodecId::to_wire (8-bit) come from T1.2 and T1.3 — add a // TODO(T1.2) placeholder if those aren't merged yet (use u8 directly).

  5. Add a round-trip test next to the existing tests:

    #[test]
    fn media_header_v2_roundtrip() {
        let h = MediaHeader {
            version: 2, flags: MediaHeader::FLAG_QUALITY,
            media_type: MediaType::Audio, codec_id: CodecId::Opus24k,
            stream_id: 0, fec_ratio: 50,
            seq: 0xDEAD_BEEF, timestamp: 0x1234_5678,
            fec_block: 0xABCD,
        };
        let mut buf = BytesMut::with_capacity(MediaHeader::WIRE_SIZE);
        h.write_to(&mut buf);
        assert_eq!(buf.len(), 16);
        let mut cursor = std::io::Cursor::new(&buf[..]);
        let parsed = MediaHeader::read_from(&mut cursor).unwrap();
        assert_eq!(h, parsed);
    }
    

Verify

cargo test -p wzp-proto media_header_v2_roundtrip
cargo build --workspace

Done when

  • New test passes. Workspace still builds. MediaHeaderV1 still exists (we delete it later in T1.5).

T1.1.1 — Add rustdoc on MediaHeaderV2 public fields

  • Parent: T1.1 (Approved)
  • PRD: PRD-wire-format-v2.md
  • Effort: 15 min
  • Files:
    • crates/wzp-proto/src/packet.rs

Context

T1.1 added MediaHeaderV2 with inline // comments on the public fields. The pre-existing MediaHeaderV1 uses /// rustdoc on every public field (coding standard #9 — public items need rustdoc). Match the existing pattern.

Steps

  1. Open crates/wzp-proto/src/packet.rs. Find pub struct MediaHeaderV2.

  2. For each public field, replace the trailing // comment with a leading /// doc comment. Example transformation:

    Before:

    pub struct MediaHeaderV2 {
        pub version: u8,    // always 2
        pub flags: u8,      // bit 7 T, bit 6 Q, bit 5 KeyFrame, bit 4 FrameEnd
        ...
    }
    

    After:

    pub struct MediaHeaderV2 {
        /// Protocol version. Always `2` on the wire; `read_from` rejects anything else.
        pub version: u8,
        /// Bit-packed flags. See `FLAG_REPAIR`, `FLAG_QUALITY`, `FLAG_KEYFRAME`, `FLAG_FRAME_END`.
        pub flags: u8,
        ...
    }
    
  3. Document the four FLAG_* constants with /// too. One line each is fine.

  4. Document the four is_* / has_* accessor methods with ///. One line each.

  5. The media_type: u8 field gets a doc comment that mentions the TODO(T1.2) — keep that TODO inline.

Verify

cargo doc -p wzp-proto --no-deps 2>&1 | grep -i "missing"   # should be empty
cargo clippy -p wzp-proto --all-targets -- -D warnings -W missing_docs   # should pass

Done when

  • All public items on MediaHeaderV2 carry /// doc comments.
  • cargo doc -p wzp-proto --no-deps emits no "missing documentation" warnings for MediaHeaderV2.

T1.1.2 — Refresh stale test-count figures in docs

  • Parent: T1.1 (Approved)
  • PRD: PRD-wire-format-v2.md (housekeeping)
  • Effort: 30 min
  • Files:
    • docs/ARCHITECTURE.md
    • docs/PRD/TASKS.md (the Environment setup block)
    • Any other doc referencing "272 tests"

Context

The original audit and the TASKS environment-setup block reference a workspace test count of 272. The actual non-Android workspace baseline measured during T1.1 is 564 (with 1 added test → 565 after T1.1). The 272 figure is stale.

Steps

  1. Grep for the stale figure across the docs:
    grep -rn "272 tests\|272 pass\|272 total" docs/
    
  2. For each hit, replace with the current count. Re-measure before writing the number.
    cargo test --workspace --no-fail-fast 2>&1 | grep "test result:" | awk '{s+=$4} END {print s}'
    # ... this gives a rough total; sanity-check against per-crate output
    
  3. If wzp-android cannot build on the dev machine (no NDK), note that the count excludes wzp-android and is the "non-Android subset".
  4. Update the per-crate Test Coverage table in docs/ARCHITECTURE.md (search for "## Test Coverage") with the new per-crate counts.

Verify

grep -rn "272 tests\|272 pass" docs/   # should be empty

Done when

  • No doc references the stale 272 figure.
  • ARCHITECTURE.md test coverage table reflects current per-crate counts.

T1.2 — Add MediaType enum

  • PRD: PRD-wire-format-v2.md
  • Effort: 1 h
  • Files:
    • crates/wzp-proto/src/codec_id.rs (or new sibling file media_type.rs)
    • crates/wzp-proto/src/lib.rs (re-export)

Steps

  1. Create crates/wzp-proto/src/media_type.rs:
    use serde::{Deserialize, Serialize};
    
    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
    #[repr(u8)]
    pub enum MediaType {
        Audio = 0,
        Video = 1,
        Data = 2,
        Control = 3,
    }
    
    impl MediaType {
        pub const fn to_wire(self) -> u8 { self as u8 }
        pub const fn from_wire(v: u8) -> Option<Self> {
            match v {
                0 => Some(Self::Audio),
                1 => Some(Self::Video),
                2 => Some(Self::Data),
                3 => Some(Self::Control),
                _ => None,
            }
        }
    }
    
  2. In crates/wzp-proto/src/lib.rs, add pub mod media_type; and pub use media_type::MediaType;.

Verify

cargo build -p wzp-proto
cargo test -p wzp-proto

Done when

MediaType is importable as wzp_proto::MediaType.


T1.2.1 — Add rustdoc on MediaType variants and methods

  • Parent: T1.2 (Approved)
  • PRD: PRD-wire-format-v2.md
  • Effort: 10 min
  • Files:
    • crates/wzp-proto/src/media_type.rs

Context

T1.2 created MediaType with a one-line top-level doc comment but no /// rustdoc on the variants (Audio, Video, Data, Control) or methods (to_wire, from_wire). Coding standard #9 — public items need rustdoc. Same shape of follow-up as T1.1.1.

Steps

  1. Open crates/wzp-proto/src/media_type.rs.
  2. Add a /// doc comment to each variant. Examples (do not just copy — pick what's accurate):
    pub enum MediaType {
        /// Encoded speech / music (Opus, Codec2, ComfortNoise).
        Audio = 0,
        /// Encoded video access unit (H.264, H.265, AV1; PRD-video-multicodec).
        Video = 1,
        /// Opaque payload not interpreted by the relay (reserved).
        Data = 2,
        /// In-band control message carried on the media plane (reserved).
        Control = 3,
    }
    
  3. Add a /// doc on to_wire and from_wire. One line each is fine — explain the wire byte mapping and the None case.

Verify

cargo doc -p wzp-proto --no-deps 2>&1 | grep -i "missing" || echo "no missing-doc warnings"
cargo clippy -p wzp-proto --all-targets -- -D warnings -W missing_docs

Done when

  • All variants and methods on MediaType carry /// doc comments.
  • cargo doc -p wzp-proto --no-deps emits no "missing documentation" warnings for MediaType.

T1.3 — Widen CodecId wire representation to u8

  • PRD: PRD-wire-format-v2.md (resolves audit W9)
  • Effort: 1 h
  • Files:
    • crates/wzp-proto/src/codec_id.rs

Context

CodecId::to_wire returns self as u8 (already u8 in memory). The "4 bits on wire" is enforced by how MediaHeaderV1 packs it. With v2 the wire byte is full 8-bit — so reserve more IDs without touching to_wire/from_wire for the existing variants.

Steps

  1. In codec_id.rs, reserve (but do not implement) future codec IDs by adding doc comments after Opus64k = 8:
    // Reserved for video codecs; implementations land in PRD-video-multicodec.
    // 9  => H264 baseline
    // 10 => H264 main
    // 11 => H265 main
    // 12 => AV1
    // 13 => VP9
    
  2. Do not add new variants yet — that happens in T4.x once wzp-video exists.
  3. Add a regression test confirming from_wire(9..=255) returns None:
    #[test] fn codec_id_unknown_values_rejected() {
        for v in 9u8..=255 { assert!(CodecId::from_wire(v).is_none(), "v={v}"); }
    }
    

Verify

cargo test -p wzp-proto codec_id_unknown_values_rejected

Done when

Test passes. Existing audio tests still pass.


T1.4 — Add v2 MiniHeader with seq_delta

  • PRD: PRD-wire-format-v2.md (resolves audit W4)
  • Effort: 2 h
  • Files:
    • crates/wzp-proto/src/packet.rs

Context

Existing MiniHeader is 4 bytes at line 501. MiniFrameContext::expand infers seq by wrapping_add(1) (line ~553) — a missed full header desyncs. v2 carries explicit seq_delta.

Steps

  1. Rename existing MiniHeaderMiniHeaderV1 and MiniFrameContextMiniFrameContextV1. Keep impls intact.
  2. Add new MiniHeader (5 bytes):
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub struct MiniHeader {
        pub seq_delta: u8,            // packets since baseline; 1 in steady state
        pub timestamp_delta_ms: u16,
        pub payload_len: u16,
    }
    
    impl MiniHeader {
        pub const WIRE_SIZE: usize = 5;
    
        pub fn write_to(&self, buf: &mut impl BufMut) {
            buf.put_u8(self.seq_delta);
            buf.put_u16(self.timestamp_delta_ms);
            buf.put_u16(self.payload_len);
        }
    
        pub fn read_from(buf: &mut impl Buf) -> Option<Self> {
            if buf.remaining() < Self::WIRE_SIZE { return None; }
            Some(Self {
                seq_delta: buf.get_u8(),
                timestamp_delta_ms: buf.get_u16(),
                payload_len: buf.get_u16(),
            })
        }
    }
    
  3. Add MiniFrameContext (no V1 suffix) tracking v2 MediaHeader:
    #[derive(Clone, Debug, Default)]
    pub struct MiniFrameContext {
        last: Option<MediaHeader>,
    }
    impl MiniFrameContext {
        pub fn update(&mut self, h: &MediaHeader) { self.last = Some(*h); }
        pub fn expand(&mut self, m: &MiniHeader) -> Option<MediaHeader> {
            let base = self.last.as_ref()?;
            let mut e = *base;
            e.seq = base.seq.wrapping_add(m.seq_delta as u32);
            e.timestamp = base.timestamp.wrapping_add(m.timestamp_delta_ms as u32);
            self.last = Some(e);
            Some(e)
        }
    }
    
  4. Add round-trip test mirroring T1.1.

Verify

cargo test -p wzp-proto mini

Done when

v2 mini header round-trips. v1 type still compiles.


T1.4.1 — Add rustdoc on MiniHeaderV2 / MiniFrameContextV2 public items

  • Parent: T1.4 (Approved)
  • PRD: PRD-wire-format-v2.md
  • Effort: 15 min
  • Files:
    • crates/wzp-proto/src/packet.rs

Context

T1.4 added v2 types with top-level /// docs on the structs themselves but no /// rustdoc on the fields or methods. Same shape of follow-up as T1.1.1 and T1.2.1 — coding standard #9.

Steps

  1. Open crates/wzp-proto/src/packet.rs. Find pub struct MiniHeaderV2.
  2. Add /// doc comments to each public field:
    • seq_delta — explain it's the count of packets since the baseline (typically 1), and that explicit deltas resolve audit W4 (one missed full header no longer desyncs).
    • timestamp_delta_ms — milliseconds since baseline's timestamp.
    • payload_len — bytes of payload following the mini header.
  3. Document WIRE_SIZE, write_to, read_from. One line each. Mention that read_from returns None on short buffer.
  4. Same treatment for MiniFrameContextV2: doc the update and expand methods. expand should note that it returns None if no baseline has been set.

Verify

cargo doc -p wzp-proto --no-deps 2>&1 | grep -i "missing" || echo "no missing-doc warnings"
cargo clippy -p wzp-proto --all-targets -- -D warnings -W missing_docs

Done when

  • All public items on MiniHeaderV2 and MiniFrameContextV2 carry /// doc comments.
  • cargo doc -p wzp-proto --no-deps emits no "missing documentation" warnings for these types.

T1.5 — Migrate emit/parse sites to v2

  • PRD: PRD-wire-format-v2.md
  • Effort: 4 h
  • Files (touch all that use MediaHeader::):
    • crates/wzp-proto/src/packet.rs
    • crates/wzp-client/src/call.rs
    • crates/wzp-relay/src/room.rs
    • crates/wzp-relay/src/pipeline.rs
    • crates/wzp-android/src/engine.rs

Context

Only 6 production files outside packet.rs reference MediaHeader::. Confirm with:

grep -rln "MediaHeader::" crates/ | grep -v target

Steps

  1. For each file in the list above, replace v1 construction patterns with v2. The audio fields are unchanged in semantics; new fields default as follows:
    • version: 2
    • flags: 0 (set FLAG_QUALITY where the v1 code set has_quality_report = true, etc.)
    • media_type: MediaType::Audio
    • stream_id: 0
    • fec_ratio: <old fec_ratio_encoded * 200 / 127> (convert range)
    • seq: old_seq as u32
    • timestamp unchanged
    • fec_block: u16::from(old_fec_block) | (u16::from(old_fec_symbol) << 8) for audio (low byte block_id, high byte symbol_idx)
  2. Update MediaHeaderV1-using parse code identically — convert from u16 seq/u8 block_id to v2 layout at parse boundary.
  3. Search for WIRE_SIZE arithmetic and update buffer sizes (12 → 16, 4 → 5).
  4. Delete MediaHeaderV1, MiniHeaderV1, MiniFrameContextV1 once everything builds.

Verify

cargo build --workspace
cargo test --workspace --no-fail-fast
# Expected: all 571 tests still pass

Done when

  • Workspace builds clean.
  • All audio tests pass.
  • No reference to MediaHeaderV1 / MiniHeaderV1 anywhere.

T1.5.1 — Remove unwrap() from encode_compact

  • Parent: T1.5 (Approved)
  • PRD: PRD-wire-format-v2.md (cleanup)
  • Effort: 20 min
  • Files:
    • crates/wzp-proto/src/packet.rs

Context

encode_compact calls ctx.last_header().unwrap() at line ~262. The invariant ("a full header is forced on the first frame and every MINI_FRAME_FULL_INTERVAL frames") makes it logically safe, but standard #4 forbids unwrap() in production paths. Carried over from v1.

Steps

  1. Open crates/wzp-proto/src/packet.rs. Find pub fn encode_compact.
  2. Replace the unwrap() with one of:
    • Recommended: when ctx.last_header() is None, fall back to emitting a full frame and force frames_since_full = 0. This makes the invariant explicit in the code rather than implicit.
    • Alternative: return Result<Bytes, EncodeError> with a typed NoBaselineHeader variant. More invasive (changes the public signature).
  3. Add a test that constructs a fresh MiniFrameContext and calls encode_compact immediately — without the existing fix, this would panic; with the fix, it should emit a full frame.

Verify

cargo test -p wzp-proto encode_compact
cargo clippy -p wzp-proto --all-targets -- -D warnings
grep -n "\.unwrap()" crates/wzp-proto/src/packet.rs | grep -v "#\[cfg(test)\]\|^[[:space:]]*//\|tests::"
# the unwrap on line ~262 should be gone; only test-code unwraps remain.

Done when

  • No unwrap() in encode_compact or anywhere else in non-test code in packet.rs.
  • New test passes; existing encode_compact tests still pass.

T1.5.2 — Workspace clippy hygiene + document pre-existing debt

  • Parent: T1.5 (Approved)
  • PRD: PRD-wire-format-v2.md (process)
  • Effort: 30 min
  • Files:
    • docs/PROTOCOL-AUDIT.md (add a "Known pre-existing clippy debt" section)
    • This file (TASKS.md) — update report template instruction to require workspace clippy

Context

T1.5 review revealed two issues: (1) the agent ran only -p wzp-proto clippy, not workspace; (2) workspace clippy fails with 9 wzp-codec errors and 3 warzone-protocol errors. Both are pre-existing (verified against HEAD~1). Need to capture these as known debt so they don't stay invisible, and tighten the report template to require workspace clippy on every task.

Steps

  1. Run cargo clippy --workspace --all-targets -- -D warnings 2>&1 | grep -E "^error\[|could not compile" | head -50 and capture the output.
  2. Add a section to docs/PROTOCOL-AUDIT.md named "Known pre-existing clippy debt (as of T1.5.2)" listing the failing crates and a brief description per error category (manual ASCII case-cmp, manual arithmetic check, loop index, etc.). Reference the commit SHA of HEAD at time of measurement.
  3. In docs/PRD/TASKS.md, update the report template's "Test summary" section: change cargo clippy --workspace --all-targets -- -D warnings: pass / fail to cargo clippy --workspace --all-targets -- -D warnings: pass / fail (or N known-debt errors in <crate>; see PROTOCOL-AUDIT.md). This makes the expectation explicit and gives agents a way to acknowledge known debt without re-discussing it every task.
  4. Optional: add a make clippy-baseline or similar script to tools/ that prints expected-error count so agents can detect regressions.

Verify

grep -c "Known pre-existing clippy debt" docs/PROTOCOL-AUDIT.md   # >= 1
grep -c "or N known-debt errors" docs/PRD/TASKS.md                # >= 1

Done when

  • PROTOCOL-AUDIT.md has the known-debt section with current error counts and categories.
  • TASKS.md report template reflects the new expectation.
  • A follow-up cleanup task is created in the audit (separate from this one) to actually fix the pre-existing debt over time.

T1.6 — Protocol version negotiation in handshake

  • PRD: PRD-wire-format-v2.md + PRD-protocol-hardening.md (W12)
  • Effort: 3 h
  • Files:
    • crates/wzp-proto/src/packet.rs (extend SignalMessage)
    • crates/wzp-client/src/handshake.rs
    • crates/wzp-relay/src/handshake.rs

Steps

  1. In packet.rs, add to CallOffer:
    #[serde(default = "default_proto_version")]
    pub protocol_version: u8,
    #[serde(default = "default_supported_versions")]
    pub supported_versions: Vec<u8>,
    
    Helpers:
    fn default_proto_version() -> u8 { 2 }
    fn default_supported_versions() -> Vec<u8> { vec![2] }
    
  2. Add a new Hangup reason variant. Find SignalMessage::Hangup (look for the Hangup variant in the enum near the bottom) and add to the reason enum / fields:
    ProtocolVersionMismatch { server_supported: Vec<u8> },
    
    If reason is a String, instead add a structured variant SignalMessage::ProtocolVersionMismatch { server_supported: Vec<u8> } and use that.
  3. In crates/wzp-relay/src/handshake.rs, after parsing CallOffer, check protocol_version == 2. If not, send ProtocolVersionMismatch and close.
  4. In crates/wzp-client/src/handshake.rs, set the field on outgoing CallOffer; on receiving the mismatch variant, return a typed error.

Verify

cargo test -p wzp-relay handshake
cargo test -p wzp-client handshake

Done when

A v1-style offer (missing protocol_version field — serde default makes it 2 in this codebase, so explicitly test with protocol_version: 1) is rejected with the typed signal.


T1.7 — Move QualityReport trailer inside AEAD payload

  • PRD: PRD-protocol-hardening.md (W5)
  • Effort: 2 h
  • Files:
    • crates/wzp-client/src/call.rs (encode/decode paths)
    • crates/wzp-crypto/src/session.rs (verify AEAD boundary)

Context

A QualityReport (4 bytes) is appended to media packets when the Q flag is set. The flag is in the (plaintext, AAD-bound) header; the trailer must sit inside the AEAD payload so stripping it corrupts decryption.

Steps

  1. Grep for the encode site:
    grep -rn "has_quality_report\|FLAG_QUALITY\|QualityReport" crates/wzp-client/src/call.rs
    
  2. Find where QualityReport::write_to (or put_* calls) writes the 4 bytes. Confirm it writes into the buffer that is then passed to encrypt_in_place / seal — not after.
  3. If currently appended after AEAD seal: refactor so the order is:
    • Write MediaHeader (becomes AAD).
    • Write payload.
    • Write QualityReport trailer if Q flag set.
    • AEAD-seal the (payload + trailer) bytes with header as AAD.
  4. Mirror on decode side.
  5. Add a test that tampers with the trailer post-encrypt and asserts decrypt fails.

Verify

cargo test -p wzp-client quality_report_aead
cargo test -p wzp-crypto

Done when

  • Tamper test passes (decryption fails on trailer tamper).
  • Round-trip with quality flag set still works.

T1.8 — Per-stream anti-replay window with configurable size

  • PRD: PRD-protocol-hardening.md (W11)
  • Effort: 2 h
  • Files:
    • crates/wzp-crypto/src/anti_replay.rs
    • crates/wzp-crypto/src/session.rs (or wherever the window is owned)

Steps

  1. Today the window is fixed 64 packets. Make it constructible with size:
    impl AntiReplay { pub fn with_window(size: usize) -> Self { ... } }
    
  2. The session owner (search AntiReplay::new) is updated to allocate per (stream_id, MediaType). Use a HashMap<(u8, MediaType), AntiReplay> keyed on the v2 header fields.
  3. Default sizes:
    • Audio: 64
    • Video: 1024
    • Data: 256
    • Control: 32

Verify

cargo test -p wzp-crypto anti_replay

Done when

  • A new test confirms a 200-packet video burst with one reorder doesn't drop any.
  • Existing audio anti-replay tests pass.

Wave 2 — Feedback + abuse mitigation (target: 1 week)

Goal: BWE drives adaptation. Tier A/B/C conformance running in observe-only mode at the relay.


T2.1 — Add SignalMessage::TransportFeedback

  • PRD: PRD-transport-feedback-bwe.md
  • Effort: 2 h
  • Files:
    • crates/wzp-proto/src/packet.rs

Steps

  1. Add to the SignalMessage enum:
    TransportFeedback {
        #[serde(default)] version: u8,        // = 1
        stream_id: u8,
        acked_seqs: Vec<u32>,
        nacked_seqs: Vec<u32>,
        remb_bps: u32,
        recv_time_us: u64,
    },
    
  2. Add a unit test serializing/deserializing with bincode to ensure forward/backward compat.

Verify

cargo test -p wzp-proto transport_feedback

Done when

Variant round-trips. No other code consumes it yet — that's T2.2/T2.3.


T2.2 — BandwidthEstimator in wzp-proto::bandwidth

  • PRD: PRD-transport-feedback-bwe.md
  • Effort: 4 h
  • Files:
    • crates/wzp-proto/src/bandwidth.rs (already exists — extend, don't replace)
    • crates/wzp-transport/src/path_monitor.rs (read existing cwnd/RTT exposure)

Context

bandwidth.rs already exists (14 KB). Read it first. The QuinnPathSnapshot type exposes loss_pct, rtt_ms today; add cwnd_bps, bytes_in_flight if missing.

Steps

  1. Read crates/wzp-transport/src/path_monitor.rs to find how Quinn PathStats are exposed.
  2. Add to QuinnPathSnapshot:
    pub cwnd_bytes: u64,
    pub bytes_in_flight: u64,
    
    Populate from quinn::Connection::stats().path.
  3. In wzp-proto/src/bandwidth.rs, add:
    pub struct BandwidthEstimator {
        cwnd_bps: AtomicU64,
        peer_remb_bps: AtomicU64,
        smoothed_bps: AtomicU64,
    }
    impl BandwidthEstimator {
        pub fn new() -> Self { ... default ... }
        pub fn update_from_quinn(&self, snap: &QuinnPathSnapshot) { /* compute cwnd_bps = cwnd_bytes * 8 / rtt_s */ }
        pub fn update_from_peer(&self, fb_remb_bps: u32) { ... }
        pub fn target_send_bps(&self) -> u64 {
            let m = self.cwnd_bps.load(Relaxed).min(self.peer_remb_bps.load(Relaxed));
            (m as f64 * 0.9) as u64
        }
    }
    
  4. EWMA smoothing: half-life 2 s. Update smoothed_bps from input on each tick.

Verify

cargo test -p wzp-proto bandwidth
cargo test -p wzp-transport

Done when

  • Unit test: feed scripted cwnd + remb values, assert target_send_bps smooths correctly.

T2.3 — Plumb BWE into adaptive controller

  • PRD: PRD-transport-feedback-bwe.md
  • Effort: 3 h
  • Files:
    • crates/wzp-proto/src/quality.rs (AdaptiveQualityController)
    • crates/wzp-client/src/call.rs (instantiate + feed)

Steps

  1. Add a setter to AdaptiveQualityController:
    pub fn set_bandwidth_estimator(&mut self, bwe: Arc<BandwidthEstimator>) { self.bwe = Some(bwe); }
    
  2. In the controller's upgrade decision (search for "consecutive_good_reports" or similar threshold logic), add a guard:
    if let Some(bwe) = &self.bwe {
        if bwe.target_send_bps() < self.current_tier_ceiling_bps() * 130 / 100 { return; }
    }
    
  3. In call.rs, instantiate one Arc<BandwidthEstimator> per session, feed it from both send loop (update_from_quinn from path snapshot) and recv loop (update_from_peer from incoming TransportFeedback), pass to the controller.

Verify

cargo test -p wzp-proto quality

Done when

Existing quality tests pass with BWE attached. New test: scripted "loss = 0, cwnd = 50 kbps" never upgrades past Opus 24k.


T2.4 — Relay conformance: Tier A (bitrate ceiling)

  • PRD: PRD-relay-conformance.md
  • Effort: 3 h
  • Files:
    • crates/wzp-relay/src/conformance.rs (new)
    • crates/wzp-relay/src/room.rs (call site)

Steps

  1. Create crates/wzp-relay/src/conformance.rs:
    use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
    use std::time::Instant;
    use wzp_proto::{CodecId, MediaHeader, MediaType};
    
    pub struct ConformanceMeter {
        window_start: parking_lot::Mutex<Instant>,
        bytes_in_window: AtomicU64,
        packets_in_window: AtomicU64,
        last_seq: AtomicU64,
        last_ts: AtomicU64,
    }
    
    #[derive(Debug)]
    pub enum Violation { BitrateExceeded, PacketRateExceeded, TimestampDrift }
    
    impl ConformanceMeter {
        pub fn new() -> Self { ... }
        pub fn observe(&self, h: &MediaHeader, payload_len: usize, now: Instant) -> Result<(), Violation> {
            // Tier A
            let window_bytes = self.bytes_in_window.fetch_add((MediaHeader::WIRE_SIZE + payload_len) as u64, Relaxed);
            // ... compare against ceiling_bps_for(h.codec_id, h.media_type)
        }
    }
    
    pub fn ceiling_bps(codec: CodecId) -> u64 {
        let nominal = codec.bitrate_bps() as u64;
        (nominal * 3 * 115 / 100).max(2_000)   // FEC 2.0 + 15% overhead, floor 2 kbps
    }
    
  2. In room.rs, attach one ConformanceMeter per participant. Call observe on each incoming media packet.
  3. Observe-only mode for now. Log violations to tracing::warn! and bump a Prometheus counter. Do not close session.

Verify

cargo test -p wzp-relay conformance

Done when

Unit test: synthetic 1 MB/s declared as Opus 24k logs Violation::BitrateExceeded.


T2.5 — Tier B (packet-rate) + Tier C (timestamp drift)

  • PRD: PRD-relay-conformance.md
  • Effort: 2 h
  • Files:
    • crates/wzp-relay/src/conformance.rs

Steps

  1. Add packet-rate enforcement: packets_in_window > max_pps(codec) * 1.5 over a 1 s window → PacketRateExceeded.
  2. max_pps(codec) = 1000 / codec.frame_duration_ms() * 3 (×3 for FEC).
  3. Timestamp drift: track Δtimestamp / Δseq over rolling 200-packet window. If outside frame_duration_ms × [0.5, 2.0], log TimestampDrift.

Verify

cargo test -p wzp-relay conformance

Done when

Both new tests pass alongside Tier A test.


T2.6 — Prometheus metrics for conformance

  • PRD: PRD-relay-conformance.md
  • Effort: 2 h
  • Files:
    • crates/wzp-relay/src/metrics.rs

Steps

  1. Add counters / histograms:
    wzp_relay_conformance_violations_total{tier, codec_id, media_type, verdict}
    wzp_relay_conformance_bytes_per_session{media_type}      histogram
    wzp_relay_conformance_iat_ms{media_type}                  histogram
    
  2. Wire ConformanceMeter to bump these on observe.

Verify

curl localhost:9090/metrics | grep wzp_relay_conformance

(after cargo run -p wzp-relay -- --listen 127.0.0.1:4433 --no-auth with a synthetic client)

Done when

Counters increment under abusive traffic; quiet on legitimate audio.


Wave 3 — Protocol hardening (target: 3-4 days)


T3.1 — Confirm RoomManager concurrency (W13)

  • PRD: PRD-protocol-hardening.md
  • Effort: 2 h
  • Files:
    • crates/wzp-relay/src/room.rs

Context

RoomManager already uses DashMap<String, Room> (verified at line 352). The audit (W13) was based on the older ARCHITECTURE doc which mentioned a single Mutex. The actual remaining contention point is whatever's inside Room — confirm.

Steps

  1. Read the Room struct definition.
  2. If Room itself uses fine-grained locks or is Arc<RwLock<Room>> already, document this in PROTOCOL-AUDIT.md and mark W13 resolved.
  3. If Room has a single per-room Mutex held during fan-out, identify the hot path and either:
    • Split fan-out list into RwLock<Vec<Participant>> (read-mostly).
    • Use ArcSwap<Vec<Participant>> for lock-free reads.
  4. Run the 40+4 relay integration tests.

Verify

cargo test -p wzp-relay
cargo test -p wzp-relay --test federation
cargo test -p wzp-relay --test handshake_integration

Done when

Tests pass + a one-line update in PROTOCOL-AUDIT.md noting actual state.


T3.2 — Document timestamp_ms rebase across rekey (W3)

  • PRD: PRD-protocol-hardening.md
  • Effort: 1 h
  • Files:
    • crates/wzp-proto/src/packet.rs (doc comment on MediaHeader::timestamp)
    • crates/wzp-crypto/src/rekey.rs (add comment)
    • docs/WZP-SPEC.md
    • Add test in crates/wzp-client/tests/long_session.rs

Steps

  1. Decision (already made): timestamp_ms is monotonic across rekeys. Document inline:
    /// Milliseconds since session start. Monotonic for the full session lifetime;
    /// NOT reset by rekey (rekey changes only key material, not framing state).
    pub timestamp: u32,
    
  2. In rekey.rs, add a comment near the rekey handler confirming sequence + timestamp are untouched.
  3. Add a test that performs 2 rekeys mid-session and asserts timestamp continues monotonically.

Verify

cargo test -p wzp-client --test long_session rekey_timestamp_monotonic

Done when

Test passes.


T3.3 — SignalMessage version field (W12)

  • PRD: PRD-protocol-hardening.md
  • Effort: 2 h
  • Files:
    • crates/wzp-proto/src/packet.rs

Steps

  1. For each variant of SignalMessage, add #[serde(default)] version: u8 as the first field, with helper fn default_signal_version() -> u8 { 1 }.
  2. Add fallback variant for unknown future signals:
    #[serde(other)]
    Unknown,
    
    (Note: bincode + serde other may need a wrapper — research before implementing. If not feasible, document the limitation and skip the Unknown arm.)
  3. Decode path: on Unknown, log tracing::warn!("unknown signal variant") and do not close session.

Verify

cargo test -p wzp-proto signal_message

Done when

Existing signal tests pass. Old payloads (without version field) still deserialize.


T3.4 — Tier D (per-codec packet size sanity)

  • PRD: PRD-relay-conformance.md
  • Effort: 2 h
  • Files:
    • crates/wzp-relay/src/conformance.rs

Steps

  1. Add per-codec typical / max payload table:
    pub fn payload_size_bound(codec: CodecId) -> usize {
        match codec {
            CodecId::Opus64k => 320, CodecId::Opus48k => 240,
            CodecId::Opus32k => 200, CodecId::Opus24k => 160,
            CodecId::Opus16k => 100, CodecId::Opus6k => 90,
            CodecId::Codec2_3200 => 30, CodecId::Codec2_1200 => 30,
            CodecId::ComfortNoise => 16,
        }
    }
    
  2. Maintain EWMA of payload size per meter. Reject if EWMA exceeds 2× typical for declared codec.

Verify

cargo test -p wzp-relay conformance_tier_d

Done when

Synthetic stream of 1400-byte payloads declared as Codec2_1200 flagged within 5 s.


T3.5 — Tier E (per-fingerprint token bucket)

  • PRD: PRD-relay-conformance.md
  • Effort: 4 h
  • Files:
    • crates/wzp-relay/src/conformance.rs (or sibling quota.rs)
    • crates/wzp-relay/src/auth.rs (for authed/anon split)

Steps

  1. Implement a simple token bucket per (fingerprint, src_ip):
    pub struct TokenBucket {
        capacity: u64,
        tokens: AtomicU64,
        refill_per_sec: u64,
        last_refill: AtomicU64,
    }
    
  2. Wire into per-participant forward loop. Refill on each observe.
  3. Authed/anon split: authenticated quota = 50 GB/month; anon = 1 GB/month. Per-session cap = 256 kbps audio (5 Mbps video reserved for later).
  4. Observe-only: log + counter; do not throttle yet.

Verify

cargo test -p wzp-relay token_bucket

Done when

Unit test: 100 KB at 256 kbps cap consumes no tokens; 1 MB exceeds.


Wave 4 — Video v1 (3 weeks)

Detailed task breakdown deferred until Wave 1-3 land. Skeleton:

Task Summary Effort
T4.1 wzp-video crate scaffold + H.264 NAL framer + depacketizer (no encoder yet) 3 d
T4.2 VideoToolbox H.264 encoder + decoder (macOS) — minimum viable 3 d
T4.3 MediaCodec H.264 encoder + decoder via JNI (Android) 5 d
T4.4 SignalMessage::Nack variant + RTT-gated NACK loop 2 d
T4.5 I-frame FEC ratio boost (encoder hint → FEC layer) 1 d
T4.6 SFU keyframe cache per (room, sender, stream_id) 2 d
T4.7 PLI suppression at SFU 1 d

Each of these will be expanded into the same step-by-step format as T1.x once Wave 3 is in progress. See PRD-video-v1.md for design.


Wave 5 — Quality, codecs, simulcast (3 weeks)

Detailed task breakdown deferred. Skeleton:

Task Summary
T5.1 PriorityMode enum + SignalMessage::SetPriorityMode
T5.2 VideoQualityController with per-mode allocation gates
T5.3 EncoderMode::SlideFallback for ScreenShare
T5.4 H.265 encoder/decoder (reuse framer from T4.1)
T5.5 3-layer simulcast at sender
T5.6 Per-receiver layer selection at SFU
T5.7 Tier F audio scorer (entropy/IAT/silence-fraction)
T5.8 Tier G response policy (typed Hangup + audit log)

Wave 6 — AV1 + Tier F video (2-3 weeks)

Task Summary
T6.1 AV1 encoder/decoder with HW probe + SVT-AV1 SW fallback
T6.2 Tier F video scorer (keyframe periodicity, I/P ratio, BWE responsiveness)
T6.3 Federated reputation gossip (optional)

Working agreements

  • One commit per task. Message: T<id>: <one-line summary>.
  • Update PRD on deviation. If you implement something differently than the PRD specifies, edit the PRD in the same commit explaining why.
  • Don't merge waves out of order — dependencies are real.
  • Ask before destroying. Any task that would delete data, drop tables, or force-push: stop and report.
  • Auto-mode caveat. Even in auto mode, if a task description doesn't fit what you find in the code, stop and surface the mismatch before guessing.

Status board

Edit this table directly when you claim, complete, or get blocked on a task. Keep it sorted by task ID. The reviewer (human) is the only one who flips Pending ReviewApproved or Changes Requested.

Statuses (in order of progression):

  • Open — not yet picked up
  • In Progress — an agent is working on it
  • Blocked — agent has hit something it can't resolve; see report
  • Pending Review — agent has finished, report filed, awaiting human
  • Changes Requested — reviewer pushed back; back to agent
  • Approved — reviewer signed off; task is closed
  • Skipped — explicitly deferred or made redundant by another task
Task Status Agent Started (UTC) Completed (UTC) Report Reviewer notes
T1.1 Approved Kimi Code CLI 2026-05-11T06:09Z 2026-05-11T06:54Z report Approved 2026-05-11. Spawned T1.1.1 (field rustdoc) and T1.1.2 (refresh stale test-count).
T1.1.1 Approved Kimi Code CLI 2026-05-11T07:17Z 2026-05-11T07:22Z report Approved after rework. Both Verify commands clean.
T1.1.2 Approved Kimi Code CLI 2026-05-11T07:19Z 2026-05-11T07:25Z report Approved after rework. Broader grep clean; remaining matches are self-refs in task spec + frozen historical note.
T1.2 Approved Kimi Code CLI 2026-05-11T06:55Z 2026-05-11T07:08Z report Approved 2026-05-11. Spawned T1.2.1 (rustdoc on MediaType variants/methods). Agent also resolved the T1.2 TODO inside MediaHeaderV2 — good call.
T1.2.1 Approved Kimi Code CLI 2026-05-11T07:23Z 2026-05-11T07:24Z report Approved. Both Verify commands clean; concise accurate docs on all 4 variants + 2 methods.
T1.3 Approved Kimi Code CLI 2026-05-11T07:10Z 2026-05-11T07:11Z report Approved 2026-05-11. No follow-ups; docs-and-test-only change.
T1.4 Approved Kimi Code CLI 2026-05-11T07:12Z 2026-05-11T07:16Z report Approved 2026-05-11. Spawned T1.4.1 (rustdoc on v2 mini types). The two-step expand test catches the W4 desync scenario nicely.
T1.4.1 Approved Kimi Code CLI 2026-05-11T07:26Z 2026-05-11T07:27Z report Approved. Closes rustdoc trilogy (T1.1.1/T1.2.1/T1.4.1).
T1.5 Approved Kimi Code CLI 2026-05-11T07:28Z 2026-05-11T10:09Z report Approved with follow-ups. Migration correct; scope creep (120 files) and workspace clippy skipped — spawned T1.5.1 (encode_compact unwrap) and T1.5.2 (clippy hygiene).
T1.5.1 Approved Kimi Code CLI 2026-05-11T10:09Z 2026-05-11T10:15Z report Approved. unwrap replaced with if let Some(base); fallback test passes. Cargo.lock churn is legit dep updates.
T1.5.2 Approved Kimi Code CLI 2026-05-11T10:15Z 2026-05-11T10:20Z report Approved. PROTOCOL-AUDIT.md known-debt section present; standard #3 amended; report template updated.
T1.6 Approved Kimi Code CLI 2026-05-11T10:20Z 2026-05-11T11:05Z report Approved. Clean impl, both sides tested, T1.5 gap-fixes folded in with explicit disclosure — good course-correction from the T1.5 scope-creep review.
T1.7 Approved Kimi Code CLI 2026-05-11T11:05Z 2026-05-11T16:29Z report Approved. W5 invariant already encoded in to_bytes() order; regression test pins it. Guards future encryption wiring.
T1.8 Approved Kimi Code CLI 2026-05-11T16:41Z 2026-05-11T16:59Z report Approved. Per-stream/per-MediaType windows; AEAD-first then anti-replay; plaintext rollback on detection. W11 resolved.
T2.1 Approved Kimi Code CLI 2026-05-11T17:00Z 2026-05-11T17:06Z report Approved retroactively. Commit fe1f948 landed; closed by reviewer.
T2.2 Approved Kimi Code CLI 2026-05-11T17:05Z 2026-05-11T17:16Z report Approved. Substance solid; rule #7 violated. Last lenient pass.
T2.3 Approved Kimi Code CLI 2026-05-11T17:13Z 2026-05-11T17:20Z report Substance good (BWE guard); 4 process violations bundled with T2.4-T2.6 in single commit 54c1a35 — see T2.6 report for consolidated notes.
T2.4 Approved Kimi Code CLI 2026-05-11T17:20Z 2026-05-11T17:35Z report Substance good (Tier A); bundled in 54c1a35 — see T2.6 report.
T2.5 Approved Kimi Code CLI 2026-05-11T17:35Z 2026-05-11T17:45Z report Substance good (Tier B+C); bundled in 54c1a35 — see T2.6 report.
T2.6 Approved Kimi Code CLI 2026-05-11T17:45Z 2026-05-11T17:55Z report Substance good (Prom metrics); bundled in 54c1a35. Consolidated reviewer notes here.
T3.1 Approved Kimi Code CLI 2026-05-11T20:55Z 2026-05-11T21:05Z report Approved. DashMap<String, Arc<RwLock>>; W13 resolved. One commit per task this time — good. Two minor process notes in report.
T3.2 Approved Kimi Code CLI 2026-05-11T21:15Z 2026-05-11T21:25Z report Approved. timestamp_ms monotonic across rekey, documented + tested. Commit 1b4f7b0.
T3.3 Approved Kimi Code CLI 2026-05-11T16:29Z 2026-05-12T06:08Z report Approved. W12 SignalMessage versioning. Commit f7f413e.
T3.4 Pending Review Kimi Code CLI 2026-05-11T16:29Z 2026-05-11T16:29Z report
T3.5 Open
T4.1 Open Skeleton — expand before claiming
T4.2 Open Skeleton — expand before claiming
T4.3 Open Skeleton — expand before claiming
T4.4 Open Skeleton — expand before claiming
T4.5 Open Skeleton — expand before claiming
T4.6 Open Skeleton — expand before claiming
T4.7 Open Skeleton — expand before claiming
T5.1 Open Skeleton — expand before claiming
T5.2 Open Skeleton — expand before claiming
T5.3 Open Skeleton — expand before claiming
T5.4 Open Skeleton — expand before claiming
T5.5 Open Skeleton — expand before claiming
T5.6 Open Skeleton — expand before claiming
T5.7 Open Skeleton — expand before claiming
T5.8 Open Skeleton — expand before claiming
T6.1 Open Skeleton — expand before claiming
T6.2 Open Skeleton — expand before claiming
T6.3 Open Skeleton — expand before claiming

Review queue (human)

Items currently waiting on the reviewer:

  • T1.8 — Per-stream anti-replay window with configurable size — report: reports/T1.8-report.md
  • T2.1 — Add SignalMessage::TransportFeedback — report: reports/T2.1-report.md
  • T2.2 — BandwidthEstimator in wzp-proto::bandwidth — report: reports/T2.2-report.md
  • T3.2 — Document timestamp_ms monotonic across rekey — report: reports/T3.2-report.md
  • T3.3 — SignalMessage version field — report: reports/T3.3-report.md
  • T3.4 — Tier D per-codec payload size sanity — report: reports/T3.4-report.md

Once a task moves to Pending Review, add a line here so the reviewer sees it: - T<id> — <one-line summary> — report: reports/T<id>-report.md. The reviewer removes the line when they mark it Approved (or moves it back to the agent on Changes Requested).