Skip to content

fix(ledger): #358 — get_context_for_ready_decisions preserves decision.status (closes silent ValidationError swallow)#378

Merged
silongtan merged 1 commit into
devfrom
fix/358-context-pending-ready-status
May 16, 2026
Merged

fix(ledger): #358 — get_context_for_ready_decisions preserves decision.status (closes silent ValidationError swallow)#378
silongtan merged 1 commit into
devfrom
fix/358-context-pending-ready-status

Conversation

@silongtan

Copy link
Copy Markdown
Collaborator

Summary

Closes #358. One-line fix in `ledger/queries.py` plus two layers of regression coverage (a new sociable test file + removal of the FF4 xfail in the eval dataset).

The bug discovered by #357 sub-task 1's de-mock: `get_context_for_ready_decisions` hardcoded `status="context_pending"` in returned dicts, but `BriefDecision.status` is `Literal['reflected','drifted','pending','ungrounded']`. Constructing `BriefDecision(status="context_pending")` raised `ValidationError`; `handlers/preflight.py:783`'s outer try/except silently swallowed it. Net production effect: `context_pending_ready` on the preflight response always returned empty, suppressing the entire "decisions ready for ratification" surface added in v0.8.0 (commit `0988e4b`).

Pre-#357 every test that exercised this code path was a solitary mock that returned a valid status, short-circuiting validation. The bug shipped silently for months.

The fix (Option A from the issue body)

`ledger/queries.py:1813`:

```python

BEFORE

"status": "context_pending",

AFTER

"status": str(r.get("status", "ungrounded")),
```

The `SELECT` at line 1796 already pulls `status` from the row; this just uses it. Pattern matches the sibling `get_collision_pending_decisions` at line 1781.

Safety: at schema v10+ (current is v17), `decision.status ASSERT` is narrowed to exactly the BriefDecision Literal set (`ledger/schema.py:728`). Any row satisfying the WHERE clause returns a status compatible with BriefDecision. The signoff field still carries the `context_pending` signal — `signoff_state` on BriefDecision is the right surface for that, not `status`.

Coverage

`tests/test_preflight_hitl.py` (NEW) — sociable defense-in-depth pin at the ledger query layer. Three tests:

  1. `test_get_context_for_ready_decisions_returns_brief_decision_compatible_status` — directly checks the regression class. Loud failure message points at the swallowing try/except.
  2. `test_get_context_for_ready_decisions_preserves_underlying_decision_status` — asserts the returned status mirrors `decision.status`, not overrides it. Pins the contract for future refactors.
  3. `test_handle_preflight_surfaces_context_pending_ready_end_to_end` — full integration: real memory:// adapter, seeded decision, calls `handle_preflight`, asserts `context_pending_ready` actually contains the seeded entry. The user-visible surface.

`tests/eval/preflight_dataset.jsonl` — FF4 xfail removed. The xfail-strict mechanism on this row would have flipped FF4 to failure once the bug got fixed; removing the xfail simultaneously is mandatory per the pre-#357 commit message.

Verification

```
$ pytest tests/test_preflight_hitl.py
3 passed in 0.71s

$ pytest tests/eval/run_preflight_eval.py
10 passed in 1.03s # FF4 now PASS, no xfail

$ pytest tests/test_preflight_*.py tests/eval/run_preflight_eval.py
46 passed, 1 skipped, 0 regressions

$ ruff check . && ruff format --check .
All clean

$ mypy .
no issues
```

Test plan

  • CI green
  • After merge, the `context_pending_ready` field on `PreflightResponse` actually populates in production for any ratification-ready decision (was empty for months)

What this doesn't do

  • Doesn't touch `get_collision_pending_decisions` — already correct (uses the right pattern at line 1781)
  • Doesn't move `seed_context_pending_ready` from `tests/eval/_preflight_eval_seed.py` to a shared location — kept scope tight per Devin's anti-bundling critique
  • Doesn't reduce the trap count — both functions are already direct-sociable post-test(preflight): #357 backfill — de-mock test_preflight_dedup_v2.py + decrement trap cap 8→5 #365 (this PR adds more sociable coverage but doesn't move metric)

🤖 Generated with Claude Code

…n.status

The bug discovered by #357 sub-task 1's sociable-test de-mock:
get_context_for_ready_decisions (ledger/queries.py:1804) hardcoded
status="context_pending" in the returned dicts, but BriefDecision.status
is Literal['reflected','drifted','pending','ungrounded']. Constructing
BriefDecision(status="context_pending") raised ValidationError; the
handler's outer try/except at handlers/preflight.py:783 silently
swallowed it. Net effect: context_pending_ready on the preflight
response always returned empty in production, suppressing the entire
"decisions ready for ratification" surface added in v0.8.0 (commit
0988e4b).

Pre-#357 every test that exercised this code path was a solitary mock
that returned a valid status, short-circuiting the validation — the
bug was only surfaced by the eval-harness de-mock in PR #359 Phase B.

The fix per the issue body's Option A: change line 1813 from a
hardcoded literal to `str(r.get("status", "ungrounded"))`. The SELECT
at line 1796 already pulls `status` from the row; this just uses it.
Pattern matches the sibling get_collision_pending_decisions at line
1781. At schema v10+ (current is v17) decision.status ASSERT is
narrowed to exactly the BriefDecision.status Literal set
(ledger/schema.py:728), so any row that satisfies the WHERE clause
returns a status compatible with BriefDecision. The signoff field
still carries the 'context_pending' signoff state separately —
signoff_state on BriefDecision is the right surface for that, not
status.

Coverage:
- tests/test_preflight_hitl.py (NEW) — sociable defense-in-depth pin
  at the ledger query layer. Three tests:
    1. test_get_context_for_ready_decisions_returns_brief_decision_compatible_status
       — directly checks the regression class
    2. test_get_context_for_ready_decisions_preserves_underlying_decision_status
       — asserts the returned status mirrors decision.status, not
       overrides it
    3. test_handle_preflight_surfaces_context_pending_ready_end_to_end
       — confirms the user-visible surface actually populates now
- tests/eval/preflight_dataset.jsonl — FF4 xfail removed. The
  strict-xfail mechanism on this row would have flipped FF4 to
  failure once the underlying bug got fixed; removing the xfail
  simultaneously is mandatory per the pre-#357 commit message.

Verified:
- pytest tests/test_preflight_hitl.py: 3/3 passing (0.71s)
- pytest tests/eval/run_preflight_eval.py: 10/10 passing (FF4 PASS,
  no xfail) (1.03s)
- pytest tests/test_preflight_* tests/eval/run_preflight_eval.py:
  46/46 passing, 0 regressions
- ruff check + format clean; mypy clean

Closes #358.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@silongtan silongtan added flow:feature Standard feature/fix PR targeting BicameralAI/dev (the default flow) P2 Medium: next milestone or two; default for new issues post-triage fix Bug fix or correctness repair labels May 16, 2026
@silongtan silongtan had a problem deploying to recording-approval May 16, 2026 01:53 — with GitHub Actions Failure
@coderabbitai

coderabbitai Bot commented May 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1854bf69-7d47-4a56-9da2-e67a428d43ba

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/358-context-pending-ready-status

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@silongtan silongtan merged commit 26f47d0 into dev May 16, 2026
11 of 12 checks passed
@silongtan silongtan deleted the fix/358-context-pending-ready-status branch May 16, 2026 02:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix Bug fix or correctness repair flow:feature Standard feature/fix PR targeting BicameralAI/dev (the default flow) P2 Medium: next milestone or two; default for new issues post-triage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant