Version: v0.1
Date: 2026-08-26
Status: Drafted in-session under completion-program P-1 (ruled: yes).
Occasion: B-37 (standing-notes/dunin7-build-list-v1_07), rank 5 of the completion program's Wave 1, clause 4.
Cites: the ShapingRoom's own D-4 comment ("the job carrying the failure is unreachable from this surface"); B-38/CR-2026-172 (the shared room contract); the B-55 join pattern (name the type on the wire, not an id).
When a shape production fails, nothing anywhere says so. The Operator asked for a draft, the work was dispatched, it died — and the Shaping room shows exactly what it showed before: nothing new. The failure's reason is stored, correctly, on the job row — reachable only by polling one job id that no listing surface ever hands out.
This CR gives failures a place to appear: the engine lists an engagement's production jobs, and the Shaping room says which productions are running and which failed, with the reason.
56ccbba
The filing says a failed production "looks identical to a running one," which implied a stuck held entry. The code says something stronger: no code path ever writes a held ShapeEvent. The state exists in the Literal and the surface handles it, but agents/shaping.py writes the ShapeEvent only after successful production (directly in pending_confirmation), the skill path likewise, and the proposal applier writes confirmed. So a failed production leaves no shape event at all — the room shows nothing, for a running production and a dead one alike. The failure IS recorded — shaping_jobs.status='failed' with error_message, mark_failed — but the only read is GET /engagements/{eid}/shaping-jobs/{job_id}, and nothing lists job ids. The ShapingRoom's header comment documents this exactly and explains why the room deliberately refuses to say "producing": the data could not support the claim. This CR supplies the data.
Engine — a list over what already exists; no migration, no schema change.
engagement/shaping_jobs.py: list_jobs(engagement_id, status=None, limit, db) — newest-first over shaping_jobs, optional status filter, joining the declared shape-type's current name (the B-55 pattern: the surface gets a name, not an id).api/routers/shape_events.py: GET /engagements/{engagement_id}/shaping-jobs — items carrying job id, status, error_message, trigger, created_at, completed_at, declared shape-type name. The per-job poll route is unchanged.Surface — the room says the two true things it could not say before.
lib/api/shape.ts: fetchShapingJobs(projectId).ShapingRoom.tsx: a notice block above the list — one line per running job ("Producing a {type}…" — now supportable, the job row is live evidence) and one per failed job ("A production of {type} failed: {reason}"). The D-4 header comment is rewritten: the constraint it recorded (failure unreachable) is discharged, and saying "producing" is no longer an unsupported claim.Honest limitation, stated: failed jobs are listed as jobs, not attached to shape entries — there is no shape to attach them to (§1). Bounded to the most recent jobs; ancient failures age out of the notice, remain on the wire.
list_jobs returns a failed job with its reason and type name; status filter narrows; route returns the list shape. New tests observed failing before the build.failed ShapeEvent state or write anything to Memory — the job table is operational state and stays so.DUNIN7 — Done In Seven LLC — Miami, Florida CR-2026-238 — Show When a Shape's Production Has Failed — v0.1 — 2026-08-26