Просмотр исходного кода

r1: twin guards removed (P3.3, sequenced last) — the one-pending render guard on the goal page and answer_block's twin-answer sweep die: fresh DBs cannot hold pre-dedupe twins and the raise-side cross-dedupe (4884259) keeps them single; their tests now pin the raise-side single-row invariant (a redelivered review-kind push adopts the pending row, the verdict leaves zero pending completion-family rows).

clanker 1 месяц назад
Родитель
Сommit
d46cc3426c
3 измененных файлов с 21 добавлено и 41 удалено
  1. 0 15
      src/Services/GoalService.vala
  2. 4 11
      src/entrypoints/GoalDetailEntrypoint.vala
  3. 17 15
      test/web_stack_test.py

+ 0 - 15
src/Services/GoalService.vala

@@ -590,21 +590,6 @@ namespace ClankerSaddle.Services {
 
             yield tasks.answer_block(block_id, answer, answered_by);
 
-            if (is_completion_row(((!)block))) {
-                // Pre-dedupe twins: answer EVERY pending completion-family
-                // row with the same verdict — a lingering twin must never
-                // re-render the review after it was decided.
-                var run_for_twins = yield goal_run_task(((!)goal).id);
-                if (run_for_twins != null) {
-                    foreach (var twin in yield tasks.list_blocks(((!)run_for_twins).id)) {
-                        if (twin.id != block_id && twin.answer == null
-                                && is_completion_row(twin)) {
-                            yield tasks.answer_block(twin.id, answer, answered_by);
-                        }
-                    }
-                }
-            }
-
             if (is_completion_row(((!)block))
                     && answer == NOTICE_REVIEW_APPROVED) {
                 // Approving the completion closes the goal agent's

+ 4 - 11
src/entrypoints/GoalDetailEntrypoint.vala

@@ -478,24 +478,17 @@ namespace ClankerSaddle {
             var open_sink = blocks_open ?? new Vector<GoalBlockView>();
             var answered_sink = blocks_answered ?? new Vector<GoalBlockView>();
             var block_array = blocks.to_array();
-            GoalBlockView? pending_review = null;
             for (var index = block_array.length - 1; index >= 0; index--) {
                 var view = (GoalBlockView) block_array[index];
                 if (view.answered) {
                     answered_sink.add(view);
                 } else if (view.is_plan_review) {
                     // rendered by the plan section
-                } else if (view.is_completion_review) {
-                    // AT MOST ONE pending completion-family block renders
-                    // (the rich review form): pre-dedupe twins may exist in
-                    // stale data — the first pending row owns the review,
-                    // any others stay hidden until the verdict answers
-                    // them all.
-                    if (pending_review == null) {
-                        pending_review = view;
-                        open_sink.add(view);
-                    }
                 } else {
+                    // Every pending completion-family row renders (the
+                    // raise-side cross-dedupe keeps them to one —
+                    // fresh databases cannot hold pre-dedupe twins, so
+                    // no render-side guard is needed).
                     open_sink.add(view);
                 }
             }

+ 17 - 15
test/web_stack_test.py

@@ -5925,22 +5925,24 @@ def bundle_goals(ctx):
     check(state["completion_pending"] is True and len(review_family_in_open) == 1,
           "the pending completion review renders ONCE — as the rich attention block")
     fake.pending_block = None
-    # Stale-twin healing (live incident shape): a pre-dedupe run left BOTH
-    # a completion-kind and a review-kind row pending — the page must
-    # render ONE review all the way through, and the final verdict must
-    # answer BOTH rows (asserted after the arc's accept below).
+    # Raise-side single-row invariant (the render guard and the
+    # twin-answer sweep are gone — fresh DBs cannot hold pre-dedupe
+    # twins, and 4884259's raise-side cross-dedupe keeps it that way):
+    # a REDELIVERED review-kind completion push adopts the same pending
+    # row instead of stacking a twin.
+    fake.push_results("blocked", block={
+        "question": "Goal completion review", "type": "review",
+        "body": "# Completion v2\n\nThe flange ships, with docs.",
+        "options": None})
+    fake.pending_block = None
     conn = db()
-    conn.execute(
-        "insert into task_blocks (task_id, question, type, body, created_at)"
-        " values (?, 'Goal completion review (stale twin)', 'review',"
-        " '# Stale twin body', strftime('%s','now'))", (run_task,))
-    conn.commit()
+    rows = conn.execute(
+        "select count(*) from task_blocks where task_id = ?"
+        " and answer is null and type in ('completion','review')",
+        (run_task,)).fetchone()[0]
     conn.close()
-    state = goal_state(goal1)
-    stale_open = [b for b in state.get("open_blocks", [])
-                  if b.get("is_completion_review")]
-    check(len(stale_open) == 1,
-          "a stale pre-dedupe twin renders NOTHING — one review only")
+    check(rows == 1,
+          "a redelivered review-kind push adopts the pending row (raise-side single-row invariant)")
     # Plan 15 C4: the completion-accept hard gate — the live probe
     # flags still report repob's branch unpushed, so the operator's
     # accept REFUSES with origin behind.
@@ -5993,7 +5995,7 @@ def bundle_goals(ctx):
         (run_task,)).fetchone()[0]
     conn.close()
     check(twins_left == 0,
-          "the verdict answered every pending completion-family row (stale twin included)")
+          "the verdict answered the pending completion-family row (single-row invariant)")
 
     print("== plan 15 C3: the narrative derives from the recorded shape ==")
     # Content equivalence with the old projection on this run's exact