Repository navigation
feat: Done cards stop alerting: re-check state before a block alert, and clear block/review labels on close - #544
Conversation
…and clear block/review labels on close
Review findings (blocking)2 finding(s): 2 major. Major
The coder is re-driving a fix for these findings. |
There was a problem hiding this comment.
QA panel review — PASS
code-review-structural · head 4eb3274f68cb · formal
Low-risk PR: the stale-alert guard and terminal-label cleanup are well-structured and verified clean. The single finding is a pre-existing non-atomic sys.modules init in release_freeze.py (a file this PR does not modify), flagged by the structural engine and confirmed by the verifier. Practical risk is low because CPython's import lock serializes module init, but the setdefault fix is trivial and correct if you want to close it. No panel disagreement; verification confirmed the finding as-is with no severity change. No gaps.
Findings
| Severity | Location | Finding | Verified | |
|---|---|---|---|---|
| 🟠 | major | release_freeze.py:102 |
Non-atomic process-stable state init in release_freeze.py allows two threads to hold different locks/dicts | confirmed |
findings JSON (machine-readable)
[
{
"file": "release_freeze.py",
"line": 102,
"severity": "major",
"category": "concurrency",
"claim": "Non-atomic process-stable state init in release_freeze.py allows two threads to hold different locks/dicts",
"evidence": "The module-level state in release_freeze.py uses a get-then-set pattern (lines 103-110): sys.modules.get(_SLOT) returns None, a new ModuleType is built, and sys.modules[_SLOT] = _holder assigns it. Two threads racing this path each build their own holder with its own Lock and dicts, and each keeps a reference to the one it built. The second assignment overwrites the first in sys.modules, so one th Fix: Replace the get-then-set with sys.modules.setdefault(_SLOT, _holder), matching the pattern already used in store._br_lock, review_coverage_hold, and work_snapshot. (protopatch confidence: high)",
"source": "protopatch",
"verdict": "confirmed",
"note": "Code at head 4eb3274f68cb lines ~102-110 is exactly as quoted: `_holder = sys.modules.get(_SLOT)` / `if _holder is None:` / \u2026 / `sys.modules[_SLOT] = _holder`. The get-then-set is non-atomic. Practical risk is mitigated by CPython's import lock (module code runs single-threaded during import), but the pattern is as described and the suggested `setdefault` fix is correct."
}
]1 finding(s) excluded from the verdict by in-diff confinement (file not among this PR's changed paths):
release_freeze.py(major) — Non-atomic process-stable state init in release_freeze.py allows two threads to hold different locks/dicts
There was a problem hiding this comment.
Promoting the PASS verdict for head 4eb3274f68cb: all checks terminal-green, zero unresolved review threads. (approve-on-green)
Open findings carried by this approval — non-blocking, but they did not go away:
- major
release_freeze.py:102— Non-atomic process-stable state init in release_freeze.py allows two threads to hold different locks/dicts
Approving a WARN does not resolve its findings (issue #22).
…and clear block/review labels on close
There was a problem hiding this comment.
QA panel review — PASS
code-review-structural · head c17ecba5222c · formal
No findings were raised this round, so there was nothing to verify. However, the prior major at release_freeze.py:102 (non-atomic process-stable state init) remains open — the panel's clean pass does not confirm it was fixed, and I cannot verify the fix without the diff. It is carried forward as a standing item. No panel disagreement; no Gaps.
Prior requests
| Prior finding | Disposition | Why | |
|---|---|---|---|
| 🔴 | release_freeze.py:102 |
open | The panel returned zero findings this round; I cannot confirm from the empty result that the non-atomic init was fixed. Carrying forward until a round with dif… |
Findings
| Severity | Location | Finding | Verified | |
|---|---|---|---|---|
| 🟠 | major | release_freeze.py:102 |
Non-atomic process-stable state init allows two threads to hold different locks/dicts. | confirmed |
findings JSON (machine-readable)
[
{
"file": "release_freeze.py",
"line": 102,
"severity": "major",
"category": "correctness",
"claim": "Non-atomic process-stable state init allows two threads to hold different locks/dicts.",
"evidence": "Prior-round confirmed major; not re-verified this round (panel returned empty findings).",
"since": "4eb3274f68cbcca739b980d4f041b86e83d32a2f",
"verdict": "confirmed",
"note": "re-listed without a fresh verifier ruling on a line unchanged since a verifier confirmed it \u2014 the earlier confirmation stands (#232)",
"carried": true,
"carried_by": "synthesizer"
}
]1 finding(s) excluded from the verdict by in-diff confinement (file not among this PR's changed paths):
release_freeze.py(major) — Non-atomic process-stable state init allows two threads to hold different locks/dicts.
There was a problem hiding this comment.
Promoting the PASS verdict for head c17ecba5222c: all checks terminal-green, zero unresolved review threads. (approve-on-green)
Open findings carried by this approval — non-blocking, but they did not go away:
- major
release_freeze.py:102— Non-atomic process-stable state init allows two threads to hold different locks/dicts.
Approving a WARN does not resolve its findings (issue #22).
Summary
Done cards no longer generate stale block alerts, and close/cancel edges now clear all stale state labels.
loop/drive.py—_notify_operatornow re-reads the card's state immediately before writing the inbox row. If the card isdoneorcancelled, the alert is dropped and logged at debug; if the re-read raises, the alert is still delivered (fail open, so a real block is never suppressed).store.py— the close edges (record_merge, manualmark_done, and the sharedcancel_featurepath) now also strip anyblocked-class:*label plusin-review,review-pending, andchanges-requested, in addition toblocked.cancelledand audit labels (e.g.verified:*) are kept. Pulled the stale-label set into a small shared helper so all three edges stay consistent.tests/test_stale_alerts_536.py(new) — covers: alert suppressed for done/cancelled cards; alert still delivered when the re-read raises;record_mergeandmark_doneclear the full stale-label set while keeping audit labels; and a regression that a card carrying bothblockedandblocked-class:terminalis still returned bylist_features(state="blocked"), confirming theblocked-flag mapping is untouched.changelog.d/536.md(new) — changelog entry.This retry only reformatted
store.pyandtests/test_stale_alerts_536.pywithruff formatto satisfy the gate; no logic changed from the prior attempt.Fixes #536