Phase 2 — State-machine & gateway integrity (rides 0.19.0)#316
Merged
Conversation
ruff format + check: green mypy roboco/: green (357 files) pytest unit+integration: 6905 passed, 10 pre-existing DB-contamination failures (pass in isolation) e2e smoke: 11 passed, 4 cross-scenario workspace-contamination failures (all 6 state-machine scenarios pass individually) Quality-gate fixes: - move function-local imports to module top (PLC0415) - fix M19 regression: submit_for_qa clears active_claimant_id so the competing-claimant guard lets the QA claim through - fix H7 regression: _StubGit gains is_pr_merged_for_task - fix M19 unit tests: mock session.execute for the FOR UPDATE lock - e2e H3: notes >= 20 chars; e2e H5: rich i_will_work_on inputs + PM unclaims (block reassigns to PM)
…_status (M19 follow-on) Phase 2 opus whole-branch review found the M19 follow-on clear lived in the gateway wrappers (qa_pass/qa_fail) not the transition methods (pass_qa/fail_qa) themselves. The direct REST routes POST /pass-qa and POST /fail-qa call the transitions directly, bypassing the wrappers and leaving the QA's stale active_claimant_id set in AWAITING_DOCUMENTATION / NEEDS_REVISION — the competing-claimant guard then rejects the next legitimate documenter/QA claim. admin_set_status had the same gap for a non-blocked override into a review/queue state (IN_PROGRESS->AWAITING_QA left the dev's id, blocking qa_claim). Root-cause fix: move the clear INTO pass_qa and fail_qa (mirroring submit_for_qa), add a clear in admin_set_status when new_status in _REVIEW_QUEUE_STATES and from_status != BLOCKED, and drop the now-redundant clears + flushes from the qa_pass/qa_fail wrappers. Every caller is covered; the wrappers keep their actor-mismatch warnings. Covering tests: test_pass_qa_clears_active_claimant_for_doc_claim (asserts a subsequent doc_claim succeeds), test_fail_qa_clears_active_claimant, test_admin_set_status_into_review_queue_clears_active_claimant, test_admin_set_status_non_review_queue_keeps_active_claimant. Updated the two wrapper unit tests that asserted the wrapper clears (now the transition's job).
|
Thanks for opening your first pull request on RoboCo! Quick checklist before review (most of these are enforced by CI, but worth a glance):
See CONTRIBUTING.md for the full workflow and the Code of Conduct for the community standards we follow. Welcome aboard — a maintainer will review shortly. |
ab5dac8 to
f7097c1
Compare
JournalService.delete_entry deleted the DB row but never de-indexed the RAG chunks, so deleted/private journal content bled forever into RAG answers and claim-time briefings. Add OptimalService.unindex_journal_entry mirroring unindex_playbook (vector-store delete_by_source + tracking-row delete via get_db_context, both idempotent + best-effort), and call it from delete_entry after the row commit inside a try/except so a de-index failure never errors the delete.
The memory distiller emits lessons with a fixed 'Problem: …' opening
shape, so two distinct lessons whose first 100 chars match collided on
learning_id = f"lrn-{md5(content[:100])[:12]}". replace_on_reingest then
routed both to the same source URI and the second ingest's replace_chunks
DELETE wiped the first lesson's chunks — silent data loss.
Hash the full content (widening the hex slice 12→16) so distinct bodies
get distinct ids and each retains its chunks.
… time mark_read and mark_all_read used to zero the unread counter FIRST, then run a bulk UPDATE … WHERE read_at IS NULL that stamped every inbound unread row. A send_chat_message committing between the counter-zero and the UPDATE inserted a new read_at NULL row that the UPDATE then stamped as read — the new message was silently consumed while the counter stayed 0. Mirrors get_unread_messages (same file): SELECT the unread message IDs at call time, UPDATE exactly those IDs, then recompute the unread counter from the DB via the existing _reset_unread_counter helper. A message arriving mid-call is not in the selected ID set, so the UPDATE skips it and the recomputed counter keeps it unread.
…empty/error/disabled
…ud 429 parks instead of crash-respawning
…ens roll into usage/cost summaries
…r's revisit counter survives a restart
…rates after 2026-08-31
…ilently falling back to a zero-usage env id
…ken-sweep test signatures)
…ck stays reachable
… tokens are bounded
…ry so a stolen cookie's exp is fixed
…t revokes the current jti
…w + jti revocation
[phase9] Auth hardening: bound agent tokens (iat/exp) + JWT jti revocation
[scan-fix] Phase 8: LLM providers / usage / billing
[Phase 7] schemas/conventions/MegaTask/API fixes (15 findings, scan-fix program)
Phase 6 — Panel frontend hardening (rides 0.19.0)
Phase 5 — Background-engine hardening (rides 0.19.0)
Phase 4 — Git / workflow hardening (rides 0.19.0)
Phase 3 — Data-integrity fixes (rides 0.19.0)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 2 — State-machine & gateway integrity (rides 0.19.0)
Second phase of the 2026-07-06 full-codebase-scan fix program. Eleven verified-real state-machine / gateway concurrency and integrity fixes plus an e2e smoke module, all under the existing
## [0.19.0]CHANGELOG section. Stacks on top of Phase 1 / Phase 1b (fix/scan-auth-security, PR #315) — base isfix/scan-auth-security; merge #315 first.Fixes
7172ab61) —add_dependencyrejects self-reference + detects cycles. Prevents a task from depending on itself or forming a dependency cycle that would deadlock dispatch.aec12879) —WorkSessionService.createtranslates the DBIntegrityErroron a duplicate active session into a cleanConflictError. Closes the claim/plan crash-into-respawn loop when a stale active session row collides with a fresh claim (migration 047's partial-unique index fires).fb31534e) —_qa_or_doc_claimlocks the task rowFOR UPDATEbefore the claim-guard check, with a competing-claimant guard. Prevents two QA/doc agents claiming the sameawaiting_qa/awaiting_documentationtask concurrently and both succeeding. PM-owner is intentionally overridable by the first reviewer (enforce_competing_claimant=Falseonpr_gate_claim).aad9c307) —docs_complete+mark_pr_createdlock the task rowFOR UPDATE. Prevents the lost-update where the documenter and developer's parallel completion writes race on the same task row.79961b06) —complete()gated to leaf / branchless tasks only whenIN_PROGRESS. Closes the PM-bypass of the PR-review gate (a PM couldcomplete()its own in-progress branchless root, skippingawaiting_pr_review).da546de6) —_unclaim_from_blockedclears the stale pre-block snapshot. Prevents a re-claim after a block reusing a stale snapshot and resuming from the wrong state.0002c14d) —admin_set_statusterminal guard + skips therevision_countincrement underforce=True. A forced admin status change is not a QA/PR bounce and must not inflate rework metrics.4bcbc9a2) —cell_pm_completeruns an idempotent "already merged to target" pre-check before merging. Prevents the merge-after-already-merged error when the integration branch was already merged to target.6af30978) — gateway post-transition side-effects (pass_review reassign+a2a, doc handoff, documents stamp) wrapped in the runner savepoint / try-except. A side-effect failure no longer corrupts the transition's committed state.16cf2d5e) —pass_qa/fail_qaaccept only tasks inawaiting_qa. Operational QA routes can no longer act on a task that already left the QA state.0c840258) —mark_pr_createdpassesaudit_agent_id. The PR-created audit event now attributes to the acting agent instead of a blank.fb066a0f) —tests/e2e_smoke/test_state_machine.py, one scenario per finding (M18/H4/H3/H5/H6/L29), scripted-agent over the real API + ephemeral Postgres + fake GitHub.b0b646b9) — ruff/mypy green; bundles three follow-on fixes surfaced during the gate:submit_for_qaclearsactive_claimant_id(root-cause for M19's competing-claimant guard letting the legitimate QA claim through),_StubGit.is_pr_merged_for_task(H7 test rig), and M19 unit-test mock-session fixes.ab5dac8f) — opus whole-branch review found theactive_claimant_idclear lived in the gateway wrappers (qa_pass/qa_fail) not the transition methods (pass_qa/fail_qa) themselves. The direct REST routesPOST /pass-qa+POST /fail-qacall the transitions directly, bypassing the wrappers → stale claimant rejected the next legitimate doc/QA claim;admin_set_statushad the same gap for a non-blocked override into a review/queue state. Fixed by moving the clear INTOpass_qa/fail_qa+admin_set_status(mirroringsubmit_for_qa), dropping the now-redundant wrapper clears. Covering tests:test_pass_qa_clears_active_claimant_for_doc_claim,test_fail_qa_clears_active_claimant,test_admin_set_status_into_review_queue_clears_active_claimant,test_admin_set_status_non_review_queue_keeps_active_claimant.Plan discrepancies (acknowledged)
FOR UPDATElock is still the correct fix for the real concurrent race (two agents claiming the same row); the test is a guard, not a TDD proof. Noted in the commit.pr_gate_claim's role-scoped guard is a superset of the competing-claimant guard. It is not (PM-owner is intentionally overridable by the first reviewer). Fixed with an explicitenforce_competing_claimantparam.Verification
uv run ruff format+ruff check— clean.uv run mypy roboco/— clean (357 files).uv run pytest tests/unit/ tests/integration/— 6905 passed; 10 pre-existing DB-contamination failures + 1 error (test_metrics_service.py,test_tasks_routes.py,test_board_review_gate_timeline_real_db.py) — all 206 pass when those three files run in isolation. Pre-existing test-isolation gap, not a Phase-2 regression.make e2e-smoke— every e2e scenario passes in isolation (all 19: 4 auth-gate from Phase 1b, 6 state-machine from Phase 2, 9 pre-existing). The full-suite run is order-dependent (9–15 pass / 4–10 fail) because the session-scopede2e_stackshares thebe-dev-1workspace across scenarios —test_state_machine.py's 6 scenarios contaminate each other within the file (full file = 4 failed / 2 passed; each scenario alone = 1 passed), and the bleed varies with pytest's run order. Pre-existing harness limitation (per-scenario workspace reset would fix it), documented out of scope for this phase; not a product regression.active_claimant_idclear in wrappers not transitions — fixed inab5dac8f; two Minor findings (M-2 H3 CEO force path, M-3 L30 sibling callers) are arguably-correct/theoretical-only, not push-blocking).Known pre-existing issues (not introduced here)
be-dev-1workspace across scenarios in one file — a per-scenario fixture reset would fix it. Harness change, not a service-layer fix.Model note
Implementer ran on sonnet; whole-branch review on opus. kimi-k2.7-code:cloud is not routable in this harness.
🤖 Generated with Claude Code