(tasks) add tasks for harness
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
# T7.4 — Outbox relay
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Phase | P7 — Distribution |
|
||||
| Size | M — 1 to 3 days |
|
||||
| Status | Not started |
|
||||
| Flags | — |
|
||||
| Spec | inlined below |
|
||||
| Blocks | — |
|
||||
|
||||
## Goal
|
||||
|
||||
Ship committed export intents out of process, with its own retry and its own
|
||||
failure domain. The execution path never calls a broker.
|
||||
|
||||
## Facts (inlined — no spec read needed)
|
||||
|
||||
- **The framework never calls a broker from the execution path.** Export intent
|
||||
is written in the same transaction as the state (T0.6); a separate relay reads
|
||||
committed intents and ships them.
|
||||
- This makes export restartable, keeps a broker outage from stalling a run, and
|
||||
is **the only pattern that survives a crash between "state committed" and
|
||||
"event published"**.
|
||||
- **A user's broker being down is not an agent outage.**
|
||||
- The relay reads through `drain_outbox` / `ack_outbox`. Ordering is per
|
||||
`BranchKey`, ascending `Lsn` — that is the only ordering the system promises,
|
||||
and it falls out of the outbox key shape.
|
||||
- **Delivery is at-least-once. Exactly-once is achieved at the fold, not in
|
||||
transport**: `(BranchKey, Lsn)` is the natural idempotency key, so a redelivered
|
||||
record is a no-op insert.
|
||||
- Only the live branch is exported (T2.3).
|
||||
|
||||
## Steps
|
||||
|
||||
1. Run the relay as its own process or task with its own supervision — separate
|
||||
failure domain is the point, so do not co-locate it with the executor's
|
||||
lifecycle.
|
||||
2. Loop: `drain_outbox(tenant, limit)` → publish in `(BranchKey, Lsn)` order →
|
||||
`ack_outbox(shipped)`. Ack only what the broker accepted.
|
||||
3. Retry with backoff inside the relay. Never propagate a broker error into run
|
||||
execution — there is no path back by construction, so verify none is added.
|
||||
4. Set the broker partition key from `(TenantId, RunId)` (T7.5).
|
||||
5. Make the consumer-side fold idempotent on `(BranchKey, Lsn)` so redelivery is
|
||||
a no-op insert.
|
||||
6. Emit relay lag as a metric — outbox backlog is one of the two that grow
|
||||
silently.
|
||||
7. Test with the broker down for an entire run.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Broker down for the whole run; **the run completes**; every event ships after
|
||||
the broker returns, **in order, exactly once at the fold**.
|
||||
|
||||
## Verify
|
||||
|
||||
**Harness:** a broker that can be held down for the whole test, plus a consumer
|
||||
that folds on `(BranchKey, Lsn)` so redelivery is observable.
|
||||
|
||||
**Integration test** — `tests/it_outbox_relay.rs`:
|
||||
1. Take the broker **down**. Run a full run to completion.
|
||||
2. Assert the run **completed normally** — no stall, no error, and its latency is
|
||||
comparable to a broker-up baseline. A user's broker being down is not an agent
|
||||
outage.
|
||||
3. Bring the broker up. Assert every event ships.
|
||||
4. Assert ordering **per `BranchKey`, ascending `Lsn`** at the consumer.
|
||||
5. Force redelivery (ack loss): assert the consumer's folded state is identical
|
||||
to the single-delivery case — exactly-once **at the fold**, not in transport.
|
||||
6. Kill the relay mid-batch; restart; assert no event is lost and none is
|
||||
duplicated at the fold.
|
||||
7. Assert the execution path never calls the broker: instrument the broker client
|
||||
and assert zero calls originate from executor threads.
|
||||
8. Assert only the **live branch** is exported after a rewind.
|
||||
9. Assert relay lag is emitted as a metric.
|
||||
|
||||
**Command:** `cargo test -p distribution outbox -- --test-threads=1`
|
||||
|
||||
**False pass:**
|
||||
- Step 2 asserting only that the run completed. If the executor retries the
|
||||
broker inline, the run completes — slowly. The latency comparison is what
|
||||
catches it; step 7 is what proves it.
|
||||
- Acking before the broker confirms: steps 3 and 4 still pass in a happy-path
|
||||
test, and events vanish silently under failure. Step 6 is the guard.
|
||||
- Testing ordering within one branch only, where any implementation is ordered.
|
||||
Use several branches and several runs interleaved.
|
||||
|
||||
## Traps
|
||||
|
||||
- Publishing from the execution path "just for low latency". A broker outage then
|
||||
stalls agent work.
|
||||
- Acking before the broker confirms. Events are lost with no trace.
|
||||
- Attempting transport-level exactly-once. The fold is where it is achievable.
|
||||
|
||||
---
|
||||
|
||||
Background (not required to do this task):
|
||||
[rust-agentic-sys.md](../../../rust-agentic-sys.md) §8.3, §9.2, §9.3, §13.3, §15 ·
|
||||
[rust-agentic-task.md](../../../rust-agentic-task.md)
|
||||
Reference in New Issue
Block a user