v0.5.0 — streaming RES_CHUNK responses
Summary
Closes #11. PACT now supports streaming responses: handlers `yield` instead of `return`, dispatcher signs each yielded payload as a `RES_CHUNK`, HTTP layer streams them as NDJSON over chunked transfer encoding.
This was the last open issue. Zero open issues, zero documented xfails.
Quick example
```python
Server side
@agent.handle("ask_llm_stream")
def ask_llm_stream(payload):
for token in ollama_stream(payload["prompt"]):
yield {"chunk": token}
Client side
req = build_req(..., stream=True)
for chunk in send_message_streaming(url, req):
print(chunk["payload"]["chunk"], end="")
```
Wire format additions
`PACTMessage` gains three new optional fields:
| Field | Where | Purpose |
|---|---|---|
| `stream: bool` | REQ | Opt-in to streaming response |
| `chunk_seq: int` | RES_CHUNK | Monotonic sequence (0, 1, 2, ...) |
| `chunk_final: bool` | RES_CHUNK | Marks the terminal chunk |
Each chunk is a fully-formed signed PACTMessage with `type="RES_CHUNK"`. Tampering with any field — including chunk_seq or chunk_final — invalidates that chunk's signature without affecting others.
Idempotency under streaming
Cached value is now polymorphic:
- Dict → one-shot replay (existing)
- List of chunk dicts → streaming replay (new)
A retried streaming REQ returns the cached chunks WITHOUT re-running the handler. Test: `test_streaming_replay_returns_same_chunks`.
Receipts
One receipt per stream, written after the final chunk. `refs` include the original REQ id plus every chunk id. If the consumer disconnects mid-stream, the receipt records `outcome="failed"` with whatever chunks did make it.
Test results
| v0.4.0 | v0.5.0 | |
|---|---|---|
| Tests passing | 135 | 140 |
| Documented xfails | 0 | 0 |
| Open issues | 1 | 0 |
| CI | 3.11 / 3.12 / 3.13 green | 3.11 / 3.12 / 3.13 green |
5 new tests in `tests/integration/test_streaming.py`:
- Round-trip stream
- Per-chunk signature verification
- Tampered chunk detection
- Idempotent replay returns same chunks
- One-shot handler under stream=true still works
No breaking changes
Streaming is opt-in on both sides:
- Client requests it via `stream=True` on `build_req`
- Server delivers it when the handler yields (generator return)
- Either side opting out → falls back to one-shot RES (existing behavior)
Stats arc — full session
The complete v0.1.3 → v0.5.0 progression in two days:
| v0.1.3 (case study era) | v0.5.0 | |
|---|---|---|
| Tests passing | 118 + 1 xfail | 140 |
| Critical auth bypasses | 3 | 0 |
| Durability gaps | 1 | 0 |
| Wire-level delegation | broken | works |
| Streaming | not supported | supported |
| Documented xfails | 1 | 0 |
| Open issues | 7 | 0 |
Install
```bash
pip install --upgrade pact-passport
```
What's next
The actionable v0.x backlog is empty. Possible next moves:
- Patrick integration — wire the local agent stack onto PACT (was out of scope; now safe and unblocked)
- Mini-cluster integration — replace the Mac→NUC SSH path with PACT for the actual Monte Carlo flow
- Spec doc audit — `spec/PACT_v1.md` was written for v0.1; needs updating to reflect v0.2-v0.5 additions (identity_doc, cap_envelope, RES_CHUNK)