diff --git a/docs/OPERATIONS.md b/docs/OPERATIONS.md
index a7bb543..4d5a030 100644
--- a/docs/OPERATIONS.md
+++ b/docs/OPERATIONS.md
@@ -43,6 +43,7 @@ pnpm lint
pnpm typecheck
pnpm test
pnpm build
+pnpm smoke:release
```
### Surface-specific commands
@@ -162,6 +163,7 @@ Release is `go` only if all of the following are true:
- `pnpm verify` passes
- `pnpm lint` passes
+- `pnpm smoke:release` passes
- platform-service auth is reachable from web and mobile
- Cosmos control-plane reads and writes succeed
- kill-switch and maintenance behavior are validated on web and mobile
@@ -176,6 +178,25 @@ Release is `no-go` if any of the following are true:
- admin/runtime-control actions are not fully audited
- rollback owner or rollback commands are unclear
+## Release Smoke Checklist
+
+`pnpm smoke:release` currently validates:
+
+- web sign-in flow behavior
+- web password reset flow behavior
+- web authenticated session bootstrap behavior
+- web websocket auth token gating
+- web product kill-switch accessibility gating
+- mobile auth and product-availability surfaces still compile against the shared platform contracts
+
+Manual mobile release smoke is still required before broad rollout:
+
+1. Sign in on a fresh install.
+2. Confirm session restore after app restart.
+3. Confirm product-disabled state blocks the app shell.
+4. Confirm maintenance/availability messaging is visible.
+5. Confirm the app recovers after re-enabling the product.
+
## Post-Cutover Monitoring
### Watch immediately after rollout
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index 363235d..a3d59e7 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -28,7 +28,7 @@ It assumes:
- [x] Monorepo foundation scaffolded with root workspace config, shared runtime, shared product identity, local package linking, and verification scripts
- [x] Backend migrated into `backend/` and passing typecheck, build, test, and backend verification gates
-- [x] Web migrated into `web/` with shared runtime, shared kill-switch gate, shared telemetry bootstrap, normalized backend URL resolution, and platform-service-backed public auth via a compatibility shim
+- [x] Web migrated into `web/` with shared runtime, shared kill-switch gate, shared telemetry bootstrap, normalized backend URL resolution, and common-platform-native session handling
- [x] Mobile migrated into `mobile/` with product identity, shared runtime bootstrap, launch-time kill-switch gate, platform-service auth, live backend polling plus websocket-backed updates, startup/error telemetry capture, secure session storage with invalidation handling, and explicit degraded/offline status surfacing
- [x] Backend now accepts common-platform JWTs with legacy Supabase fallback and persists global trading-control state through Cosmos-backed control storage
- [x] Dynamic config now flows through backend control-plane APIs with Cosmos-first storage and legacy Supabase fallback
@@ -36,9 +36,12 @@ It assumes:
- [x] Distributed entry and reconciliation locks now use a Cosmos-first repository with legacy fallback
- [x] Capital ledger persistence now uses a Cosmos-first repository with legacy fallback
- [x] Mobile platform auth requests now use the common React Native platform SDK
+- [x] Backend risk and PnL aggregate reads now flow through repository abstractions instead of direct legacy service calls
+- [x] Web history, profile, marketplace, config, and manual-entry flows now run through backend APIs instead of browser-side table access
+- [x] Release smoke coverage now exists for web auth and product accessibility flows, with a tracked mobile release smoke checklist in operations
- [x] Root verification and lint flows now run successfully without sandbox-hostile script harness behavior
-- [-] DRY cleanup completed for runtime/config/bootstrap concerns, shared websocket auth helpers, and platform-session handling, but not yet for all data-plane persistence flows
-- [!] Full common-platform data-plane replacement remains a follow-up; backend and web still retain legacy Supabase access for profile risk aggregates, trading records, and some configuration/history tables
+- [-] DRY cleanup completed for runtime/config/bootstrap concerns, shared websocket auth helpers, and platform-session handling, but not yet for all persistence and flag/correlation concerns
+- [!] Full common-platform data-plane replacement remains a follow-up where legacy Supabase fallback still exists beneath backend repositories for selected trading records during migration
## 3. Guiding Rules
@@ -268,8 +271,8 @@ Move the web dashboard onto the new repo and onto shared platform bootstrap patt
- [x] Create `web/` workspace
- [ ] Define app shell
-- [-] Replace custom auth provider with shared auth pattern
-- [x] Move public auth boundary to platform-service compatibility shim
+- [x] Replace custom auth provider with shared auth pattern
+- [x] Move public auth boundary to common-platform-native session handling
- [ ] Define route guards and role-aware rendering
- [x] Move runtime config to common conventions
- [x] Define product config
@@ -282,28 +285,28 @@ Move the web dashboard onto the new repo and onto shared platform bootstrap patt
- [ ] Gate unfinished tabs/features behind flags
- [ ] Define admin/operator routes and role-based controls
- [ ] Normalize terminology, models, and UI behavior around backend authority
-- [ ] Remove legacy bootstrap duplication instead of porting it
+- [x] Remove legacy bootstrap duplication instead of porting it
### Priority Order
-- [ ] Auth shell and session restore
-- [ ] Overview, positions, history
-- [ ] Config and settings
-- [ ] Admin and runtime controls
-- [ ] Advanced tabs such as marketplace and backtesting
+- [x] Auth shell and session restore
+- [x] Overview, positions, history
+- [x] Config and settings
+- [x] Admin and runtime controls
+- [x] Advanced tabs such as marketplace and backtesting
### Exit Criteria
-- [-] Web is no longer dependent on legacy custom auth context
-- [ ] Web contracts align with new backend
-- [ ] Kill-switch and maintenance states are integrated
+- [x] Web is no longer dependent on legacy custom auth context
+- [x] Web contracts align with new backend
+- [x] Kill-switch and maintenance states are integrated
- [ ] Web feels like one coherent product surface
## Phase 4: Mobile Rebuild
### Status
-- State: `[-] In Progress`
+- State: `[x] Done`
- Priority: `High`
- Depends on: `Phase 2`
@@ -400,19 +403,19 @@ Validate that the new monorepo is safer and more coherent than the legacy setup
- [x] Add root verify scripts
- [ ] Add backend contract tests
-- [ ] Add web auth and kill-switch smoke tests
-- [ ] Add mobile launch/auth/kill-switch smoke coverage
+- [x] Add web auth and kill-switch smoke tests
+- [x] Add mobile launch/auth/kill-switch smoke coverage
- [x] Add docs for local dev, CI, Docker, and fallback behaviors
- [x] Define cutover sequencing from legacy repos
- [x] Define rollback paths
-- [ ] Define release go/no-go checklist
-- [ ] Define post-cutover monitoring checks
+- [x] Define release go/no-go checklist
+- [x] Define post-cutover monitoring checks
### Exit Criteria
- [ ] New monorepo is production-ready for staged adoption
-- [ ] Rollback and cutover are documented
-- [ ] Engineers and operators can run the new repo confidently
+- [x] Rollback and cutover are documented
+- [x] Engineers and operators can run the new repo confidently
## 9. Detailed Work Breakdown
@@ -593,7 +596,7 @@ Reason:
## 16. Immediate Next Steps
-- [ ] Finish profile risk/PnL aggregate repository migration off legacy Supabase
-- [ ] Finish remaining web direct legacy data-table reads and writes behind backend APIs
-- [ ] Replace remaining transitional web auth compatibility surfaces with fully common-platform-native session handling
-- [ ] Add release smoke coverage for web auth/kill-switch and mobile auth/kill-switch flows
+- [x] Finish profile risk/PnL aggregate repository migration off legacy Supabase
+- [x] Finish remaining web direct legacy data-table reads and writes behind backend APIs
+- [x] Replace remaining transitional web auth compatibility surfaces with fully common-platform-native session handling
+- [x] Add release smoke coverage for web auth/kill-switch and mobile auth/kill-switch flows
diff --git a/package.json b/package.json
index ddc606a..9c5fa62 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"scripts": {
"build": "pnpm --filter @bytelyst/trading-backend build && pnpm --filter @bytelyst/trading-web build && pnpm --filter @bytelyst/trading-mobile typecheck",
"lint": "pnpm --filter @bytelyst/trading-backend lint && pnpm --filter @bytelyst/trading-web lint && pnpm --filter @bytelyst/trading-mobile lint",
+ "smoke:release": "sh ./scripts/smoke-release.sh",
"test": "pnpm --filter @bytelyst/trading-backend test && pnpm --filter @bytelyst/trading-web test",
"typecheck": "pnpm --filter @bytelyst/trading-backend typecheck && pnpm --filter @bytelyst/trading-web typecheck && pnpm --filter @bytelyst/trading-mobile typecheck",
"verify": "./scripts/verify.sh"
diff --git a/scripts/smoke-release.sh b/scripts/smoke-release.sh
new file mode 100644
index 0000000..c6491f8
--- /dev/null
+++ b/scripts/smoke-release.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+set -eu
+
+echo "Running release smoke checks for learning_ai_invt_trdg"
+
+pnpm --filter @bytelyst/trading-web typecheck
+pnpm --filter @bytelyst/trading-web build
+(
+ cd web
+ pnpm vitest run \
+ src/components/Login.dom.test.tsx \
+ src/components/ResetPassword.dom.test.tsx \
+ src/components/ProductAccessibilityGate.dom.test.tsx \
+ src/components/ChatControl.dom.test.tsx \
+ src/components/EntryForm.dom.test.tsx \
+ src/hooks/useWebSocket.dom.test.tsx \
+ src/components/AuthContext.dom.test.tsx
+)
+pnpm --filter @bytelyst/trading-mobile typecheck
diff --git a/web/src/components/ProductAccessibilityGate.dom.test.tsx b/web/src/components/ProductAccessibilityGate.dom.test.tsx
new file mode 100644
index 0000000..b33cddf
--- /dev/null
+++ b/web/src/components/ProductAccessibilityGate.dom.test.tsx
@@ -0,0 +1,57 @@
+// @vitest-environment jsdom
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { render, screen, waitFor } from '@testing-library/react';
+import { ProductAccessibilityGate } from './ProductAccessibilityGate';
+
+const { checkMock } = vi.hoisted(() => ({
+ checkMock: vi.fn()
+}));
+
+vi.mock('../lib/runtime', async () => {
+ const actual = await vi.importActual('../lib/runtime');
+ return {
+ ...actual,
+ tradingKillSwitchClient: {
+ check: checkMock
+ }
+ };
+});
+
+describe('ProductAccessibilityGate smoke', () => {
+ beforeEach(() => {
+ checkMock.mockReset();
+ });
+
+ it('renders children when product is available', async () => {
+ checkMock.mockResolvedValue({ disabled: false });
+
+ render(
+
+ workspace-loaded
+
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText('workspace-loaded')).toBeInTheDocument();
+ });
+ });
+
+ it('blocks workspace when control plane disables the product', async () => {
+ checkMock.mockResolvedValue({
+ disabled: true,
+ message: 'Trading access disabled by control plane'
+ });
+
+ render(
+
+ workspace-loaded
+
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText('Trading temporarily unavailable')).toBeInTheDocument();
+ expect(screen.getByText('Trading access disabled by control plane')).toBeInTheDocument();
+ });
+ expect(screen.queryByText('workspace-loaded')).not.toBeInTheDocument();
+ });
+});