fix: make join approval atomic

This commit is contained in:
User
2026-07-14 15:24:06 +02:00
parent bc02213d3e
commit 333874a755
9 changed files with 314 additions and 34 deletions
@@ -93,6 +93,9 @@ Expected: PASS.
- Modify: `harness/.pi/extensions/tht-gate.js`
- Create: `harness/.pi/extensions/gate/__tests__/gate_join_review.test.js`
- Modify: `harness/.pi/skills/tht-sessione/SKILL.md`
- Modify: `harness/tht/decisions.py`
- Modify: `harness/tht/cli/decision_cmd.py`
- Create: `harness/tests/test_decision_join_set_cli.py`
- Create: `frontend/src/widgets/JoinReviewWidget.tsx`
- Create: `frontend/src/widgets/JoinReviewWidget.test.tsx`
- Modify: `frontend/src/widgets/index.ts`
@@ -108,7 +111,8 @@ Expected: PASS.
Assert the builder emits read-only option details, `confirm_label: "Continue"`, and reserved controls.
Exercise `reviewer_decide` with only `join_modified` decisions; queue a Continue response containing
all ids and assert every `tht decision add` call occurs. Queue `control:"freetext"` and assert no
all ids and assert one atomic `tht decision add-join-set` call occurs. Queue malformed and partial
responses and assert the widget is presented again. Queue `control:"freetext"` and assert no
decision is written.
- [x] **Step 2: Run harness tests and verify RED**
@@ -125,7 +129,8 @@ Add `buildJoinReviewRequest`. In `reviewer_decide`, detect a non-empty, join-onl
const joinOnly = opts.length > 0 && opts.every((o) => o.decision.type === "join_modified");
```
Emit `join-review` with `detail` and `rationale`; after Continue persist all original decisions.
Emit `join-review` with `detail` and `rationale`; accept only a response carrying the exact complete
id set. After Continue persist all original decisions through atomic `decision add-join-set`.
On free text, return feedback without persistence. Keep all other decisions on `multiselect`.
- [x] **Step 4: Write failing frontend widget tests**
@@ -269,7 +274,7 @@ Expected: PASS.
- Running Compose services must use the newly built `thothii-core:local` and
`thothii-frontend:local` image ids.
- [ ] **Step 1: Run all regression suites**
- [x] **Step 1: Run all regression suites**
Run: `cd backend && npx vitest run && npx tsc --noEmit -p . && npm run build`
@@ -35,9 +35,11 @@ the configured model activity.
F4 calls to `reviewer_decide` whose merit decisions are all `join_modified` become a read-only
`join-review` widget. Each proposed join is rendered as an informational card with its name,
join expression, and rationale. `Continue` returns every join id; the gate persists all decisions.
`Other — specify` returns textual feedback without persisting the current proposal, so the model
must revise and present the complete join set again. Mixed join/non-join calls remain regular
join expression, and rationale. `Continue` returns every join id; the gate accepts only that exact
complete response and persists all decisions through one atomic ledger replacement. Malformed or
partial responses re-present the widget. `Other — specify` returns textual feedback without
persisting the current proposal, so the model must revise and present the complete join set again.
Mixed join/non-join calls remain regular
multiselects, and the skill instructs the model to keep joins in a separate call.
### CTE presentation