fix (ornith-35): watchlistRepository double-encoding bug — single JSON.stringify, 13/13 tests pass

This commit is contained in:
Investor Flow Build
2026-06-30 17:54:01 -04:00
parent 97607e0bd4
commit 1007ab4ed5
62 changed files with 11617 additions and 34 deletions
View File
+10
View File
@@ -0,0 +1,10 @@
[AgentLoader] Loaded agent: broadcaster (type: BroadcasterAgent)
[AgentLoader] Loaded agent: heavy-lifter (type: HeavyLifterAgent)
[AgentLoader] Loaded agent: manager (type: ManagerAgent)
[AgentLoader] Loaded agent: miner (type: MinerAgent)
[pi-vault-mind] Agent engine started (4 agent(s) loaded)
[pi-vault-mind] Port 11435 in use; retrying on ephemeral port.
[pi-vault-mind] HTTP server listening on http://127.0.0.1:63167
[pi-vault-mind] Auto-starting watcher for 1 vault(s)
[pi-vault-mind] Watching vault "default" at /Users/laptran/.obsidian
[pi-vault-mind] Watcher started. Monitoring 1 vault(s).
+10
View File
@@ -0,0 +1,10 @@
[AgentLoader] Loaded agent: broadcaster (type: BroadcasterAgent)
[AgentLoader] Loaded agent: heavy-lifter (type: HeavyLifterAgent)
[AgentLoader] Loaded agent: manager (type: ManagerAgent)
[AgentLoader] Loaded agent: miner (type: MinerAgent)
[pi-vault-mind] Agent engine started (4 agent(s) loaded)
[pi-vault-mind] Port 11435 in use; retrying on ephemeral port.
[pi-vault-mind] HTTP server listening on http://127.0.0.1:63035
[pi-vault-mind] Auto-starting watcher for 1 vault(s)
[pi-vault-mind] Watching vault "default" at /Users/laptran/.obsidian
[pi-vault-mind] Watcher started. Monitoring 1 vault(s).
+13
View File
@@ -0,0 +1,13 @@
[AgentLoader] Loaded agent: broadcaster (type: BroadcasterAgent)
[AgentLoader] Loaded agent: heavy-lifter (type: HeavyLifterAgent)
[AgentLoader] Loaded agent: manager (type: ManagerAgent)
[AgentLoader] Loaded agent: miner (type: MinerAgent)
[pi-vault-mind] Agent engine started (4 agent(s) loaded)
[pi-vault-mind] Port 11435 in use; retrying on ephemeral port.
[pi-vault-mind] HTTP server listening on http://127.0.0.1:62879
[pi-vault-mind] Auto-starting watcher for 1 vault(s)
[pi-vault-mind] Watching vault "default" at /Users/laptran/.obsidian
[pi-vault-mind] Watcher started. Monitoring 1 vault(s).
[pi-vault-mind] Stopped watching vault "default".
[pi-vault-mind] Watcher stopped.
[pi-vault-mind] HTTP server stopped.
+13
View File
@@ -0,0 +1,13 @@
[AgentLoader] Loaded agent: broadcaster (type: BroadcasterAgent)
[AgentLoader] Loaded agent: heavy-lifter (type: HeavyLifterAgent)
[AgentLoader] Loaded agent: manager (type: ManagerAgent)
[AgentLoader] Loaded agent: miner (type: MinerAgent)
[pi-vault-mind] Agent engine started (4 agent(s) loaded)
[pi-vault-mind] Port 11435 in use; retrying on ephemeral port.
[pi-vault-mind] HTTP server listening on http://127.0.0.1:62861
[pi-vault-mind] Auto-starting watcher for 1 vault(s)
[pi-vault-mind] Watching vault "default" at /Users/laptran/.obsidian
[pi-vault-mind] Watcher started. Monitoring 1 vault(s).
[pi-vault-mind] Stopped watching vault "default".
[pi-vault-mind] Watcher stopped.
[pi-vault-mind] HTTP server stopped.
+13
View File
@@ -0,0 +1,13 @@
[AgentLoader] Loaded agent: broadcaster (type: BroadcasterAgent)
[AgentLoader] Loaded agent: heavy-lifter (type: HeavyLifterAgent)
[AgentLoader] Loaded agent: manager (type: ManagerAgent)
[AgentLoader] Loaded agent: miner (type: MinerAgent)
[pi-vault-mind] Agent engine started (4 agent(s) loaded)
[pi-vault-mind] Port 11435 in use; retrying on ephemeral port.
[pi-vault-mind] HTTP server listening on http://127.0.0.1:62508
[pi-vault-mind] Auto-starting watcher for 1 vault(s)
[pi-vault-mind] Watching vault "default" at /Users/laptran/.obsidian
[pi-vault-mind] Watcher started. Monitoring 1 vault(s).
[pi-vault-mind] Stopped watching vault "default".
[pi-vault-mind] Watcher stopped.
[pi-vault-mind] HTTP server stopped.
+1
View File
@@ -0,0 +1 @@
70294
+70
View File
@@ -0,0 +1,70 @@
You are a code implementer. Build ONE focused module. Write the code now.
## HARD RULES
- Do NOT call any automaton_* tool. Do NOT read PARENT_SPEC.md or DECOMPOSITION.md.
- Start writing files within your first 2 tool calls. Read only what's listed, then build.
- You are building micro-slice 1a: the SQLite database foundation for the Investor Flow backend.
## CONTEXT (read these, then build)
The project is "Investor Flow" — a Bun + SQLite backend serving a Next.js SPA. You are creating the database layer. The schema follows DESIGN.md Section 1 (Tier A shared market cache + Tier D system).
Read this ONE file for the exact schema:
`grep -n "Tier D\|Tier A\|CREATE TABLE\|users\|sessions\|symbol_meta\|price_quotes\|price_candles\|symbol_demand" /Users/laptran/Documents/investor-flow/.automaton/tasks/investor-flow-platform-design/DESIGN.md | head -40`
If the grep doesn't give enough, read lines 17-175 of that DESIGN.md.
## BUILD EXACTLY THESE 4 FILES
### 1. `app/server/package.json`
```json
{
"name": "investor-flow-server",
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "bun run src/index.ts",
"test": "bun test"
},
"dependencies": {
"better-sqlite3": "^11.0.0",
"@trpc/server": "^11.0.0",
"argon2": "^0.40.0",
"yahoo-finance2": "^2.0.0",
"zod": "^3.23.0"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.0",
"bun-types": "^1.1.0",
"typescript": "^5.0.0"
}
}
```
### 2. `app/server/tsconfig.json`
Standard Bun TypeScript config (target ESNext, module ESNext, moduleResolution bundler, strict true, types ["bun-types"], outDir ./dist).
### 3. `app/server/src/db/schema.sql`
SQLite DDL for these tables (match DESIGN.md Section 1 Tier A + Tier D):
- `users` (id TEXT PK, email TEXT UNIQUE NOT NULL, pw_hash TEXT NOT NULL, complexity TEXT DEFAULT 'beginner', risk_tolerance TEXT, created_at TEXT NOT NULL)
- `sessions` (id TEXT PK, user_id TEXT NOT NULL REFERENCES users(id), expires_at TEXT NOT NULL, created_at TEXT NOT NULL)
- `symbol_meta` (symbol TEXT PK, name TEXT, sector TEXT, industry TEXT, exchange TEXT, ticker_kind TEXT NOT NULL DEFAULT 'equity', updated_at TEXT)
- `price_quotes` (symbol TEXT NOT NULL, price REAL, bid REAL, ask REAL, change REAL, change_pct REAL, ts_observed TEXT NOT NULL, fetched_at TEXT NOT NULL, PRIMARY KEY(symbol, ts_observed))
- `price_candles` (symbol TEXT NOT NULL, tf TEXT NOT NULL, ts TEXT NOT NULL, o REAL, h REAL, l REAL, c REAL, v REAL, adj_close REAL, PRIMARY KEY(symbol, tf, ts))
- `symbol_demand` (symbol TEXT PK, refcount INTEGER NOT NULL DEFAULT 0, protected INTEGER NOT NULL DEFAULT 0)
Add indexes: on price_quotes(symbol), price_candles(symbol, ts), sessions(user_id).
### 4. `app/server/src/db/client.ts`
A `DbClient` class (or factory function) that:
- Opens a SQLite database (better-sqlite3). Accepts a path arg; default `./investor-flow.db`. For tests, accept `:memory:`.
- On init, reads and executes `schema.sql` (use `import.meta.dir` to resolve the path relative to the file).
- Exposes the raw `Database` instance via a `db` property for other modules to use prepared statements.
- Exports a `createDb(path?)` factory.
TypeScript throughout. Use `better-sqlite3` synchronous API.
## THEN
Run `cd /Users/laptran/Documents/investor-flow/app/server && bun install` to install deps.
Then run a quick smoke test: `bun -e "import {createDb} from './src/db/client'; const db = createDb(':memory:'); console.log(db.db.prepare('SELECT name FROM sqlite_master WHERE type=\"table\"').all());"` to confirm tables exist.
Print the result. Then STOP — do not build anything else.
## PRIMARY RULE
No user-facing strings in this micro-slice (it's the DB layer). Just clean typed code.
+195
View File
@@ -0,0 +1,195 @@
# Investor Flow — DECOMPOSITION (vertical tracer-bullet slices)
Per the `to-issues` skill: each slice is a thin vertical path through ALL layers (schema, adapter, cache repo, API, UI, tests) — narrow but COMPLETE end-to-end, demoable on its own, fresh-context per slice. Slice #1 is the approved tracer bullet. Slices are listed in dependency order (blockers first).
The shared **tRPC integration seam** (Section 7.2) and **Primary-Rule lint** (ADR-0007) are established in slice #1 and extended by every slice. Cache-only testability (fakes at the seam) is required; nothing reaches the network in tests.
---
## Slice 1 — tracer-bullet-1: signup + cached NVDA overview [APPROVED]
**What to build:** User can sign up (email+password, no 2FA yet) + log in, and see a cached NVDA overview panel hydrated by one yfinance `quote` + `price_history`+`info/sector` adapter behind CacheRepository + AdapterQueue + a tRPC `market.snapshot` endpoint. Establishes: SQLite schema (Tier A price_quotes/price_candles/symbol_meta; Tier D users/sessions), one SourceAdapter (yfinance), CacheRepository staleness (quote 60s, sector weekly), single-page shell with active-symbol signal (NVDA hardcoded first), the tRPC seam, FakeLLM/FakeSourceAdapter test infra, and the Primary-Rule stub (landing page carries ADR-0007 footer).
**Acceptance criteria:**
- [ ] Signup → login → session cookie; users/sessions rows Tier D.
- [ ] `market.snapshot(symbol=NVDA)` returns from cache; UI renders price + sparkline + one-line sector.
- [ ] Stale-while-revalidate: UI renders cached immediately; background AdapterQueue job refreshes.
- [ ] AdapterQueue dedupe collapses two concurrent NVDA snapshot calls into one yfinance fetch.
- [ ] Playwright + Playwright-contract: shell single-page, active-symbol rehydration works, ADR-0007 footer present, no imperative-trade-verb in any string.
- [ ] Primary-Rule lint test runs and passes (stoplist).
**Blocked by:** None.
## Slice 2 — auth-2fa-and-social-oauth
**What to build:** Add TOTP 2FA + social OAuth (GitHub/Google) to slice 1; session includes `complexity` default beginner; refresh-token flow. Adds `two_factor` table + `oauth_identities`.
**Acceptance criteria:**
- [ ] 2FA enrollment + login works; backup codes generated.
- [ ] Social OAuth sign-in / link existing account.
- [ ] Session carries complexity; UI reflects beginner defaults.
**Blocked by:** Slice 1.
## Slice 3 — onboarding-wizard
**What to build:** First-login wizard: complexity pick, risk tolerance, drawdown-tolerance plain-English Q (gentle-halt explained), starter watchlist (IREN, CIFR, ASST, SLNH, BKKT, NUAI, NVDA, BTC, SATA) with one-line reasons + ticker-kind, optional portfolio CSV/manual. Writes Tier C watchlist + portfolio. Explicit ADR-0007 statement during onboarding.
**Acceptance criteria:**
- [ ] Wizard completes → user has complexity, risk tolerance, first watchlist (default set with ticker-kind).
- [ ] Crypto symbols flagged "limited research module"; SEC-derived rows gated.
- [ ] Onboarding explicitly states "educational tool, not financial advice".
**Blocked by:** Slice 2.
## Slice 4 — yfinance-backfill-permanent-ohlcv
**What to build:** On first symbol-track (Slice 3 starter watchlist) trigger `history(period="max")`; write permanent daily OHLCV with `adj_close` + `price_adjustments` for splits/dividends; this enables backtests. Extend yfinance SourceAdapter kinds; staleness = daily locked end-of-day.
**Acceptance criteria:**
- [ ] First track of NVDA backfills years of daily candles; rerun is a no-op (stale only checks for NEW).
- [ ] `adj_close` correct; split/dividend in `price_adjustments`; raw toggle available.
- [ ] Storage only Tier A shared; refcount in `symbol_demand` protects while user tracks.
**Blocked by:** Slice 3.
## Slice 5 — chart-lab-panel (M2)
**What to build:** M2 panel — multi-timeframe candles + volume + indicator toggles (EMA 9/21/50/200, RSI, relative volume) reading permanent OHLCV from cache. Per-indicator one-line lesson tooltip (P7 G2). No trade signals; every chart string passes Primary-Rule lint.
**Acceptance criteria:**
- [ ] 1D/1W read from cache; intraday opt-in.
- [ ] EMA200 tooltip gloss present; no buy/sell arrows; relative-volume "above/below typical" not "bullish/bearish".
- [ ] P7 + Primary-Rule lint pass.
**Blocked by:** Slice 4.
## Slice 6 — sec-edgar-adapter-and-filings-panel (M6)
**What to build:** SEC EDGAR SourceAdapter (filings_index, full_text_search, primary_doc, company_facts, 13f_holdings, form4_tx, 13d/13g, filer_cik_meta SIC). M6 filings panel with summaries + materiality 8-K heuristics. ETAG/If-Modified-Since; immutable cache forever. LLM `filing_summary` via FakeLLM fixture in tests.
**Acceptance criteria:**
- [ ] Fetch a 10-K + 8-K with UA + 8 req/sec; 304 re-check is a no-op.
- [ ] Filter by form type/date; "Summarize" renders cached summary; materiality tags present.
- [ ] Filer CIK/SIC fetched once, cached; class-inference ready for next slice.
**Blocked by:** Slice 1 (schema seam).
## Slice 7 — institution-flow-engine (M4 view) + insider-stream (M5)
**What to build:** InstitutionFlowEngine deep module behind M4 per-symbol view (5 holder classes via CIK/SIC; 13F diff; buy-zone estimate stamped "estimated"). Form 4 adapter paths for M5 Insider Activity Stream (Informed Buy/Sell/Routine via 10b5-1). Plotted on quarterly price strip with citation chips.
**Acceptance criteria:**
- [ ] Each owner class has one-line plain-English meaning; buy-zone estimate always stamped "estimated".
- [ ] Form 4 informed events distinct from routine; Routine hidden by default for beginners.
- [ ] Class via CIK SIC metadata, not name heuristics.
**Blocked by:** Slice 6.
## Slice 8 — institutional-dashboard-rollup (M4 dashboard)
**What to build:** M4 dashboard rollup across watchlist + portfolio; compact grid (Symbol / Net Active Conviction Δ / insider recency / class-roll flag / alert); sortable + filterable; one-paragraph LLM `dashboard_rollup` summary (always on, ADR-0005 voice, ADR-0007 footer).
**Acceptance criteria:**
- [ ] Rollup reads across owned watchlists+portfolio; grid sortable; LLM rollup summary present + cited.
- [ ] Summary passes Primary-Rule lint (no "follow this flow").
**Blocked by:** Slice 7.
## Slice 9 — sector-rotation (M7)
**What to build:** RotationDetector deep module: RS-breadth thrust + cross-sectional rank; incipient signal detection daily; γ two-stage resolution (price ~4wk + institutional at quarter-end); rotation phase labels + confidence; retention of signal history with real/false labeling.
**Acceptance criteria:**
- [ ] Heatmap (RS-ratio + rel-volume) + phase labels; signal-history table shows resolution timestamps; false-alarm rate visible per signal type.
- [ ] γ resolution labels real vs false; educational framing "capital appears to be moving".
**Blocked by:** Slice 4 (price history), Slice 7 (institutional).
## Slice 10 — watchlist-portfolio-shell-panels (M9 + M10 minimal)
**What to build:** M9 multiple watchlists (add/import/drag-reorder) with compact mini-overviews. M10 minimal portfolio (holdings, P/L) + journal entry collects Two-Axis Model: fundamental thesis WHY + invalidation criteria + technical entry WHEN + Confluence Rack (SlotLibrary default 4-slot beginner Rack).ApiKey: no TradePlan accepted without stop + risk% + thesis + invalidation criteria (server-side block). A_STAR/Strategy authoring disabled (not unlocked yet).
**Acceptance criteria:**
- [ ] Watchlists CRUD + ticker-kind gating; portfolio CRUD.
- [ ] Journal TradePlan requires stop + risk + thesis-invalidation; server rejects otherwise with helpful error.
- [ ] Confluence Rack default 4-slot beginner; redundancy-awareness tags duplicate signals.
**Blocked by:** Slice 3 (onboarding), Slice 4.
## Slice 11 — sizing-engine-and-conviction-unlock (deep module behind M10/M16)
**What to build:** SizingEngine 4-layer sizing (stop / ATR / conviction-tier / correlation-cluster + macro gate); Conviction Tier unlock gate (20B→A, 10A→A_STAR) reading per-tier win-rate from journal; Two-Axis matrix enforced pre-create in UI AND server-side (High conviction × Bad entry = WAIT; override-with-written-reason). `sizing_explain` LLM feature. SizingEngine pure/cache-deterministic.
**Acceptance criteria:**
- [ ] Sizing computed from plan + account + portfolio + regime; A_STAR blocked until unlock met.
- [ ] Override-with-written-reason recorded; matrix enforced both sides.
- [ ] LLM "if your plan is X, the math implies ~Y shares" framing (Primary-Rule).
**Blocked by:** Slice 10.
## Slice 12 — strategy-lab-and-backtest (M16)
**What to build:** Author + parameterize Strategy bundles {Regime gate, Setup, Risk Policy, Exit Strategy}; BacktestEngine.run/evaluateLatest against permanent OHLCV. Symbol-locked at base unless Conviction Tier unlocks Strategy authoring (slice 11). Exit reasons: TA-stop/thesis-broken/target-hit. Sample-size caveat in UI.
**Acceptance criteria:**
- [ ] Strategy author gated by unlock; backtest runs cache-only; exit-reasons labeled.
- [ ] Equity curve annotated with reasons; no "155% return!" hype highlight (P7).
- [ ] "this Strategy would have behaved" framing (Primary-Rule).
**Blocked by:** Slice 11.
## Slice 13 — universe-evaluator + filter-screener (M15a) + strategy-screener (M15b)
**What to build:** UniverseEvaluator deep module (one engine, two predicates: compiled filter expression OR Strategy entry conditions). M15a filter screener over tiered universe (watchlist → broader by sector). M15b strategy screener delegates to BacktestEngine.evaluateLatest. One-tap "open in workbench". Saved filter sets per-user (Tier C).
**Acceptance criteria:**
- [ ] Filter screener runs instantly on watchlist universe; broader-scan gated with cost/time note.
- [ ] Strategy screener outputs conviction-strength + conditions-fired; one-tap loads M1.
- [ ] "discovery for learning" framing + ADR-0007 footer.
**Blocked by:** Slice 12.
## Slice 14 — sector-confirmation-via-screener cross-link
**What to build:** Wire screener + rotation: "show symbols in the rotated-into sector matching my Strategy". Educational framing only — NOT a ready-made buy list.
**Blocked by:** Slice 9, 13.
## Slice 15 — options-adapters-and-options-dd-panel (M3)
**What to build:** yfinance options_chain kind; M3 read-only Options Due Diligence panel — IV rank/percentile, greeks, OI walls, max-pain; defined-risk stamp; undefined-risk shaded with "advanced only". Feeds M17 in slice 19; never directional options.
**Acceptance criteria:**
- [ ] Options data cached 15min; IV-rank bar teaches the mechanic (not hype gauge).
- [ ] Default no directional options; no "buy this call".
**Blocked by:** Slice 1 (adapter queue).
## Slice 16 — x-cookie-adapter-and-sentiment-feed (M8)
**What to build:** X cookie SourceAdapter (cashtag_search + trusted-account timeline) at 1 req/3s; cookie-expiry → FAILED + source-degraded UI. Reddit PRAW adapter. M8 sentiment feed with per-user trusted accounts + post_summary LLM. Attribution preserved; "crowds aren't edge" caveat.
**Acceptance criteria:**
- [ ] X + Reddit threads cached 7d rolling; trusted accounts per-user.
- [ ] Cookie expiry alerts operator; UI shows cached-only.
- [ ] "Crowd sentiment is not edge" caveat visible; no "buy because Twitter is bullish".
**Blocked by:** Slice 1.
## Slice 17 — alerts-v1 (AlertEngine hybrid)
**What to build:** AlertEngine hybrid (event-driven for cheap Form4/13DA/quote-stale; poll for thesis-monitor). Alert types: informed_buy/sell, new_13da, rotation_incipient, regime_shift, conviction_unlock, thesis_broken/weakening, cluster_breach, drawdown_halt, asymmetry_warning. SSE push. Dedupe per filing.
**Acceptance criteria:**
- [ ] Informed-buy fires once per filing (not every tick).
- [ ] Alert text "something changed" not "action needed" (Primary-Rule).
**Blocked by:** Slice 7, 11, 9, 16.
## Slice 18 — risk-engine-and-risk-posture (M20 + halt circuit breaker)
**What to build:** RiskEngine aggregator → RiskPosture + recommendedActions (reworded considerations per ADR-0007). Gentle halt circuit breaker: MaxDrawdownTolerance breach → `halt_new_entries` 24h + `consider_reducing_position` consideration; existing positions continue; HaltedError on `journal.trade.create` during cooldown. M20 posture surface; prominence on M19 (S20 read-only mobile from same API).
**Acceptance criteria:**
- [ ] Gentle halt blocks new entries 24h; existing continue; consideration reworded (ADR-0007).
- [ ] Asymmetry < 1 → warning; cluster > cap → consider_rebalancing_cluster.
- [ ] Every recommended-action has "trade-off to think through" frame + ADR-0007 footer.
**Blocked by:** Slice 11.
## Slice 19 — options-convexity-sleeve (M17)
**What to build:** M17 5-state unlock (Off → Covered Income → Cash-Secured Entry → Insurance Sleeve → LEAPS Conviction), defined-risk-only; naked永远 blocked; IV-Regime Gate; requires core position or articulated thesis; default OFF. Payoff diagrams teach the convex mechanic (P7 G1/G4). Reads M3 (slice 15).
**Acceptance criteria:**
- [ ] 5-state unlock with demonstrated-understanding step before each elevation.
- [ ] Max-loss/breakeven/convex-shape labeled; no P&L celebration.
- [ ] "insurance / cheaper entry / defined leverage" frame only; ADR-0007 footer.
**Blocked by:** Slice 15, 18.
## Slice 20 — macro-module (M18)
**What to build:** FRED adapter + economic-calendar adapter (Ethercalc fixed safely); M18a calendar, M18b regime classifier, M18c Portfolio-Impact Commentary (LLM Druckenmiller lens, 2-horizon: short-term reaction risk with sample-size + disclaimer; long-term structural), M18d regime history. Never a macro-trade recommendation (Alfred caution).
**Acceptance criteria:**
- [ ] Regime history lane teaches regime-shift mechanic; commentary samples disclosed.
- [ ] No macro-trade recommendations (Primary-Rule).
**Blocked by:** Slice 18 (portfolio link + regime).
## Slice 21 — thesis-monitoring-l1 (timeline + cover alerts wired)
**What to build:** L1 thesis monitor via local-only LLM (ADR-0006) cross-refs stated invalidation criteria against events (filing/insider/sentiment) → intact/weakening/broken; wiring into AlertEngine (thesis_broken/weakening). Feeds "consider exiting if thesis broken" — never silent hold (RiskEngine rule).
**Blocked by:** Slice 17, 18.
## Slice 22 — reports-research-note (M11 HTML v1)
**What to build:** ReportRunner HTML research-note v1 (inline SVG charts, P7, Analyst Voice, ADR-0007 footer). Scopes: symbol/watchlist/portfolio/rotation/sizing_year/risk_posture (cache-only reconstruction). Markdown + CSV/JSON deferred.
**Acceptance criteria:**
- [ ] HTML report self-contained, opens in browser; browser print → PDF works.
- [ ] recommendedActions rendered as considerations+questions; ADR-0007 footer present.
- [ ] Cross-owner download → NotOwnerError; deterministic given cache.
**Blocked by:** Slice 18.
## Slice 23 — derisking-strategy-library (behind M10)
**What to build:** Derisking Library 6 (scale-out at targets / stop-trail-up EMA21-50 / thesis-based partial / option-protected collar / regime-cut / correlation-driven). `derisk_suggestion` LLM (Alfred framing — winners have flexibility; losers only cut, never average down). Options-protected hold depends on slice 19.
**Blocked by:** Slice 19, 21.
## Slice 24 — mobile-companion (M19)
**What to build:** Thin responsive Next route (not RN in v1) reading same backend — alerts, P/L glance, condensed symbol story, thesis monitoring L1/L2/L3 in priority order. Read-mostly; limited authoring (add watchlist, ack alert, save post). No backtests/screens/strategy authoring; no P&L celebration animations.
**Acceptance criteria:**
- [ ] Shares all backend work (refcounted shared cache — second client, not second product).
- [ ] ADR-0007 footer on every card; no trade-act buttons.
**Blocked by:** Slice 17, 18, 22.
## Slice 25 — admin-tooling (M13)
**What to build:** Operator CLI/hidden route: list users, reset pw, GDPR export, adapter queue health, rate-limit backoff reset, ownership labels on exports.
**Blocked by:** Slice 1.
## Slice 26 — docker-compose-deployment
**What to build:** Docker Compose target: Bun backend + SQLite volume + SPA build + secrets file (X cookies, LLM provider URL). Operator self-hostable.
**Acceptance criteria:**
- [ ] `docker compose up` boots the stack; secrets file mounted; SQLite volume persists.
- [ ] ADM ADR-0007 footer present on deployed pages.
**Blocked by:** Slice 22 (most features) — deployable earlier with subset.
+48
View File
@@ -0,0 +1,48 @@
You are a CODE IMPLEMENTER for Slice 1 of "Investor Flow". Write code. Do not explore the task framework.
## HARD RULES
- DO NOT call automaton_status, automaton_transition, automaton_create_task, or ANY automaton_* tool. The orchestrator manages tasks. You only write code.
- DO NOT read PARENT_SPEC.md or DECOMPOSITION.md. Ignore the automaton task folders entirely.
- The task is already in "implement" phase — edits are allowed. Just build.
- Start writing code within your first 2 tool calls. Limit reading to the 3 files listed below, then build.
- Thinking: be concise. Plan the file list, then create files. Do not over-analyze the framework.
## READ EXACTLY THESE 3 FILES, THEN BUILD
1. `.automaton/tasks/tracer-bullet-1-signup-cached-nvda-overview/SPEC.md` (your scope)
2. `CONTEXT.md` (repo root — just the "Primary Rule" + "Ticker" sections)
3. `.automaton/tasks/investor-flow-platform-design/DESIGN.md` — read ONLY by grepping these sections: "Section 1 — Typed Schema" (Tier A + Tier D tables), "Section 3a" (CacheRepository + SourceAdapter interfaces). Use `grep -n` to find line numbers then read those ranges. Do NOT read the whole file.
## BUILD THIS EXACT FILE PLAN (Bun backend + Next panel), in order
### Backend: `app/server/` (new dir; `app/server/` already exists, empty)
1. `app/server/package.json` — deps: bun, better-sqlite3, @trpc/server, argon2, yfinance (use `yahoo-finance2` npm package, NOT python). scripts: `dev: bun run src/index.ts`, `test: bun test`.
2. `app/server/tsconfig.json`
3. `app/server/src/db/schema.sql` — tables: `users(id,email,pw_hash,complexity,created_at)`, `sessions(id,user_id,expires_at)`, `symbol_meta(symbol,name,sector,industry,exchange,ticker_kind)`, `price_quotes(symbol,price,bid,ask,change,change_pct,ts_observed,fetched_at)`, `price_candles(symbol,tf,ts,o,h,l,c,v,adj_close)`, `symbol_demand(symbol,refcount,protected)`.
4. `app/server/src/db/client.ts` — better-sqlite3 wrapper, runs schema.sql on init.
5. `app/server/src/cache/CacheRepository.ts` — interface `{get(key), set(key,val,ttl,provenance), stale(key)}` + SQLite impl. Staleness: quote 60s, sector weekly, candles daily-locked.
6. `app/server/src/adapters/SourceAdapter.ts` — interface `{fetch(symbol, kind, opts): Promise<CacheRecord[]>}` (the deep-module interface from DESIGN §3a).
7. `app/server/src/adapters/YFinanceAdapter.ts` — implements SourceAdapter using `yahoo-finance2`. Kinds: `quote`, `price_history` (daily, last 30 days for sparkline), `info/sector`.
8. `app/server/src/queue/AdapterQueue.ts` — token-bucket (1 req/s, burst 2), dedupe by `source|kind|symbol|paramsHash`, exponential backoff on 429 (2,4,8,16,60s, 5 tries→FAILED).
9. `app/server/src/trpc/router.ts` — procedures: `auth.signup({email,password})`, `auth.login({email,password})→session`, `market.snapshot({symbol})→{quote,symbolMeta,recentCandles}` (reads cache, enqueues refresh if stale).
10. `app/server/src/trpc/context.ts` — session guard.
11. `app/server/src/index.ts` — Bun.serve on :3001, mounts tRPC.
12. `app/server/src/__tests__/CacheRepository.test.ts` — in-memory SQLite, staleness asserts.
13. `app/server/src/__tests__/AdapterQueue.test.ts` — FakeSourceAdapter, dedupe collapses 2 calls→1, 429 backoff.
14. `app/server/src/__tests__/router.test.ts` — signup/login/market.snapshot integration with FakeSourceAdapter.
15. `app/server/src/__tests__/primary-rule-lint.test.ts` — grep curated UI strings for stoplist ["buy","sell","you should","add to your","rotate into","action needed"] (allow "buy-zone estimate"); FAIL on imperative trade directive.
### Frontend: reuse existing `app/` Next project
16. `app/src/lib/trpc.ts` — tRPC client to :3001.
17. `app/src/stores/active-symbol-store.ts` — Zustand store, default symbol "NVDA".
18. `app/src/components/OverviewPanel.tsx` — M1: name + sector + complexity badge (beginner) + price + day-change sparkline (Recharts, already installed) + one-line "why this matters" placeholder + ADR-0007 footer.
19. `app/src/app/page.tsx` — rewrite to render OverviewPanel bound to active-symbol store + a minimal login/signup form (calls auth.signup/login). Single-page, no extra routes.
20. `app/src/app/globals.css` — keep existing; ensure footer style.
## PRIMARY RULE (ADR-0007) — the lint test MUST pass
No UI string says "buy/sell/hold this". Footer text (in OverviewPanel): "Educational analysis, not investment advice. Verify the underlying data; you are responsible for your own decisions."
## WHEN DONE
Run `cd app/server && bun test`. If all green, run `cd app && npx tsc --noEmit` for the frontend. Print a summary: files created, test results, which acceptance criteria met. Then STOP.
## SCOPE GUARDRAILS — DO NOT BUILD
No 2FA/OAuth, no onboarding, no full max-history backfill (only last 30 days for sparkline), no other panels, no LLM calls, no Docker.
+44
View File
@@ -0,0 +1,44 @@
You are implementing **Slice 1 (tracer bullet)** of the Investor Flow project. You are an implementer agent operating with read/bash/edit/write tools. The orchestrator will review your diff afterward.
## READ THESE FIRST (in this order)
1. `.automaton/tasks/tracer-bullet-1-signup-cached-nvda-overview/SPEC.md` — YOUR EXACT SCOPE. Build only what it lists.
2. `CONTEXT.md` (repo root) — domain glossary + Primary Rule (Education, not investment advice).
3. `docs/adr/0001-local-first-multi-tenant.md`, `docs/adr/0004-shared-cache-vs-per-user-fetch.md`, `docs/adr/0007-education-not-investment-advice.md` — the load-bearing ADRs for this slice.
4. `.automaton/tasks/investor-flow-platform-design/DESIGN.md` — read **Section 1 (Typed Schema, Tier A + Tier D only)**, **Section 2 (API Surface, tRPC + auth seam + market.snapshot)**, **Section 3a (CacheRepository + SourceAdapter interfaces — match these EXACTLY)**, **Section 7 (test-seam map, FakeSourceAdapter, Primary-Rule lint)**.
Do NOT read all of DESIGN.md — only the sections above. It is 1379 lines; the rest is for later slices.
## WHAT TO BUILD (from slice SPEC — do not over-build)
A Bun + SQLite backend + a single-page Next.js panel where a user signs up (email+password, argon2/bcrypt, NO 2FA), logs in, and sees a cached NVDA overview hydrated by one yfinance adapter behind CacheRepository + AdapterQueue, served via tRPC.
Concretely, establish the skeleton that every later slice extends:
- SQLite schema: `users`, `sessions`, `symbol_meta`, `price_quotes`, `price_candles` (daily, permanent, with `adj_close`), `symbol_demand` (refcount). Match DESIGN.md §1 Tier A + Tier D.
- yfinance SourceAdapter: kinds `quote`, `price_history` (daily), `info/sector`. Use the `yfinance` python package via a thin subprocess/HTTP bridge OR a JS yfinance client — pick the simpler one that works. No options/holders/fundamentals.
- CacheRepository: `get/set/stale` with staleness (quote 60s market-hours / 15m after-hours; price_history daily locked EOD; sector weekly). Stale-while-revalidate (return cached immediately, enqueue background refresh).
- AdapterQueue: token-bucket 1 req/sec sustained burst 2/sec; dedupe same `source|kind|symbol|paramsHash`; exponential backoff on 429 (2,4,8,16,60s, 5 attempts → FAILED).
- tRPC: `auth.signup`, `auth.login`, `market.snapshot(symbol)`.
- SPA shell (reuse existing `app/` Next project, do NOT create a new one): single-page, active-symbol Zustand signal (NVDA hardcoded), M1 Symbol Overview panel (name + sector + complexity badge default beginner + price + day-change sparkline + one-line "why this matters" placeholder + ADR-0007 footer).
- Test infra: FakeSourceAdapter, in-memory SQLite fixture, tRPC integration test, Playwright contract test.
- Primary-Rule lint test: grep curated UI strings for stoplist ["buy","sell","you should","add to your","rotate into","action needed"] outside approved noun phrases ("buy-zone estimate" allowed). FAIL on imperative trade directive.
## SCOPE GUARDRAILS — DO NOT BUILD
2FA/OAuth (slice 2) · onboarding wizard (slice 3) · full `period="max"` backfill (slice 4 — only fetch latest quote + recent daily candles for the sparkline here) · any other panel M2–M20 · LLM Gateway/ornith (M1 "why this matters" is a placeholder STRING, not an LLM call) · Docker Compose (slice 26).
## ENGINEERING DISCIPLINE (from installed skills — follow these)
- `tdd`: vertical slices, red-green-refactor. One test → one impl → repeat. Do NOT write all tests then all impl.
- `codebase-design`: CacheRepository and SourceAdapter are DEEP MODULES — small interface (match DESIGN.md §3a exactly), large implementation hidden behind it. The interface IS the test surface; tests cross the same seam as callers.
- Tests use FakeSourceAdapter + in-memory SQLite. NO network calls in tests.
## PRIMARY RULE (ADR-0007) — non-negotiable
No user-facing string says "buy/sell/hold this." ADR-0007 footer on the page: *"Educational analysis, not investment advice. Verify the underlying data; you are responsible for your own decisions."* Primary-Rule lint test must pass.
## HOW TO WORK
1. Read the files above.
2. Check the existing `app/` structure (Next 16, React 19, Tailwind v4, Zustand already present). Reuse it.
3. Scaffold the Bun backend in a new `app/server/` directory (or `backend/` at repo root — pick one and be consistent). Install deps with bun.
4. Implement TDD: write the schema + a failing test, make it pass; write CacheRepository interface + failing test, pass; etc.
5. Run the test suite. Make it green. The Primary-Rule lint test must pass.
6. When acceptance criteria in the slice SPEC are all green, STOP. Do not start slice 2.
## OUTPUT
Write the code to disk using your edit/write tools. When done, print a concise summary: files created/modified, test command + result, and which acceptance criteria are met. The orchestrator (me) will review the diff.