Skip to content

feat(backtests): honor per-user concurrent dashboard slots - #342

Open
Allan-Feng wants to merge 3 commits into
Open-Finance-Lab:mainfrom
Allan-Feng:feat/concurrent-dashboard-backtests
Open

feat(backtests): honor per-user concurrent dashboard slots#342
Allan-Feng wants to merge 3 commits into
Open-Finance-Lab:mainfrom
Allan-Feng:feat/concurrent-dashboard-backtests

Conversation

@Allan-Feng

Copy link
Copy Markdown
Collaborator

Summary

  • Replace process-wide single-flight dashboard backtests with entitlement-aware slots.
  • Poll every in-flight live_run_id so each running agent card shows its own progress.
  • Keep Configure clickable while a backtest is running.

Stacking

Depends on #341 (feat/admin-entitlements). This branch includes that commit plus the concurrent follow-up. Review the latest commit for concurrent-only changes; merge #341 first, then this.

Test plan

  • pytest dashboard/backend/tests/test_admin_users.py dashboard/backend/tests/test_backtest_progress_card.py -q
  • Set max concurrent ≥ 2, start two backtests, both cards show step/percent
  • Third run refused with clear message when at cap

Made with Cursor

Allan-Feng and others added 2 commits August 12, 2026 20:47
Add per-user max_concurrent_backtests and credits, admin list/patch/bootstrap
APIs, and an Admin page with site stats. Signup stays role=user.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace process-wide single-flight with entitlement-aware slots, poll every
in-flight live_run_id for card progress, and keep Configure clickable while
a run is in progress.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

@Allan-Feng is attempting to deploy a commit to the allan-feng's projects Team on Vercel.

A member of the Team first needs to authorize it.

Comment thread dashboard/backend/tests/test_backtest_progress_card.py Fixed
Comment thread dashboard/backend/tests/test_backtest_progress_card.py Fixed
Comment thread dashboard/backend/tests/test_backtest_progress_card.py Fixed
Mocked workers never finalize slots, so the process-wide cap of 20
refused later /backtest/run calls. Also register the new /api/admin
routes and bump cache-buster floors.

Co-authored-by: Cursor <cursoragent@cursor.com>
@FlyM1ss

FlyM1ss commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Heads-up from #348 — cache-buster collision on this branch, and CI cannot see it.

This PR ships app.js?v=88, which is exactly what main already serves. Because the line text is identical there's no merge conflict and nothing goes red, but it changes app.js by ~390 lines — so on deploy, every browser holding the current v=88 would keep running the old bundle.

styles.css?v=97 collides with #341, which is also on 97 (and on app.js?v=98).

The guard only compares app.html against the test file, never against main (#347), which is why this passes. Please bump app.js above main's floor at merge time in both app.html and test_frontend_fast_boot.py, and take styles.css past 97 if #341 lands first.

FlyM1ss added a commit to Allan-Feng/AgenticTrading that referenced this pull request Aug 14, 2026
Security review fixes for Open-Finance-Lab#341. The admin routes themselves held up — one
correct `require_admin` gate, a transactional PATCH, a constant-time compare,
and both twins serializing the one-shot promotion. What did not hold up was the
plane around them: the entitlement was built as a control and wired where it
could not bind, and bootstrap's own budgets could be turned on the operator.

- Close the two cap bypasses. `resolve_owner_cap_context` returned None the
  instant `owner_user_id` was falsy, and agent creation needs no login at all
  (`_require_owner_context` accepts a self-chosen X-Session-Id), so signing in
  was strictly worse than staying anonymous: one shared account budget versus
  the per-agent cap times however many agents you registered. Unclaimed agents
  are now billed to the browser session that created them, resolved inside the
  store (`list_owner_scope_agent_ids`, both twins) because `_public_agent`
  withholds `owner_browser_session` on purpose — `_owner_context` accepts that
  value AS an ownership credential.
- Cap the legacy `/api/v1/backtest/*` surface. It authenticates nothing and
  writes no `protocol_runs` row, so all three protocol caps were blind to it:
  a bare POST spawned a thread and pinned a bar window, unbounded, beside a
  quota system holding every authenticated agent to its limit. Per-session and
  global budgets, counted and inserted under one lock. Opt-in, so the protocol
  path (already capped three ways) is not charged twice.
- Raise the default quota to 5 and make it env-overridable. Nothing seeds
  `user_entitlements` at signup and nothing backfills, so `1` was not a default
  but every existing account's live limit on deploy day — a silent demotion
  from 5x(agents), with no remedy short of an admin.
- Bootstrap: check the global budget AFTER the compare. Checked first, 20 wrong
  guesses a window — re-spent every window, from any account, and signup is
  open — refused the correct secret indefinitely, and the window it blocked is
  exactly the fresh-deploy window the route exists for. Require >= 32 chars
  (refused as if unset), and answer 403 for unset/weak/wrong alike so a caller
  cannot learn whether a deployment is bootstrappable.
- Lock FixedWindowRateLimiter. The module had no `threading` import at all, and
  the buckets are mutated on every call — `_pruned` popleft()s from inside
  `check`, not only `record`. `allow()` is now atomic.
- Reject control characters in emails at the validator. strip() only touched
  the ends, so an interior newline validated, was stored, and forged lines in
  the admin console's role-change confirm() — a native dialog with no markup
  to escape. One fix at the source beats one guard per renderer.
- Get the store off the event loop in signup/login. This PR added a sync
  `get_entitlements()` to `_auth_json`, called from both async handlers, which
  the threadpool guard cannot see (it pins plain-`def` routes, and those two
  are exempt for already awaiting). Bundled with `create_session` into one
  `asyncio.to_thread` hop, plus the guard that can see it.

Also: router-level `require_admin` so a route added later is gated by default,
a 403 case for all four routes enumerated from the router, an audit line on
every role/quota change (ids only — no email reaches a print sink), quota floor
0 so a quota can actually suspend, `credits` labelled not-enforced in the
console it is editable from, and the first Postgres tests for
`promote_first_admin` — prod's copy of the one-shot predicate had none.

Cache busters to 100/100: Open-Finance-Lab#342 also ships styles.css?v=97 and Open-Finance-Lab#344 ships 99/98,
so 98/97 would have collided with no git conflict (Open-Finance-Lab#347/Open-Finance-Lab#348).

Backend suite: 2797 passed, 76 skipped. SDK suite: 171 passed, 2 skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QCiPGs3ZYVjyFYVLa6E7bZ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants