# QA Plan: session-robustness-and-dox — master..feat/session-robustness-and-dox

**Status:** DRAFT
**Run date / operator:**
**BACKLOG item:** feat/session-robustness-and-dox full-branch sweep

Commits under test:
```
c8c2521 fix(kernel,inference): reliable artifact emission for local models
af9da3c fix(inference): raise llama HTTP request timeout 600s -> 1800s
5e0c7df feat(server): REST stage-approval route POST /sessions/{id}/approve
e72051a feat(kernel): emit_artifact tool + actionable validation feedback
0652d87 fix(inference): salvage truncated/malformed tool-call JSON from content
4737300 fix(server): anchor static-registry file_read to the workspace
82674e8 feat(inference): capability-aware routing for tool-heavy stages
238d353 feat(qa): remote NIM provider + headless-QA robustness
e0f623a feat(workflow): tasked execution loop replaces role_pipeline
a00bd4a feat(tools): propose_scope recalibration valve for the execution loop
3e44c6d feat(kernel): deterministic per-task claim stage + per-task write scope
c1e4c7b feat(transitions): tasks_ready predicate for execution loop
d26f20c docs(dox): build out the DOX AGENTS.md hierarchy
1cb7fec feat(kernel): read-only mode after read-before-write + mandatory task
201b599 fix(inference): recover tool calls emitted as JSON in message content
ccfa4b4 feat(tui): composer soft-wrap, alt+backspace, bracketed paste, borders
8abe7d9 fix(tui): collapse multiline tool summaries so action rows don't stripe
```

---

## Preconditions

- [x] **server build/branch:** `feat/session-robustness-and-dox`, `:apps:server:run` on :8080 — _rebuild only with the server stopped_
- [x] **llama-server + model:** router = gemma-4-12B-it-qat-UD-Q4_K_XL :10000 (tool-capable, ~48 tok/s), narrator = LFM2.5-1.2B :10001
- [ ] **external deps:** none required for the core loop; capability-aware routing (§C8) is only meaningfully exercised if **two** tool-capable providers are configured — otherwise it's a single-candidate no-op. OpenAI-compat/NIM path (238d353) needs a remote endpoint; **out of scope** unless one is wired.
- [ ] **config synced:** `~/.config/correx/workflows/` must contain the NEW `task_planning.toml` + `prompts/task_planner.md` and the REWRITTEN `role_pipeline.toml` (execution loop). Copy from `examples/workflows/` before running — stale role_pipeline = wrong graph.
- [ ] **fixtures/seed:** a real workspace with a `.correx/project.toml` and a small, genuinely-actionable request (so `task_decompose` produces >1 task and the loop iterates).

## Acceptance gate (one sentence)

> The branch is correct **iff** a real request drives the execution loop end-to-end —
> planner emits a task graph, each task is deterministically claimed, writes are scoped to
> the claimed task's `affected_paths` (and blocked outside it / in read-only mode), artifacts
> emit reliably on the local model, and a headless `POST /approve` clears an approval gate —
> every step evidenced by a recorded event.

## Checks

| # | Action | Expected observable evidence | Result |
|---|--------|------------------------------|--------|
| 1 | Start a `task_planning` session on a real request | `correx events <id>` shows the `planner` stage; a `task_decompose` tool invocation succeeds and task-created events appear; stage cannot complete without it (`require_task_decompose` gate) — try a run where the model skips decompose and confirm the stage is **blocked**, not completed | |
| 2 | Start a `role_pipeline` (execution loop) session | Stage chain analyst→architect→decomposer→implementer→reviewer in the log; `decomposer-to-implementer` fires on `tasks_ready` (not always_true); loop re-enters implementer via `review-2-more` while tasks remain, exits via `review-3-done` when drained | |
| 3 | Watch the per-task claim stage (`claimTask=true`) | On each implementer entry, a claim record for the next ready task + its context bundle injected as **L0** (`sourceType=claimedTask`); the loop advances by claim events, not model memory — verify the claimed task id changes across iterations | |
| 4 | Let the implementer write a file inside the claimed task's `affected_paths` | `FileWriteTool` invocation ASSESSED and allowed; file appears on disk under the workspace | |
| 5 | Force a write **outside** the claimed scope | `ToolCallAssessedEvent` blocks it (scope violation); the write does not land on disk; feedback surfaced to the stage | |
| 6 | Trigger read-only mode (a stage that reads before any task is claimed / after read-before-write) | `isReadOnlyMode` denies a subsequent write: `ToolCallAssessedEvent` with the read-only denial; no file mutation event | |
| 7 | Have the implementer call `propose_scope` with a `paths`/`reason` | Tool records a scope-widen proposal on the task; **operator approval required** (approval gate fires); on approve, paths added to `affected_paths` and the previously-blocked write now lands | |
| 8 | Confirm artifact emission on the local model (analysis/design/patch) | Each `*_validated` transition fires because the artifact actually emitted — `artifact_validated` events present; no stage stuck retrying because the artifact never materialized (the c8c2521 fix) | |
| 9 | Feed a truncated/malformed tool-call (or observe a real one from gemma) | Salvage path recovers it: the tool call executes instead of the turn failing; log shows the salvage of JSON-in-content (0652d87 / 201b599) | |
| 10 | With a session suspended on an approval, `POST /sessions/{id}/approve` `{"decision":"APPROVE"}` (no requestId) | HTTP 200; server resolves the pending requestId itself; the gate clears and the session advances — no WebSocket needed. Also test `REJECT` (session takes reject path) and a bad `decision` → 400, and no-pending → 404 | |
| 11 | `POST /approve` with `{"decision":"STEER","steeringNote":"..."}` | Treated as APPROVE with the note attached; note recorded on the approval response | |
| 12 | (If 2 tool-capable providers configured) run a tool-heavy stage | Routing selects the higher tool-capability provider, not just any healthy one; check `/metrics/tool-reliability` reflects observed per-model reliability | |
| 13 | TUI composer smoke: soft-wrap a long line, alt+backspace, bracketed paste | Composer wraps within borders; alt+backspace deletes a word; pasted multiline text lands as one block; multiline tool summaries render collapsed (no striped action rows) | |
| 14 | `correx replay <id>` on a completed loop session | Double-read digest matches — the claim/scope/read-only logic is derived from recorded events, replay-stable | |

## Out of scope (explicitly NOT covered this pass)

- OpenAI-compat / remote NIM provider (238d353) — no remote endpoint wired; unit-tested only.
- Capability-aware routing real differentiation (§C12) unless a second tool-capable provider is configured; single-provider makes it a no-op.
- The DOX AGENTS.md hierarchy (d26f20c) — docs only, nothing to exercise at runtime.
- Layer-2 LLM annotation of tool-intent; the AMD/ROCm perf envelope.

## Disposition

- **PASS** → move the BACKLOG entry into `RETRO.md` with run date + cited evidence. Set Status: PASSED.
- **FAIL** → file each miss as a numbered `BACKLOG.md` finding (action + repro + wrong/missing signal), fix, re-run only failed checks. Status: FAILED until green.
