Review: Watchdog — auto-buy when market crosses 85% threshold

review-1689-2026-07-03 Review

review needs-refinement

Verdict: NEEDS_REFINEMENT

Template Completeness

  • [x] Type -- "Feature (bot consumer -- Solid Queue job)"
  • [x] Lineage -- Present, references Sprint 4 dependencies
  • [x] Repo -- ldraney/prediction-assistant
  • [x] User Story -- Present (references AI-Powered Portfolio Builder)
  • [x] Context -- Detailed, explains dual-trigger architecture
  • [x] File Targets -- 4 files + tests listed
  • [ ] Feature Flag -- Name only (late_game_lock_enabled). Missing required fields: Type, Default, Visibility, Removal. Also, no feature flag infrastructure exists in the codebase.
  • [x] Acceptance Criteria -- 17 items (excessive, see Decomposition)
  • [x] Test Expectations -- Present with unit, integration, and edge cases
  • [x] Constraints -- Present
  • [x] Checklist -- Present
  • [x] Related -- Present

Traceability

  • [x] story:watchdog-trading label -- Watchdog Trading story
  • [x] story note verified -- watchdog-trading found in project-prediction-assistant user-stories section
  • [ ] User story body mismatch -- Issue body references "AI-Powered Portfolio Builder" (portfolio-builder) but label is story:watchdog-trading. The label is correct (Late-Game Lock targets 85%+ win rate), but the issue body should reference the matching story. [BODY]
  • [x] arch:rails label -- Rails component
  • [ ] arch note MISSING -- No arch-rails note found in pal-e-docs. [SCOPE] Create architecture note arch-rails for Rails component.
  • [x] Forgejo issue -- https://forgejo.tail5b443a.ts.net/ldraney/prediction-assistant/issues/4, open. Redirects from old kalshi-assistant URL.

File Targets

  • [ ] app/jobs/late_game_lock_job.rb -- ISSUE: Issue says this should be on the late_game_lock queue, but existing architecture has PriceUpdateJob and TimeUpdateJob as stubs on the events queue waiting for Sprint 4 consumers (their comments say "Consumers (Sprint 4): Late-Game Lock, Edge Learner"). The Late-Game Lock should be called FROM these event jobs, not subscribe to its own queue. Queue architecture needs reconciliation. [BODY]
  • [ ] app/services/late_game_lock_evaluator.rb -- New file, reasonable scope for dual-trigger logic.
  • [ ] app/services/late_game_lock_exit_checker.rb -- ISSUE: PositionTracker (app/services/position_tracker.rb) already has exit_candidates and exit_signal methods that evaluate stop_loss and take_profit thresholds. Creating a separate LateGameLockExitChecker would duplicate this logic. Issue should clarify whether to extend PositionTracker or replace its exit logic. [BODY]
  • [ ] config/late_game_lock_sport_mappings.yml -- ISSUE: LateGameLockConfig model already validates time_remaining_threshold against a hardcoded list: %w[last_5_pct last_10_pct last_15_pct last_quarter]. But the issue specifies sport-specific mappings (NFL 4th quarter <5min, MLB 9th inning, etc.) which are a different concept than generic time percentages. The YAML config and the model validation need reconciliation. [BODY]

Repo Placement

OK. Issue specifies ldraney/prediction-assistant, which matches the current repo name. The board item Forgejo URL still uses the old kalshi-assistant name but redirects correctly.

Dependencies

  • #2 MarketScanner + event bus -- board item 1687, in validation column
  • #3 Strategy STI schema -- board item 1688, in validation column
  • OrderService -- board item 1710, in validation column
  • PositionTracker -- board item 1711, in validation column
All 4 dependencies are in the validation column (not yet done). If validation surfaces issues in any dependency, this ticket's implementation may need to change. The dependencies themselves exist in the codebase and are functional: MarketScannerJob emits time_update and price_update events, OrderService handles trade execution, PositionTracker handles position monitoring including exit signals, and LateGameLockConfig STI model is already implemented.

Acceptance Criteria

17 acceptance criteria. All are individually testable. However:
  • Missing AC for late_game_lock_enabled feature flag behavior (what happens when disabled?)
  • Missing AC for activity logging (OrderService logs trades, but bot-level event processing logs are not specified)
  • Missing AC for error handling when dependencies (OrderService, PositionTracker) are unavailable
  • AC #1 says "on the late_game_lock queue" which conflicts with the stub-based event dispatch pattern in PriceUpdateJob/TimeUpdateJob
  • AC #11-14 (exit checker) overlap with PositionTracker's existing exit_candidates/exit_signal methods

Blast Radius

  • PriceUpdateJob (app/jobs/price_update_job.rb) -- Currently a stub. Implementing Late-Game Lock requires adding consumer dispatch logic here. Edge Learner (Sprint 6) will also consume from this job. Changes must be additive.
  • TimeUpdateJob (app/jobs/time_update_job.rb) -- Currently a stub. Same concern as PriceUpdateJob.
  • PositionTracker (app/services/position_tracker.rb) -- Already has exit_candidates and exit_signal logic. Creating a separate LateGameLockExitChecker risks duplication or inconsistency.
  • LateGameLockConfig (app/models/late_game_lock_config.rb) -- Already validates time_remaining_threshold against hardcoded values. YAML sport mappings would need to align with or replace this validation.
  • Other bots (PregameStacker, BulkSweep) are not affected -- they use different event paths.

Decomposition Assessment

NEEDS DECOMPOSITION -- 17 ACs across 4+ new files + test files + modifications to 2 existing stub jobs. Estimated agent work exceeds 5 minutes significantly.
Recommended decomposition into 3 sub-tickets:
  • Event dispatch wiring + dual-trigger evaluator (ACs 1-6, 9): Wire PriceUpdateJob and TimeUpdateJob stubs to dispatch to Late-Game Lock. Create LateGameLockEvaluator with sport-specific time mapping and dual-trigger logic. ~5 pts.
  • Entry logic + OrderService integration (ACs 7-8, 10, 16-17): Flat bet sizing, config reading, OrderService calls, idempotency. ~3 pts.
  • Exit management + PositionTracker integration (ACs 11-15): Reconcile with existing PositionTracker exit logic. Stop-loss, take-profit, auto_sell_vs_hold. ~3 pts.

Recommendation

  • [LABEL] Update board item title from "Watchdog -- auto-buy when market crosses 85% threshold" to "Late-Game Lock bot -- dual-trigger consumer (price + time)" -- the issue explicitly says "This is NOT the watchdog/scanner."
  • [BODY] Fix queue architecture: Replace "late_game_lock queue" with dispatch from PriceUpdateJob/TimeUpdateJob stubs. Both stubs already have comments saying "Consumers (Sprint 4): Late-Game Lock, Edge Learner".
  • [BODY] Reconcile exit checker with PositionTracker: Either extend PositionTracker.exit_candidates (which already handles stop_loss/take_profit) or document why a separate LateGameLockExitChecker is needed.
  • [BODY] Reconcile sport mappings YAML with LateGameLockConfig validation: The model validates time_remaining_threshold against generic presets (last_5_pct, last_10_pct, etc.) but the issue specifies sport-specific mappings (NFL 4th quarter <5min, MLB 9th inning, etc.). These are different concepts.
  • [BODY] Complete Feature Flag section with Type, Default, Visibility, and Removal fields per template-issue-feature. Note: no feature flag infrastructure exists in the codebase yet.
  • [BODY] Fix user story reference to match label: body says "AI-Powered Portfolio Builder" but label is story:watchdog-trading ("Watchdog Trading").
  • [SCOPE] Create architecture note arch-rails for the Rails component.
  • [DECOMPOSE] 17 ACs across 4+ new files + 2 stub modifications exceeds the 5-minute rule. Route to skill-decompose-ticket with the 3-way split recommended above.