A case action's event and its patch are two writes with no transaction — and the run path can lose the event

Open
#598 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
35/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
typescript
Domain
backend, database

Research direction

Start with backend-ts/src/case/case-actions.ts at lines 138 and 150, then inspect run-pipeline.ts and DATA_MODEL_CONTRACTS §4. Read ADR-076, ADR-080, and the relevant CLAUDE.md hard rules to understand the required transaction boundaries and existing best-effort behavior. Done means operator actions and run-created case transitions keep the state change, action record, and audit row consistent under failure.

Written by the indexing model from the issue text.

Description

backend

Two related gaps found by the 2026-09-07 review. Accepted on 2026-09-08, unchanged since, no issue until now.

1. An operator action is two writes with no transaction

backend-ts/src/case/case-actions.ts records the event (:138) and then applies the patch (:150). A failure between them leaves an action recorded but not applied.

The direction is deliberate and should stay: it can never produce an unaudited state change, which is the hard rule in CLAUDE.md. What is missing is the primitive — there is no applyCaseAction({ patch, action, audit }) that makes the three one unit.

2. The run pipeline is the other way round, and CAN lose the event

This is the part the review's framing understated and the follow-up assessment corrected. run-pipeline.ts upserts the case and then best-effort audits it, catching an audit-insert failure and logging a run WARN rather than aborting. That ordering is also deliberate — it exists so a transient audit_events failure cannot strand an otherwise-complete run as RUNNING after the case was already mutated.

But it means the system-wide claim is false as stated. Carry the narrower true statement:

"Every state change writes an audit_event" holds for operator actions. It does not hold for the run-created case transition, which can mutate and lose the event.

That belongs in DATA_MODEL_CONTRACTS §4 next to the best-effort note, rather than being discoverable only by reading the pipeline.

What was decided about the fix

A reconciliation job was proposed and qualified: it is not magic — it needs durable operation identity, an expected version, a deadline, and a visible failure state, or it is a second unreliable thing checking the first. Before any real operational reliance, the state change, the action record and the audit row should be one transaction, including the run-created case transition.

Partial progress already exists and is worth building on rather than around: recordCaseEvents is a batch atomic dual-write, added for bulk assign and reused for panel backfill. Bulk assign deliberately audits before it mutates — the same side of the trade as the single-action path.

Related: ADR-076, ADR-080, CLAUDE.md hard rules.

Dominant language
TypeScript
Stars
0
Forks
0
Avg merge
6h 6m
Merged PRs (30d)
69

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from Taleef7/workwell

All issues in Taleef7/workwell

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.