Skip to main content

V2.0 Implementation Plan

Who this page is for

This is the team's working document. The project leader designed the release; this page is the patch queue you work from.

Every patch below is independently assignable. Read your patch's Deps before starting - a patch may not begin until all its dependencies are merged. The application must boot after every single patch.

How to work a patch

  1. Claim the patch by putting your name in the Owner column of the status board below.
  2. Create a branch named v2/patch-NN-short-name off Strategies.
  3. Copy developer/tasks/task-template.md into developer/tasks/ and fill it in before writing code - this is an existing project convention, not a new one.
  4. Implement only what the patch's Files list names. Scope creep breaks the dependency graph for everyone else.
  5. Satisfy every line of Acceptance, then run the Verify step and paste its output into your PR.
  6. Open the PR against Strategies, linking your task doc.

Standing rules that apply to every patch

These come from the repository's existing CLAUDE.md files. Violating them causes silent, hard-to-diagnose failures.

RuleSource
Run backend modules from backend/ with python -m pkg.module - never python pkg\module.pyroot CLAUDE.md HARD RULE 1
Log messages must be ASCII-only. A non-ASCII character crashes on Windows cp1252 and is swallowed by a bare except Exceptionroot CLAUDE.md HARD RULE 2
All frontend API calls go through services/api.js - never import axios directly in a componentroot CLAUDE.md HARD RULE 7
Adding a doc under developer/packages/* means also adding its id to docs/sidebars.tsroot CLAUDE.md HARD RULE 8
Stop any preview server you start as soon as verification is done (ports 8000 / 5173)root CLAUDE.md HARD RULE 9
Use the loguru logger (from app.logging_config import logger) for anything beyond a routine printbackend/CLAUDE.md rule 3
Hand-check every autogenerated Alembic migration. Deleting a model does not produce a DROP TABLE, and create_foreign_key(None, ...) breaks downgrade()backend/AA/CLAUDE.md rule 3

Status board

#PatchPhaseOwnerDepsStatus
0Fix boot-breaking AA ImportError0unassigned-Not started
1Confine strategy_builder path traversal0unassigned0Not started
2De-duplicate shadowed routes0unassigned0Not started
3Stop printing DB passwords0unassigned0Not started
4Config + second engine1unassigned0Not started
5UserBase models1unassigned4Not started
6Second Alembic env + initial migration1unassigned5Not started
7Seed data + first-admin script1unassigned6Not started
8Auth primitives + Notifier2unassigned5Not started
9Auth router /api/auth/*2unassigned7, 8Not started
10Entitlements + gating dependencies2unassigned9Not started
11Gate all routers + WebSocket2unassigned10Not started
12UI primitives + persisted store3unassigned-Not started
13Axios interceptors3unassigned12Not started
14Auth pages + route guards + nav3unassigned13Not started
15Admin user management screen3unassigned14Not started
16Theme engine + 4 palettes + picker4unassigned12Not started
17Chart token refactor4unassigned16Not started
18De-hardcode sweep A (3 heavy files)4unassigned16Not started
19De-hardcode sweep B (14 files)4unassigned18Not started
20Documentation reconciliation5unassigned11, 15, 19Not started

Dependency graph

Patch 12 has no dependencies and can start on day one alongside Patch 0. Frontend work (Phases 3 and 4) runs in parallel with backend work (Phases 1 and 2) throughout.


Phase 0 - Unblock and harden

Patch 0 blocks everything

The backend does not currently start. Nothing in this release can be verified until Patch 0 is merged.

Patch 0 - Fix boot-breaking AA ImportError

  • Owner role: Backend
  • Deps: none
  • Files: backend/app/main.py (lines 20, 67)

aa_routes.py:19 imports AnnualReturn, a class retired by migration 95e95ed1bbaa. Because main.py imports every router at module level, this ImportError takes down the whole application.

Comment out both the import (main.py:20) and the registration (main.py:67), exactly as backend/AA/CLAUDE.md rule 4b prescribes. Add a comment pointing at that rule so nobody re-enables it blindly.

Do not "fix" this by rewriting aa_routes.py

Rule 4b is explicit: re-enabling requires rewriting aa_routes.py and allocation_engine.py against te_asset/te_price first. That rebuild is deferred out of v2.0 - Asset Allocation ships as an Under Construction page in Patch 14.

  • Acceptance: The import and registration are commented out with a rule reference. Backend starts cleanly.
  • Verify:
cd backend && uvicorn app.main:app --port 8000

Then curl http://localhost:8000/ returns HTTP 200.

Patch 1 - Confine strategy_builder path traversal

  • Owner role: Backend
  • Deps: 0
  • Files: backend/app/api/strategy_builder.py (lines 118-122)

BacktestRequest.filePath is a client-supplied string that is os.path.exists-checked and then read with pd.read_csv, with no path confinement - an arbitrary file read. Resolve the path against Config.CSV_PATH/strategy_uploads and reject anything that escapes that root after os.path.realpath normalisation.

  • Acceptance: A filePath outside the uploads root returns HTTP 400. Legitimate uploaded files still run.
  • Verify: POST /api/strategy-builder/run with a filePath of ../../../etc/passwd returns 400, not 200.

Patch 2 - De-duplicate shadowed routes

  • Owner role: Backend
  • Deps: 0
  • Files: backend/app/api/routes.py (lines 266, 279, 292)

/api/spot-symbols, /api/fo-expiries-by-range and /api/fo-symbols-by-expiry are each registered twice. FastAPI silently keeps the first definition and ignores the second, so the duplicates are dead code that will mislead whoever gates these routes in Patch 11.

  • Acceptance: Three duplicate definitions removed; the surviving handlers unchanged.
  • Verify: Path count in /openapi.json drops by exactly 3; all three endpoints still respond.

Patch 3 - Stop printing DB passwords

  • Owner role: Backend
  • Deps: 0
  • Files: backend/nseeod/pgs.py (lines 24-27, 55-58), backend/app/core/config.py (lines 79-84)

Both database_conn() and conn() print Config.DB_PASS in cleartext on every connection open. Replace with a masked value, or drop the credential from the message entirely.

  • Acceptance: No credential appears in stdout from any code path.
  • Verify:
cd backend && python -m nseeod.downloadbhav

Output contains no password string.


Phase 1 - teudb foundation

Full schema reference: User DB (teudb) Overview.

Patch 4 - Config + second engine

  • Owner role: Backend
  • Deps: 0
  • Files: backend/app/core/config.py, backend/app/database/user_session.py (new), backend/.env.example (new)

Add {_PFX}_UDB_* variables following the existing _PFX pattern at config.py:17 and :32-36, plus USER_DATABASE_URL and SYNC_USER_DATABASE_URL properties mirroring :44-52 and :54-64. Add JWT_SECRET with no default - the app must refuse to start without it.

user_session.py mirrors session.py:11-40: a second create_async_engine (pool_size 5, max_overflow 10 - auth traffic is low-volume), UserSessionLocal, class UserBase(DeclarativeBase), and async def get_user_db().

Create backend/.env.example documenting every variable - one does not exist today.

  • Acceptance: settings.USER_DATABASE_URL resolves; get_user_db yields a working session; startup fails with a clear message if JWT_SECRET is unset.
  • Verify: SELECT 1 against teudb through the new session factory.

Patch 5 - UserBase models

  • Owner role: Backend
  • Deps: 4
  • Files: backend/app/models/user_models.py (new)

All 10 tables as ORM classes on UserBase, with every CHECK constraint, index and partial unique index from the schema doc. The named constraints matter - ck_usr_master_mobile_for_privileged is what makes the differential verification rule unbypassable.

  • Acceptance: 10 model classes; every constraint from the DDL present and named.
  • Verify: cd backend && pytest tests/test_user_models.py
Run the whole suite, not just your file

AA/CLAUDE.md rule 4a records that a stale import in one test file once aborted the entire pytest collection. Run pytest with no arguments after this patch.

Patch 6 - Second Alembic env + initial migration

  • Owner role: Backend
  • Deps: 5
  • Files: backend/alembic.ini, backend/alembic_user/ (new)

Add a [user] section to alembic.ini with script_location = %(here)s/alembic_user. The new env.py sets its URL from settings.SYNC_USER_DATABASE_URL and uses target_metadata = UserBase.metadata.

It needs no include_object filter (unlike alembic/env.py:55-63) because teudb contains nothing but UserBase tables.

Why a second section rather than branch labels: branch labels share one alembic_version table, but here the version table must live inside teudb. See teudb Migrations.

  • Acceptance: alembic -n user upgrade head creates all 10 tables in teudb. The tedb chain is untouched.
  • Verify: alembic -n user check reports no drift; alembic heads on tedb is still 604991c1d258.

Patch 7 - Seed data + first-admin script

  • Owner role: Backend / DevOps
  • Deps: 6
  • Files: backend/alembic_user/versions/*, backend/scripts/create_admin.py (new)

A data migration seeding 6 usr_category rows, 5 usr_plan rows, roughly 20 usr_feature rows and the usr_plan_feature grants. Baseline grants: only ADMIN and DEVELOPER hold page.* features, except page.asset_allocation, which is granted to ANON.

create_admin.py creates the first ADMIN interactively - prompting for email, mobile and password - with email_verified=true, mobile_verified=true, status='active'.

  • Acceptance: Seed rows present; the script creates a login-capable admin.
  • Verify: Run the script, then confirm the row has catid=1 and status='active'.

Phase 2 - Auth backend

Patch 8 - Auth primitives + Notifier

  • Owner role: Backend
  • Deps: 5
  • Files: backend/app/auth/ (new package), backend/requirements.txt
FileContents
security.pyhash_password / verify_password (argon2id), hash_token (sha256), new_token, new_otp
tokens.pycreate_access_token, create_refresh_token, decode_token
notifier.pyNotifier ABC plus ConsoleNotifier / SmtpNotifier / SmsNotifier and get_notifier()

New pinned dependencies:

argon2-cffi==25.1.0
PyJWT==2.10.1
email-validator==2.2.0
ASCII-only in ConsoleNotifier

ConsoleNotifier logs the OTP to stdout via loguru. Per root CLAUDE.md HARD RULE 2, no arrows, ellipses or box-drawing characters - they crash on Windows cp1252 and the exception is silently swallowed.

  • Acceptance: argon2 round-trips; JWT encodes and decodes with correct expiry; ConsoleNotifier prints an ASCII-only OTP line.
  • Verify: pytest tests/test_auth_security.py

Patch 9 - Auth router

  • Owner role: Backend
  • Deps: 7, 8
  • Files: backend/app/api/auth_routes.py (new), backend/app/main.py

Endpoints under /api/auth: register, login, refresh, logout, verify-email, request-otp, verify-otp, me, forgot-password, reset-password.

Raw tokens and OTPs are never stored - only sha256 hashes, in usr_verification.token_hash and usr_session.refresh_hash. Refresh tokens rotate on use, with rotated_from forming a chain so token reuse can be detected.

  • Acceptance: The full flow works: register, read OTP from console, verify, log in, refresh, log out.
  • Verify: curl the sequence end to end, reading the OTP from the backend console.

Patch 10 - Entitlements + gating dependencies

  • Owner role: Backend
  • Deps: 9
  • Files: backend/app/auth/entitlements.py, backend/app/auth/deps.py

resolve_features(user) returns the union of the category's grants and the active subscription's usr_plan_feature rows, TTL-cached. An anonymous request resolves against the ANON category row rather than taking a special code path.

Dependencies exported: get_optional_user, get_current_user, require_category(*codes), require_feature(code).

  • Acceptance: 401 for anonymous, 403 for wrong tier, 200 for entitled.
  • Verify: pytest tests/test_entitlements.py

Patch 11 - Gate all routers + WebSocket

  • Owner role: Backend
  • Deps: 10
  • Files: backend/app/main.py (lines 57-67), backend/app/api/websocket.py (lines 16-19, 38)

Gate at include_router time, not with per-endpoint decorators:

_ADMIN_DEV = [Depends(require_category("ADMIN", "DEVELOPER"))]
app.include_router(api_router, dependencies=_ADMIN_DEV)

This fails closed: a teammate adding an endpoint to an existing router cannot forget to protect it. Per-endpoint decorators fail open by omission.

Split GET /api/health out of the gated api_router into its own ungated router. Set docs_url=None and redoc_url=None when APP_ENV == "VPS" - the OpenAPI schema currently advertises the entire surface to anyone who reaches the port.

WebSocket: accept a token query parameter on the handshake and decode it before manager.connect() (which currently accepts unconditionally at websocket.py:16-19); close with code 4401 on failure.

Public surface after this patch: GET /, GET /api/health, POST /api/auth/*. Everything else requires ADMIN or DEVELOPER.

  • Acceptance: Every data endpoint returns 401 anonymously; health and auth stay public.
  • Verify: pytest tests/test_route_gating.py - parametrized over every gated path.
Deployment coupling

The moment this patch reaches the VPS, all existing anonymous usage breaks. It must deploy together with Patch 14, and the admin account from Patch 7 must already exist.


Phase 3 - Frontend auth

Patch 12 - UI primitives + persisted store

  • Owner role: Frontend
  • Deps: none - start immediately
  • Files: frontend/src/components/ui/ (new), frontend/src/store/useStore.js

There is no shared UI primitive layer today - every page hand-rolls the same Tailwind card recipe. Add Button, Card, Input and Modal so the eight new pages in Patch 14 do not multiply that duplication.

Wrap the store in zustand's persist middleware. It currently has none (useStore.js:1-43), which is why the theme toggle resets to dark on every reload. Use partialize to persist only accessToken, refreshToken, user and theme - the chart and F&O selection state at lines 9-40 must stay ephemeral.

  • Acceptance: Four primitives exported; theme and auth survive a page reload.
  • Verify: Set a theme, reload, confirm it persists.

Patch 13 - Axios interceptors

  • Owner role: Frontend
  • Deps: 12
  • Files: frontend/src/services/api.js (lines 13-18), frontend/src/pages/ContractNote.jsx (lines 10-26)

The axios instance has zero interceptors today. Add a request interceptor attaching Authorization: Bearer, and a response interceptor that on 401 calls POST /api/auth/refresh once, retries the original request, and on second failure clears the store and redirects to /login. Guard with a single-flight promise so parallel 401s trigger exactly one refresh.

ContractNote.jsx:10-26 bypasses this module with raw fetch and would silently lose the auth header - migrate it, per root CLAUDE.md HARD RULE 7.

  • Acceptance: Bearer attached to every call; one refresh per expiry burst; no raw fetch remains in src/.
  • Verify: In DevTools, force an expired token and confirm exactly one refresh call fires.

Patch 14 - Auth pages + route guards + role-filtered nav

  • Owner role: Frontend
  • Deps: 13
  • Files: frontend/src/App.jsx, frontend/src/components/Header.jsx (lines 11-22, 92-109), eight new pages

New pages: Login, Register, VerifyEmail, VerifyMobile, Profile, Unauthorized, NotFound, UnderConstruction.

Wrap protected children in a ProtectedRoute guard. Replace the wildcard fallback to Home (App.jsx:48) with a real NotFound.

The navLinks array in Header.jsx gains a requires field per entry, filtered against the user's resolved feature set. The dead Search and Settings buttons at lines 92-109 become the theme picker and a profile menu.

AssetAllocation.jsx is replaced by the UnderConstruction view. The existing 349-line implementation stays in git history for the later rebuild.

Anonymous visitors see: the Home landing page, Asset Allocation (Under Construction), and a Sign in call to action. Nothing else.

  • Acceptance: Anonymous sees only the public surface; guards redirect; nav reflects category.
  • Verify: Log in as each of ADMIN, DEVELOPER and FREE, and confirm the nav differs correctly.

Patch 15 - Admin user management screen

  • Owner role: Frontend
  • Deps: 14
  • Files: frontend/src/pages/AdminUsers.jsx (new), plus /api/admin/* endpoints if not already covered

List and filter users, change category, activate and suspend.

  • Acceptance: Promoting a FREE user to POWER without a verified mobile is rejected, and the UI surfaces why.
  • Verify: Attempt that promotion; confirm the constraint violation surfaces as a clean 400 with a readable message.

Phase 4 - Theme engine

Full palettes and the sweep mapping table: Theme System.

Patch 16 - Theme engine + 4 palettes + picker

  • Owner role: Frontend
  • Deps: 12
  • Files: frontend/src/index.css, frontend/tailwind.config.js, frontend/src/theme/ (new), App.jsx (lines 21-29), Header.jsx (lines 92-109)

Replace the single .dark class with a data-theme attribute on the root element. This is safe: tailwind.config.js:7 sets darkMode: 'class' but zero dark: variant classes exist - all theming already flows through CSS variables.

Expand from 8 variables to 16 and register each in tailwind.config.js under colors.trading.

The scrollbar rules at index.css:34-50 are currently hardcoded dark regardless of theme - move them onto the variables.

Midnight reproduces the current dark palette exactly, so existing screens are visually unchanged by the swap.

  • Acceptance: All four themes switch live and persist; the scrollbar follows the theme.
  • Verify: Cycle all four themes on every page.

Patch 17 - Chart token refactor

  • Owner role: Frontend
  • Deps: 16
  • Files: charts/LightweightChart.jsx, components/ChartView.jsx, components/EquityCurve.jsx, components/StrategyLogicBox.jsx, pages/ContractNote.jsx, pages/Dashboard.jsx

Charts hardcode colors in seven places. Add frontend/src/theme/tokens.js exporting getChartTokens(), which reads live values via getComputedStyle on the root element, plus a useChartTokens() hook that re-reads on theme change.

  • Acceptance: Charts recolor on theme change without a reload.
  • Verify: Open Dashboard with a chart rendered, switch themes, watch it recolor.

Patch 18 - De-hardcode sweep A (heavy files)

  • Owner role: Frontend
  • Deps: 16
  • Files: pages/BhavDownloader.tsx (54 occurrences), pages/ContractNote.jsx (28), pages/Dashboard.jsx (22)

Apply the mapping table from the theme doc mechanically. These three files hold roughly 45 percent of the 234 total occurrences.

  • Acceptance: Zero hardcoded white or gray color utilities remain in these three files.
  • Verify: Grep count returns 0; all three pages are legible in Daylight.

Patch 19 - De-hardcode sweep B (remaining 14 files)

  • Owner role: Frontend

  • Deps: 18

  • Files: the remaining 14 files, HolidayMaster.jsx (22) and AssetAllocation.jsx (11) worst first

  • Acceptance: Repo-wide grep for hardcoded color utilities returns 0.

  • Verify: All four themes on all pages.

Do not merge this partially

Daylight is unusable until the sweep is complete. A half-swept merge ships a visibly broken theme.


Phase 5 - Documentation

Patch 20 - Documentation reconciliation

  • Owner role: Docs
  • Deps: 11, 15, 19

Most of this release's documentation was written up front by the project leader - this page, plus the design docs linked below. Patch 20 is the reconciliation pass once the code exists:

  • Update docs/docs/versions/v2.0.md with what actually shipped, including any deviations from this plan

  • Move JWT auth from Planned to Completed in docs/docs/versions/roadmap.md

  • Add real screenshots to user-guide/themes.md

  • Correct any drift between these design docs and the merged implementation

  • Fix docs/docs/internal/db-design.md, which is stale and documents column names that do not exist

  • Verify: cd docs && npm run build completes with no broken-link warnings.