# Implementation Plan

- [x] 1. Write bug condition exploration test
  - **Property 1: Bug Condition** - Paper Mode Approval Bypass
  - **CRITICAL**: This test MUST FAIL on unfixed code — failure confirms the bug exists
  - **DO NOT attempt to fix the test or the code when it fails**
  - **NOTE**: This test encodes the expected behavior — it will validate the fix when it passes after implementation
  - **GOAL**: Surface counterexamples that demonstrate the bug exists
  - **Scoped PBT Approach**: Scope the property to paper mode with `auto_approve_paper=False` — the bug is that even when `auto_approve_paper` is explicitly set to `False` in a `PortfolioRiskConfig`, the system works correctly at the function level, but the config JSON stored in `risk_configs` never contains `operator_approval` settings because no UI/endpoint sets them. The exploration test should:
    1. Verify `requires_approval(config, PAPER)` returns `True` when `auto_approve_paper=False` (this actually passes — the function itself is correct)
    2. Verify `PortfolioRiskConfig.from_db_json({})` produces `auto_approve_paper=True` (demonstrates the root cause — empty JSON always defaults to auto-approve)
    3. Verify there is no dedicated endpoint to update `operator_approval` settings in the risk config (the existing `PUT /api/admin/trading/config` replaces the entire config JSON, and no UI calls it with `operator_approval` fields)
  - Test file: `tests/test_pbt_operator_approval.py`
  - Use Hypothesis with `@settings(max_examples=100)` per project conventions
  - Generate random `PortfolioRiskConfig` instances with `auto_approve_paper=False` and `trading_mode=PAPER`
  - Assert `requires_approval()` returns `True` for all such configs (function-level correctness)
  - Also test that `PortfolioRiskConfig.from_db_json({})` always produces `auto_approve_paper=True` (demonstrates the config-level bug)
  - Run test on UNFIXED code
  - **EXPECTED OUTCOME**: The function-level property passes (function is correct), but the `from_db_json({})` test demonstrates the root cause — empty config always auto-approves
  - Document counterexamples found to understand root cause
  - Mark task complete when test is written, run, and results are documented
  - _Requirements: 1.1, 1.2, 1.5, 2.1_

- [x] 2. Write preservation property tests (BEFORE implementing fix)
  - **Property 2: Preservation** - Non-Paper-Toggle Approval Behavior
  - **IMPORTANT**: Follow observation-first methodology
  - Observe behavior on UNFIXED code for non-buggy inputs:
    - Observe: `requires_approval(config, LIVE)` returns `True` when `require_approval_for_live=True`
    - Observe: `requires_approval(config, LIVE)` returns `False` when `require_approval_for_live=False`
    - Observe: `requires_approval(config, DISABLED)` returns `False` for all configs
    - Observe: `requires_approval(config, PAPER)` returns `False` when `auto_approve_paper=True`
  - Write property-based tests capturing observed behavior patterns:
    - For all `(trading_mode, auto_approve_paper, require_approval_for_live)` combinations where `trading_mode != PAPER` OR `auto_approve_paper == True`, verify `requires_approval()` returns the same result as the original function
    - Property: for all LIVE mode configs, result equals `require_approval_for_live`
    - Property: for all DISABLED mode configs, result is always `False`
    - Property: for all PAPER mode configs with `auto_approve_paper=True`, result is always `False`
  - Test file: `tests/test_pbt_operator_approval.py` (same file, separate test class)
  - Use Hypothesis with `@settings(max_examples=100)` per project conventions
  - Verify tests PASS on UNFIXED code
  - **EXPECTED OUTCOME**: Tests PASS (confirms baseline behavior to preserve)
  - Mark task complete when tests are written, run, and passing on unfixed code
  - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6_

- [x] 3. Implement backend API endpoints

  - [x] 3.1 Add `GET /api/admin/trading/approval-config` endpoint
    - Read the active `risk_configs` row and extract `operator_approval` settings from the `config` JSONB column
    - Parse with `PortfolioRiskConfig.from_db_json()` to get defaults when fields are missing
    - Return `{ auto_approve_paper: bool, require_approval_for_live: bool, approval_timeout_minutes: int }`
    - File: `services/api/app.py`
    - _Bug_Condition: isBugCondition(input) where risk_config_json has no "operator_approval" key_
    - _Expected_Behavior: Endpoint returns current operator_approval settings (with defaults filled in)_
    - _Preservation: Existing GET /api/admin/trading/config endpoint unchanged_
    - _Requirements: 1.5, 2.5_

  - [x] 3.2 Add `PUT /api/admin/trading/approval-config` endpoint
    - Read current `risk_configs.config` JSONB, merge `operator_approval` sub-object, write back
    - Accept `{ auto_approve_paper: bool }` (and optionally `require_approval_for_live`, `approval_timeout_minutes`)
    - Use JSON merge (not full replace) to preserve other config fields
    - File: `services/api/app.py`
    - _Bug_Condition: No UI/endpoint to set auto_approve_paper=False in risk_config_json_
    - _Expected_Behavior: PUT updates operator_approval in config JSONB, requires_approval() reflects the change_
    - _Preservation: Other config fields (position_limits, sector_exposure, etc.) unchanged by merge_
    - _Requirements: 1.5, 2.1, 2.5_

  - [x] 3.3 Add `POST /api/admin/trading/lockouts` endpoint
    - Accept `{ ticker: string, reason: string, duration_minutes: int, lockout_type?: string }`
    - Compute `expires_at = NOW() + duration_minutes`
    - Default `lockout_type` to `"manual"`
    - Insert into `symbol_lockouts` table
    - Return the created lockout row
    - File: `services/api/app.py`
    - _Bug_Condition: No POST endpoint exists for symbol_lockouts_
    - _Expected_Behavior: Manual lockout created and visible in GET /api/admin/trading/lockouts_
    - _Preservation: System-generated lockouts (news-shock, cooldown) unaffected_
    - _Requirements: 1.3, 1.4, 2.3, 2.4, 3.6_

  - [x] 3.4 Add `DELETE /api/admin/trading/lockouts/{lockout_id}` endpoint
    - Delete the lockout row by ID
    - Return 404 if not found
    - File: `services/api/app.py`
    - _Bug_Condition: No DELETE endpoint exists for symbol_lockouts_
    - _Expected_Behavior: Lockout removed early, no longer appears in GET results_
    - _Preservation: Other lockouts unaffected_
    - _Requirements: 1.4, 2.4_

- [x] 4. Implement frontend hooks

  - [x] 4.1 Add `useApprovalConfig()` hook
    - Fetches `GET /api/admin/trading/approval-config` via `useGet` with query key `['approval-config']`
    - Type the response as `{ auto_approve_paper: boolean, require_approval_for_live: boolean, approval_timeout_minutes: number }`
    - File: `frontend/src/api/hooks.ts`
    - _Requirements: 2.5_

  - [x] 4.2 Add `useUpdateApprovalConfig()` mutation hook
    - Calls `PUT /api/admin/trading/approval-config` via `apiPut`
    - Invalidates `['approval-config']` and `['trading-config']` query keys on success
    - File: `frontend/src/api/hooks.ts`
    - _Requirements: 2.1, 2.5_

  - [x] 4.3 Add `useCreateLockout()` mutation hook
    - Calls `POST /api/admin/trading/lockouts` via `apiPost`
    - Accepts `{ ticker: string, reason: string, duration_minutes: number }`
    - Invalidates `['lockouts']` query key on success
    - File: `frontend/src/api/hooks.ts`
    - _Requirements: 2.3_

  - [x] 4.4 Add `useDeleteLockout()` mutation hook
    - Calls `DELETE /api/admin/trading/lockouts/{id}` via `apiDelete`
    - Invalidates `['lockouts']` query key on success
    - File: `frontend/src/api/hooks.ts`
    - _Requirements: 2.4_

- [x] 5. Implement frontend UI controls

  - [x] 5.1 Add Approval Toggle Card to Trading page
    - New `Card` between the existing signal layer toggles and Pending Approvals section
    - Toggle switch for `auto_approve_paper` using `useApprovalConfig()` and `useUpdateApprovalConfig()`
    - Confirmation dialog before toggling (since enabling approval requirements changes order flow)
    - Show current state label ("Auto-approve paper orders" / "Require approval for paper orders")
    - Use same toggle switch pattern as Macro Signal Layer and Competitive Signal Layer cards
    - File: `frontend/src/pages/Trading.tsx`
    - _Requirements: 1.5, 2.1, 2.5_

  - [x] 5.2 Add Lockout Creation Form to Active Lockouts card
    - Inline form at the top of the Active Lockouts card with fields:
      - Ticker (text input, uppercase)
      - Reason (text input)
      - Duration in minutes (number input, default 60)
    - Submit button calls `useCreateLockout()`
    - Clear form on successful submission
    - File: `frontend/src/pages/Trading.tsx`
    - _Requirements: 1.3, 1.4, 2.3, 2.4_

  - [x] 5.3 Add Lockout Delete Button to each lockout row
    - "Remove" button on each lockout entry in the Active Lockouts list
    - Calls `useDeleteLockout()` with the lockout ID
    - File: `frontend/src/pages/Trading.tsx`
    - _Requirements: 1.4, 2.4_

  - [x] 5.4 Add MSW handlers for new endpoints
    - Add mock handlers in `frontend/src/test/mocks/handlers.ts` for:
      - `GET /api/admin/trading/approval-config`
      - `PUT /api/admin/trading/approval-config`
      - `POST /api/admin/trading/lockouts`
      - `DELETE /api/admin/trading/lockouts/:id`
    - File: `frontend/src/test/mocks/handlers.ts`
    - _Requirements: 2.1, 2.3, 2.4, 2.5_

- [x] 6. Verify bug condition exploration test now passes

  - [x] 6.1 Re-run bug condition exploration test
    - **Property 1: Expected Behavior** - Paper Mode Approval Toggle
    - **IMPORTANT**: Re-run the SAME test from task 1 — do NOT write a new test
    - The test from task 1 encodes the expected behavior
    - With the new `PUT /api/admin/trading/approval-config` endpoint in place, the operator can now set `auto_approve_paper=False` in the risk config JSON
    - The `requires_approval()` function-level property should still pass
    - The config-level test should now demonstrate that the endpoint correctly updates the config
    - Run: `.venv/bin/python -m pytest tests/test_pbt_operator_approval.py -x --tb=short -q`
    - **EXPECTED OUTCOME**: Test PASSES (confirms bug is fixed — operator can now control approval settings)
    - _Requirements: 2.1, 2.2, 2.5_

  - [x] 6.2 Verify preservation tests still pass
    - **Property 2: Preservation** - Non-Paper-Toggle Approval Behavior
    - **IMPORTANT**: Re-run the SAME tests from task 2 — do NOT write new tests
    - Run: `.venv/bin/python -m pytest tests/test_pbt_operator_approval.py -x --tb=short -q`
    - **EXPECTED OUTCOME**: Tests PASS (confirms no regressions)
    - Confirm all preservation properties still hold after the fix
    - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6_

- [x] 7. Run frontend tests
  - Run: `cd frontend && npx vitest --run`
  - Verify Trading page renders correctly with new controls
  - Verify no regressions in existing frontend tests
  - _Requirements: 2.1, 2.3, 2.4, 2.5_

- [x] 8. Checkpoint — Ensure all tests pass
  - Run full Python test suite: `.venv/bin/python -m pytest tests/test_pbt_operator_approval.py -x --tb=short -q`
  - Run full frontend test suite: `cd frontend && npx vitest --run`
  - Ensure all property-based tests pass (both bug condition and preservation)
  - Ensure all frontend tests pass (including new MSW handlers)
  - Ask the user if questions arise
