Commit 479fcd1
authored
fix(rbac+estimation): drop legacy OrgToUser.role and harden every estimate_bud_dates caller (#181)
* fix(rbac+estimation): repair count_active_by_role GROUP BY and harden every estimate_bud_dates caller
The dynamic-role refactor introduced count_active_by_role with a
parameterised CASE in both SELECT and GROUP BY. SQLAlchemy compiled
each call to _effective_role_case() as a fresh expression, so the two
CASEs emitted distinct bind parameters and Postgres rejected the query
with "column roles.scope_type must appear in the GROUP BY clause". That
GroupingError cascaded through get_role_capacity into estimate_bud_dates
and crashed every caller. The original failure was then masked as
InFailedSQLTransactionError on the next innocent statement because every
"except Exception" around estimation swallowed the error without
rolling back, leaving the connection in an aborted state.
Two-part fix:
1. Repository: hoist the CASE into a single labelled expression so SELECT
and GROUP BY reference the same object. Postgres accepts GROUP BY by
output column alias, sidestepping the expression-identity trap.
2. Every estimate_bud_dates caller that ran on a shared session is now
wrapped in db.begin_nested() (Postgres SAVEPOINT). A query failure
inside the estimator only rolls back its own writes; the outer txn
stays alive and prior flushed writes survive. Same session, same
connection — no row-lock conflict with the outer's pending writes.
Each except now logs with exc_info=True so the real traceback is not
lost again.
- handle_prd_result: explicit flush of linked-feature inserts before
the savepoint so an autoflush inside it cannot scope them to the
savepoint and lose them on rollback.
- handle_tech_arch_result: existing flush already anchored
tech_spec_md + impacted_repos; only the swallow shape changed.
- handle_testing_result: moved the trailing flush above the savepoint
for the same anchoring reason.
- check_all_prs_merged: create_agent_task_for_stage already commits
internally, so no extra flush; SAVEPOINT still isolates the webhook's
trailing commit from estimator failure.
- code_review_override endpoint: outer was already committed earlier
in the handler, so estimation safely runs in a fresh AsyncSessionLocal
without a row-lock conflict.
Inline imports in the touched handlers were hoisted to top of file per
the project's import policy.
Verified end-to-end against live Postgres on the failing bud:
- count_active_by_role / get_role_capacity / estimate_bud_dates all
succeed.
- Forced SQL error inside db.begin_nested() raises cleanly; outer txn
stays valid; subsequent outer query sees the pre-savepoint flush.
- Full handler flow (outer flush -> SAVEPOINT estimate incl. LLM call
-> outer rollback) completes in 5.7s with no deadlock.
- All four triggers (prd_completed, tech_arch_completed,
testing_completed, prs_merged) pass.
ruff + mypy clean on the four modified files.
Signed-off-by: Arun Rajkumar <mickyarunr@gmail.com>
* chore(format): apply ruff format on the estimation-resilience edits
The PR-181 commit passed `ruff check` locally but not `ruff format
--check` — CI's separate format gate flagged three files. Pure
whitespace: ruff collapses multi-line calls / logger.warning args
that fit on one line. No behavioural change. lint, mypy, and the
behaviour-verifying live tests from the original commit still pass.
Signed-off-by: Arun Rajkumar <mickyarunr@gmail.com>
---------
Signed-off-by: Arun Rajkumar <mickyarunr@gmail.com>1 parent 6244428 commit 479fcd1
4 files changed
Lines changed: 80 additions & 51 deletions
File tree
- backend/app
- api/v1
- repositories
- services
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| 37 | + | |
37 | 38 | | |
38 | 39 | | |
39 | 40 | | |
| |||
92 | 93 | | |
93 | 94 | | |
94 | 95 | | |
| 96 | + | |
95 | 97 | | |
96 | 98 | | |
97 | 99 | | |
| |||
1126 | 1128 | | |
1127 | 1129 | | |
1128 | 1130 | | |
1129 | | - | |
1130 | | - | |
1131 | | - | |
1132 | 1131 | | |
1133 | 1132 | | |
1134 | | - | |
1135 | | - | |
1136 | 1133 | | |
1137 | 1134 | | |
1138 | 1135 | | |
| |||
1274 | 1271 | | |
1275 | 1272 | | |
1276 | 1273 | | |
1277 | | - | |
| 1274 | + | |
| 1275 | + | |
| 1276 | + | |
| 1277 | + | |
| 1278 | + | |
| 1279 | + | |
| 1280 | + | |
1278 | 1281 | | |
1279 | | - | |
1280 | | - | |
1281 | | - | |
1282 | | - | |
1283 | | - | |
1284 | | - | |
1285 | | - | |
1286 | | - | |
1287 | | - | |
1288 | | - | |
1289 | | - | |
| 1282 | + | |
| 1283 | + | |
| 1284 | + | |
| 1285 | + | |
| 1286 | + | |
| 1287 | + | |
| 1288 | + | |
| 1289 | + | |
| 1290 | + | |
| 1291 | + | |
1290 | 1292 | | |
1291 | 1293 | | |
1292 | 1294 | | |
1293 | 1295 | | |
| 1296 | + | |
1294 | 1297 | | |
1295 | 1298 | | |
1296 | 1299 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
284 | 284 | | |
285 | 285 | | |
286 | 286 | | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
287 | 292 | | |
288 | | - | |
| 293 | + | |
289 | 294 | | |
290 | 295 | | |
291 | 296 | | |
292 | 297 | | |
293 | | - | |
| 298 | + | |
294 | 299 | | |
295 | 300 | | |
296 | 301 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| 33 | + | |
33 | 34 | | |
| 35 | + | |
| 36 | + | |
34 | 37 | | |
35 | 38 | | |
| 39 | + | |
36 | 40 | | |
37 | 41 | | |
38 | 42 | | |
| |||
96 | 100 | | |
97 | 101 | | |
98 | 102 | | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | | - | |
104 | | - | |
105 | | - | |
106 | | - | |
107 | | - | |
108 | | - | |
109 | | - | |
110 | | - | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
111 | 122 | | |
112 | 123 | | |
113 | 124 | | |
| |||
254 | 265 | | |
255 | 266 | | |
256 | 267 | | |
257 | | - | |
258 | | - | |
259 | | - | |
260 | 268 | | |
261 | 269 | | |
262 | 270 | | |
| |||
292 | 300 | | |
293 | 301 | | |
294 | 302 | | |
295 | | - | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
296 | 310 | | |
297 | | - | |
298 | | - | |
299 | | - | |
| 311 | + | |
| 312 | + | |
300 | 313 | | |
301 | | - | |
| 314 | + | |
302 | 315 | | |
303 | 316 | | |
304 | | - | |
305 | | - | |
306 | 317 | | |
307 | 318 | | |
308 | 319 | | |
| |||
476 | 487 | | |
477 | 488 | | |
478 | 489 | | |
479 | | - | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
480 | 499 | | |
481 | | - | |
482 | | - | |
483 | | - | |
| 500 | + | |
| 501 | + | |
484 | 502 | | |
485 | | - | |
486 | | - | |
| 503 | + | |
487 | 504 | | |
488 | 505 | | |
489 | 506 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
34 | 35 | | |
35 | 36 | | |
36 | 37 | | |
| |||
178 | 179 | | |
179 | 180 | | |
180 | 181 | | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
181 | 186 | | |
182 | | - | |
183 | | - | |
184 | | - | |
| 187 | + | |
| 188 | + | |
185 | 189 | | |
186 | | - | |
| 190 | + | |
187 | 191 | | |
0 commit comments