First visit lands on /welcome instead of burying signup in Settings. Add a host CLI to reset passwords without a session, plus Settings change-password. Refresh as-built design docs and the Unraid operator guide.
28 KiB
Investor Flow - Technical Design (as built)
Audit date: 2026-08-18
Companion: FUNCTIONAL_DESIGN.md
Operator deploy: DEPLOY_UNRAID.md
Canonical code roots: app/src (Next frontend), app/server/src (Node backend)
1. Stack
| Layer | Actual technology | Notes vs older docs |
|---|---|---|
| Frontend | Next.js 16.2.9, React 19.2, Tailwind v4, Recharts, Zustand, Radix UI | Not Bun SPA. output: "standalone" for the frontend image. |
| Backend runtime | Node ≥22 (dev on Node 26), native TS via --experimental-strip-types |
DESIGN said Bun; runtime glue is Node + node:sqlite |
| API | tRPC v11 HTTP, path /api/trpc/* |
Frontend uses a hand-rolled fetch client in app/src/lib/trpc.ts (not @trpc/client). Next route handler proxies /api with a 200s timeout (rewrites time out ~30s and break Ornith). |
| DB | SQLite file (IFLOW_DB_PATH, default app/server/data/investor-flow.db) |
Single file multi-tenant. On Unraid this is a bind-mounted cache-disk path. |
| Market data | yahoo-finance2 v3 class API |
Quotes, candles, options, short interest (Yahoo slice) |
| SEC | EdgarAdapter + SecFetchAdapter + SecLintAdapter + SecCompanyTickersAdapter + secDataFetcher |
Filer CIK from accession prefix. Daily company_tickers.json seed. |
| Short interest | NasdaqAdapter + FinraShortInterestAdapter + FinraBulkAdapter | Three-way merge with discrepancy flags. finra-bulk not auto-scheduled. |
| Macro / COT | FredAdapter + CotAdapter | FRED key admin-side. COT from CFTC zip (confluence slot). |
| Social | XCookieAdapter (encrypted ct0/auth_token), RedditAdapter | X is queued. Reddit tRPC calls the adapter directly and is not in the boot adapter map. |
| Auth | Sessions + TOTP + OAuth (GitHub/Google) | Password hash via local crypto helpers. Production refuses to start without IFLOW_SESSION_SECRET. |
| Tests | node --test --experimental-strip-types |
Backend 903 pass / 0 fail / 1 skip; frontend 58 pass / 0 fail (2026-08-18). |
| Deploy | Two Node containers on Unraid | Gitea Actions: test, then push images to the Gitea registry. See §11. |
2. Process topology
2.1 Runtime (laptop or Unraid)
Browser
│ fetch /api/trpc/<proc> (credentials include, same-origin)
▼
Next frontend (:3000)
│ app/src/app/api/[...path]/route.ts (200s proxy)
▼
Backend (:3001)
│
├─ appRouter (tRPC)
├─ CacheRepository + AdapterQueue + vendorGate
├─ Source adapters (yfinance+options, nasdaq, finra-*, sec-*, x, fred, cot)
├─ Background loops (drain, schedules, alerts, SMTP outbox, confluence, housekeeping)
└─ SQLite
- Dev helpers:
restart-servers.sh, adminserverRestart(laptop-oriented). - Queue drain default: every 2s (
IFLOW_DRAIN_MS). - Schedule loop: every 30s.
- Realtime/per-fetch alerts: every 30s (VIX).
- Batched alerts: every 10 min.
- SMTP outbox drain: every 60s.
- Confluence tick: hourly (+ boot fill after 3 min).
- Queue housekeeping: daily (
clearDone+ prunequeue_errors).
2.2 Production host
Unraid is the intended host. Git + Gitea Actions live at unraid.local:3003/transnet/investor-flow (HTTP registry also at 10.37.0.86:3003).
git push origin main
│
▼
Gitea repo + act_runner (label ubuntu-latest)
│
├─ ci.yml / test frontend tests, backend tests, backend typecheck
└─ ci.yml / images (main only, after test) build + push
│
▼
Gitea registry
transnet/investor-flow-backend:{sha,latest}
transnet/investor-flow-frontend:{sha,latest}
│
▼
Unraid compose (backend :3001 + frontend :3000)
data volume: IFLOW_DATA_DIR (cache/single-disk, not /mnt/user FUSE)
cd.yml is manual (workflow_dispatch) - rebuild and push images without a new commit.
A git-pull + compose up --build cron (deploy/unraid/user-scripts/sync-and-up.sh) remains as a fallback when you want Unraid to build from source instead of pulling registry tags.
3. Multi-tenant data tiers (still valid)
| Tier | Content | Isolation |
|---|---|---|
| A | quotes, candles, options, filings, institution_filings, insider_tx, sector_map, macro, symbols, dealer maps, short interest, COT, FRED | Shared, no owner |
| B | threads, x_cookie_posts, reddit_posts, adapter_queue*, queue_* | Shared infrastructure |
| C | watchlists, portfolio_holdings, portfolio_option_legs, trades, strategies, alerts, theses, emotion_logs, reports, saved filters, confluence user racks, corridor watchlist | owner_id / user_id |
| D | users, sessions, admin_audit, x_credentials (singleton), smtp_config, llm_*, tracked_funds (operator-curated), fund_position_records | System / shared fund facts |
Demand set: symbol_demand refcount drives which symbols the queue refreshes. System pins (rotation universe, SPY, VIX, confluence universe) use system_pin so they are not refcount-inflated.
Stale-while-revalidate: CacheRepository returns cached rows and schedules refresh when past TTL.
3.1 Rate-limit-first data plane (ADR-0009)
All vendors (Yahoo, X, FRED, SEC, Reddit, NASDAQ, FINRA, CFTC) have short rate limits. The system is designed so stress yields stale/static UI, not stampede:
| Rule | Mechanism |
|---|---|
| Request path never stampede | Serve SQLite / kv_cache / static fallback first; no live Yahoo on tRPC (queue only) |
| One outbound owner | AdapterQueue only (background drain); UI schedules via CacheRepository.get |
| Steady throttle | Per-source min-interval (sourceRatePolicy.DEFAULT_SOURCE_MIN_INTERVAL_MS) |
| 429 cool-down | Source-wide pause 2→5→15→30→60 min; skip all jobs for that source; no schedule flood |
| Demand-bounded work | Watchlist/portfolio subscribe + ensureInDemand / pinSystemSymbol |
| TTL-aware tiers | quote-portfolio 1m, quote-priority 5m, quote-watched 10m, EOD candles 6h, meta daily, holdings weekly |
| Incremental candles | Warm symbols re-fetch ~14d lookback, not full 10y every tick |
| Poison quarantine | Delisted / not-found symbols fail permanent; not requeued; demand cleared |
| Per-kind drain budgets | Quotes cannot starve symbol meta / candles forever |
| Observability | queue.health(): vendorGate+queue_state cooldowns, pendingByKind, demand size, SPY candle lag, dataPlaneHealthy |
| Per-source admin control | pause / resume / stop (clear pending) / start |
Anti-patterns (do not reintroduce): N parallel Yahoo charts on a click; live quoteSummary without cache on every panel open; treating 429 as a 2s job retry that keeps hammering the same edge; calling subscribe on every page view (inflates refcount - use ensureInDemand); dealerMap.get calling Yahoo directly; frontend looping expiries to paint Dealer Flow; Next rewrites for /api (30s hard timeout).
3.2 Dealer Flow data plane
- Engine: pure
dealerExposureEngineonNormalizedOptionSurfaceonly (no vendor imports). - Default provider: Yahoo via
composeYFinanceWithOptions+OPTIONS_CHAIN_PROVIDER(defaultyfinance). - Paid switch later: implement SourceFetch for
tradier/polygon, register insourceRatePolicy, set env - engine unchanged. - Request path: SQLite recompute + schedule-on-miss; max 4–6 nearest expiries; 15m map TTL; daily
dealer_map_snapshotsfor velocity. - Integrity: pure
dealerMapIntegrityhard/soft checks (missing expiries/OI/greeks fail; delay does not);data_qualitykinddealer_map; replay viadealerMapReplay+scripts/dealer-map-replay.ts(as-of chain ts). - Dealer GEX sign convention: maps are stored as classic OI GEX (
classic_call_pos_put_neg: call +, put −). Request path can re-express asdealer_inventory(full GEX/VEX sign flip) viawithExposureConvention/dealerMap.get({ convention }). First-paint UI is GEX | VEX only; Method drawer has Customer book | Dealer inventory. - Han-style day script (
hanStyleLevels) is derived from the active convention and returned ondealerMap.get.
3.3 SEC / institutional data plane
- SC-first fetch seeds
sec:cusip:SYMBOL; offline curated CUSIP registry (cusipRegistry) so resolve does not depend solely on EFTS. - 13F prefers EFTS CUSIP pagination; on EFTS 403/outage falls back to reverse 13F (
reverse13fRefresh: prior holders + tracked funds + major managers viadata.sec.gov). SecFetchAdapterthrows on hard resolve failure so queue retries (no silent done).requeueUnhealthySecSymbolscaps heal requeues per tick.- Daily
sec-tickersmaterializescompany_tickers.jsonintosymbols(issuer CIK, name, exchange). Merge policy: SEC never overwrites yfinancesector/industry/peers/ticker_kindand never purges non-SEC rows (ADR-0011).
3.4 Vendor gate (hard requirement)
Process-wide vendorGate with open registration (registerVendorIntegration). Built-in families: yfinance, sec, fred, finra, nasdaq, reddit, x, llm, cftc. New vendors must register family + bind source_kind before AdapterQueue construction (throws otherwise). Prefer VendorSourceAdapter / defineVendorAdapter so fetchOne is auto-gated. HTTP via vendorFetch / secHttp; SDKs via withVendorGate. CI guard bans bare fetch( in adapters/services. See docs/VENDOR_INTEGRATIONS.md.
3.5 Confluence data plane (ADR-0012)
- Evaluators resolve candles through
CandleProvider(cache + freshest quote folded into a partial daily bar). - Hourly evaluation of today + incremental as-of replay (~3y, budgeted).
- Weekly walk-forward derivation of entry/exit zone rules on 11 GICS sector ETFs + SPY.
- Reliability weights (once a slot has ≥2 resolved fires) scale evidence inside
evaluateRack.
Slow-changing composition (ETF top holdings) uses kv_cache + etfHoldingsFallback.ts + queued yfinance:topHoldings:* refresh.
4. Backend module map
app/server/src/
index.ts HTTP server + boot: adapters, schedules, pins, loops
trpc/router.ts all routers
trpc/context.ts session, cookies, db, cache, queue injection
db/schema.sql DDL
db/*Repository.ts watchlist, portfolio, option legs, emotion logs,
alertSubscription, fund, confluence, corridor
cache/CacheRepository.ts content-addressed cache API
queue/AdapterQueue.ts schedule, pause/stop per source, retry, cool-downs
queue/sourceRatePolicy.ts 429 detect, cool-down ladders, SCHEDULE_INTERVALS
adapters/ YFinance, Options, Nasdaq, Finra*, Cot, Edgar,
SecFetch, SecCompanyTickers, SecLint, X, Reddit
analysis/ indicators, rotation, seasonality, etfHoldingsFallback,
tickerContext, dealerExposureEngine, dealerMapService,
dealerMapExplain, dealerFlowExplainNotes,
dealerStudyEngine, hanStyleLevels, volumeByPrice
options/ bsm, types, OptionsChainRouter, ConvexityGate (stub)
llm/ openaiCompatible client, userLlmEndpoint
admin/ operator functions + CLI
auth/ totp, oauth, backup codes
alerts/AlertEngine.ts
alerts/producers/ insider, 13D/13F, VIX, rotation, unlock, thesis,
portfolio risk, confluence, mirror
services/ emailAlertService, vendorGate, secHttp, secDataFetcher,
reverse13fRefresh, cusipRegistry, captureIngest,
analystRatingsService, FinraIngestService, stockFloat
confluence/ slots, library, rack, engine, seed, evaluators,
candleProvider, zones, corridor*, backtest
mirror/ captureParser, fund13fFetcher, mirrorEngine
risk/ RiskEngine, haltCircuitBreaker, optionRiskContribution
sizing/ SizingEngine, convictionUnlock, twoAxisMatrix
strategy/ BacktestEngine, PortfolioBacktestEngine
screener/ UniverseEvaluator, SectorCrosslink
derisking/ DeriskingEngine
thesis/ ThesisMonitor
macro/ FredAdapter, MacroRegime
reports/ ReportRunner
onboarding/ starter
x/ backfill
Boot adapter map (index.ts): yfinance (composed with options), nasdaq, finra-bulk, finra-si, sec-fetch, sec-sc-fetch, sec-tickers, sec-lint-holders, sec-lint-insiders, cot. X and FRED register only when credentials/keys exist.
4.1 tRPC surface (mounted)
| Router | Procedures (summary) |
|---|---|
auth |
signup, login, logout, me, changePassword, enable2fa, confirm2fa, oauthStart, oauthCallback |
onboarding |
starter, complete, updateProfile |
market |
snapshot, snapshots, tickerContext, candles, indicators, truckSales, manufacturingPmi, condition, rotation, add/remove/listCustomEtf, rotationCheckForAlert, seasonality, sectorHoldings |
dashboard |
rollup |
admin |
users + modules + enable/disable/delete, sessions, passwords, queue* (incl. per-source pause/stop), lint, data quality, dealerMapIntegrity, alertStatus, X creds/accounts/prune, FRED key, FINRA URL, pending/approve/reject, audit, serverRestart, gdprExport, smtp* |
alerts |
list, acknowledge, acknowledgeAll, clearAll, unackedCount, create/list/update/delete subscription, listTypes, toggleType |
institutional |
flow, insiderStream, ownershipHistory, buyEvents, analystRatings, shortInterest |
edgar |
filings_index, company_facts, filer_cik_meta, full_text_search, form13f_holdings, form4_tx |
watchlists |
list, listByWatchlist, listWatchlists, create, delete, rename, reorder, moveSymbol, addSymbol, removeSymbol |
portfolio |
holdings, addHolding, updateHolding, removeHolding, optionLegs, addOptionLeg, removeOptionLeg |
options |
chain, greeks |
dealerMap |
get, levels, scenario, velocity, explain |
dealerStudy |
propose, log, list, grade, gradeDue, scorecard, promoteToJournal, exportCsv |
mentorLedger |
importFromHarvest, list, confirm, discard, grade, gradeDue, scorecard |
userLlm |
status, upsertEndpoint, clear, test |
reports |
generate |
screener |
filter, strategy |
strategies |
list, create, get, listPresets, getPreset, forkPreset, suggestTickers |
backtest |
run, evaluateLatest, runPortfolio |
sectorCrosslink |
confirm |
derisking |
suggest, dividendHealth |
macro |
series, calendar, regimeClassify, commentary, regimeHistory |
thesisMonitor |
assess (by symbol), timeline (empty events) |
x |
feed, cashtag_search, timeline, accountsForSymbol |
reddit |
subreddit, search (not queued) |
emotionLogger |
add, getByTrade, delete |
sizing |
compute |
risk |
posture (no haltStatus; halt fields currently hardcoded false/null) |
trades |
list, get, save, close, stats |
theses |
list, get, create, update, delete |
symbols |
search, holders |
funds |
list, get, liveBook, adminCreate, adminDelete, adminSetEnabled, adminSync13f, adminIngestCaptures |
mirror |
diff |
confluence |
slots, racks, evaluation, backtest, scorecard, signalHistory, recentZones, saveRack, runEvaluationNow, corridorSnapshot, corridorWatchlist, corridorWatchlistAdd/Remove, corridorBacktest, corridorBacktestRun |
Removed since 2026-07-23: optionsConvexity.unlock / getPayoff.
4.2 Default schedule intervals
From sourceRatePolicy.SCHEDULE_INTERVALS:
| Kind | Interval |
|---|---|
yfinance-quote-portfolio |
1 min |
yfinance-quote-priority |
5 min |
yfinance-quote-watched |
10 min |
yfinance-eod |
6 h |
yfinance-meta |
1 d |
yfinance-holdings |
7 d |
sec-fetch |
1 d |
sec-sc-fetch |
6 h |
sec-tickers |
1 d |
sec-lint-holders / sec-lint-insiders |
7 d |
x |
1 h |
finra-si |
14 d |
fred |
1 d |
finra-bulk is deleted on every boot if present (historical 403).
5. Frontend architecture
app/src/
app/ Next App Router pages + /health + /api proxy
components/ Panels + ui kit + layout + dealer-flow + confluence
stores/ Zustand (theme, goals, active symbol/watchlist, outlook sections)
lib/trpc.ts Hand-rolled client (covers the product routers)
lib/workspace-profile.ts Density + nav visibility
lib/useFeatureAccess.ts Module access
lib/chart-theme.ts CSS-variable themed Recharts
lib/strings.ts Primary-rule sensitive copy
5.1 Routes
| Path | Role |
|---|---|
/ |
Symbol workbench (density-gated sections) |
/chart-lab |
Charts |
/institutional |
Institutional + filings |
/filings |
Filings only |
/options-dd |
Options DD |
/dealer-flow |
Dealer map + Study Desk |
/confluence |
Signal Confluence + Price Corridor |
/market-outlook |
Macro / rotation / notes |
/portfolio |
Book (holdings + option legs; guided setup via ?strategyId=) |
/risk |
Risk posture + sizing math |
/funds, /funds/[id] |
Tracked funds + mirror |
/guided-start |
Preset picker + quiz |
/plan |
Decision plan (server) |
/theses |
Thesis CRUD (server) |
/journal |
Close + reflection (server) |
/strategies |
Preset catalog + fork |
/lab |
Backtest |
/screener |
Filter + strategy screen |
/exits |
Derisking considerations |
/reports |
HTML research notes |
/daily-focus |
Goals (localStorage) |
/alerts |
Events + subscriptions |
/welcome |
First-user desk (signup, signin, pending approval panel, TOTP step, interview). Standalone layout — no LayoutShell, no SymbolHeader. |
/settings |
Account + density + user LLM (signed-in only; guest redirects to /welcome?mode=signin) |
/more |
Mobile secondary destinations |
/mobile, /monitor |
Redirect → /portfolio |
/admin, /admin/users, /admin/queue, /admin/audit-logs, /admin/x-accounts, /admin/smtp |
Admin |
/health |
Frontend liveness { ok, service: "investor-flow-web" } |
5.2 Client coverage
lib/trpc.ts now exposes the product surface listed in §4.1 (including confluence, funds, mirror, symbols, trades, theses, sizing, risk, strategies, backtest, screener, reports, derisking, dealerMap/Study, mentorLedger, userLlm).
Still server-only / thin on the client: admin.gdprExport, macro.series / calendar / regimeHistory (commentary is aliased as market.commentary), thesisMonitor, reddit (mounted, no product screen).
5.3 Shell
- Desktop (
lg+): sidebar + watchlist + main. - Tablet (
md–lg): compact watchlist bar + main. - Phone (
<md): header + scroll main + bottom tabs. CSS-first so there is no desktop-layout flash. - Nested
<main data-app-scroll>is the only scroller (100dvh). Nav clicks force that pane to the top.
6. Caching & adapters
| Source kind | Role | Typical freshness |
|---|---|---|
| yfinance (tiered) | quote, candles, sector, options, short-interest Yahoo slice | 1–10 min quotes / EOD candles |
| nasdaq | days-to-cover + 24mo short-interest history | on demand + queue |
| finra-si | FINRA short interest | ~14 d |
| finra-bulk | bulk ingest (optional; not auto-scheduled) | operator |
| sec / sec-fetch / sec-sc-fetch | filings, 13F, Form 4, SC 13D/G | daily / 6 h SC |
| sec-tickers | issuer CIK seed | daily |
| sec-lint-* | gap detection / backfill | weekly / on demand |
| x | timelines / cashtags | hourly + demand |
| subreddit/search | on demand, not queued | |
| fred | series + commentary inputs | daily |
| cot | CFTC TFF positioning | confluence consumers |
| llm | summaries / dealer explain | sparse; 180s client timeout |
AdapterQueue features (built): global and per-source pause/resume/stop/start, per-job errors with stacks, retry job/source, clear done, schedules, startup in_flight→pending recovery, demand hygiene, daily housekeeping.
7. AuthZ model
| Gate | Behavior |
|---|---|
publicProcedure |
No session required (many market/edgar/watchlist reads still public - intentional for local demo; tighten later if multi-tenant hardens) |
protectedProcedure |
Session + users.status === 'active' |
adminProcedure |
is_admin flag |
| Module access | users.modules JSON. Default ["research","settings"]. Admin flag injects admin. Frontend FeatureGate + sidebar filter. |
| Density | users.density (focused / standard / full) filters nav items and overview sections. Independent of modules. |
Watchlist list currently falls back to userId ?? 'anonymous' - convenient for local dev, weak isolation if exposed beyond localhost.
Production: trpc/context.ts throws if IFLOW_SESSION_SECRET is unset and NODE_ENV !== 'development'.
8. Testing
| Suite | Command | Audit result (2026-08-18) |
|---|---|---|
| Backend | cd app/server && npm test |
903 pass / 0 fail / 1 skip |
| Backend types | cd app/server && npm run typecheck |
Run in CI |
| Frontend unit | cd app && node --test --experimental-strip-types "src/**/*.test.ts" |
58 pass / 0 fail |
| CI | .github/workflows/ci.yml |
Frontend tests + backend tests + backend typecheck, then image push on main |
Node may warn that frontend test files are typeless in package.json (Next app is not "type": "module"). Do not add "type": "module" to the Next package; it would change how Next loads config.
9. Schema notes
Single strategies table (the 2026-07 duplicate-definition debt is gone). New / notable tables since that audit:
symbols(issuer CIK + search index)portfolio_option_legsdealer_map_snapshots,dealer_study_setups,mentor_sources,mentor_callsuser_llm_endpointsfinra_short_interest,finra_short_interest_biweekly,finra_config,stock_floattracked_funds,fund_position_recordsconfluence_racks,confluence_evaluations,confluence_signal_history,confluence_zone_rulescorridor_snapshots,corridor_watchlist,corridor_backtestproducer_run_log,notification_outbox,smtp_confighalt_state(exists; not currently folded intorisk.posture)
Remaining debt:
- Parallel filter tables:
screener_filtersvssaved_filters(no product loop). options_unlocktable leftover after the unlock ladder was removed.thesisMonitordoes not yet populate events from filings/insiders.risk.posturedoes not read/writehalt_state.
10. LLM / provenance (as built vs design)
| Designed | As built |
|---|---|
| Full LLMGateway with classifyPayload gate | Tables llm_providers, llm_dispatch_audit, llm_summaries; no complete gateway module under src/ |
Ornith default is_local=true |
Env LLM_PROVIDER_URL / ORNITH_LLM_URL on the backend container (LAN IP, not localhost) |
| Per-user endpoint | Live - userLlm.* + Settings UI; encrypted key; used for dealerMap.explain |
| Sensitive thesis data never leaves host | Thesis content is not flowing through LLM product paths |
ADR-0006/0008 still govern intent; the full gateway is incomplete. Dealer Flow Layer-0 notes are in-app (dealerFlowExplainNotes.ts). Optional offline harvest/distill scripts write that file and a personal Obsidian copy - the app never reads Obsidian at runtime.
11. Deployment and CI/CD
This is the as-built operator path. Day-to-day commands live in DEPLOY_UNRAID.md.
11.1 Images
| Piece | Path | Runtime |
|---|---|---|
| Backend image | app/server/Dockerfile |
Node 22, tini, node --experimental-strip-types src/index.ts, data at /app/data |
| Frontend image | app/Dockerfile |
Next standalone, IFLOW_BACKEND_URL=http://backend:3001 |
| Build-from-git compose | docker-compose.yml |
Used on a laptop or an Unraid checkout |
| Pull-prebuilt compose | deploy/unraid/compose.pull.yml |
After CI has published tags |
| Gitea runner | deploy/unraid/act-runner-compose.yml |
gitea/act_runner:0.2.13, Docker socket, label ubuntu-latest |
| Cron fallback | deploy/unraid/user-scripts/sync-and-up.sh |
git fetch + ff-only merge + compose up --build when HEAD moved |
Health:
- Backend
GET /health→{ ok, queue: queue.health() } - Frontend
GET /health→{ ok, service: "investor-flow-web" }
Compose depends_on: backend.service_healthy so the web container does not start until tRPC is up.
11.2 Gitea Actions
ci.yml (automatic)
| Trigger | Jobs |
|---|---|
push / PR to main, workflow_dispatch |
test |
push to main after test succeeds |
images |
test: Node 22, npm ci in app/ and app/server/, frontend unit tests, backend npm test, backend npm run typecheck.
images: Docker Buildx against the insecure HTTP registry, login with REGISTRY_TOKEN (or github.token), push:
${REGISTRY}/transnet/investor-flow-backend:${{ github.sha }}and:latest${REGISTRY}/transnet/investor-flow-frontend:${{ github.sha }}and:latest- registry cache tags
:buildcache
Default REGISTRY is 10.37.0.86:3003 (override with a Gitea Actions variable).
cd.yml (manual)
Same image build/push without the test gate. Use when you need to republish images from the current main SHA.
11.3 How a change reaches Unraid
- Commit and
git push origin mainto Gitea. - act_runner picks up the workflow (label
ubuntu-latest). - Tests run. On failure, images are not published.
- On success, new
:latestand:${sha}tags land in the Gitea registry. - Unraid must pull and recreate containers. Publishing is not the same as rolling the running stack.
Two supported run modes:
A. Pull prebuilt (preferred after CI is green)
# .env image names point at the registry (see deploy/unraid/.env.example)
docker compose -f deploy/unraid/compose.pull.yml pull
docker compose -f deploy/unraid/compose.pull.yml up -d
B. Build on Unraid from the git checkout
cd /mnt/cache/appdata/investor-flow/repo
git pull --ff-only
docker compose --env-file /mnt/cache/appdata/investor-flow/.env up -d --build
Mode B is what sync-and-up.sh automates. Mode A is what the registry tags are for. Do not mix them on the same project name without knowing which image tags .env pins.
11.4 Secrets and data
Required in production .env:
IFLOW_SESSION_SECRET-openssl rand -hex 32SEC_OPERATOR_EMAIL- real address (SEC + Yahoo user-agent)IFLOW_DATA_DIR- cache or single-disk path, not/mnt/user(FUSE can corrupt SQLite WAL)
Recommended:
IFLOW_CRYPTO_KEY- required to decrypt existing X/FRED rows if you copy a Mac DBLLM_PROVIDER_URL/ORNITH_LLM_URL- Ornith LAN URL (notlocalhostinside the container)YF_OPERATOR_EMAIL,FRED_API_KEY, OAuth client ids (optional)
SQLite lives at /app/data/investor-flow.db inside the backend container. Persist that directory. Stop the backend (or use the SQLite backup API) before copying investor-flow.db*.
11.5 Local dev (unchanged)
cd app/server && npm run dev # :3001
cd app && npm run dev # :3000
Or docker compose --env-file .env up -d --build from the repo root.
12. Documentation map
| Doc | Role |
|---|---|
docs/FUNCTIONAL_DESIGN.md |
What users can do / pending |
docs/TECH_DESIGN.md |
This file - how it is built |
docs/DEPLOY_UNRAID.md |
Operator process: Unraid + Gitea CI/CD |
docs/VENDOR_INTEGRATIONS.md |
How to add a vendor without bypassing the gate |
CONTEXT.md |
Ubiquitous language (glossary only) |
docs/adr/* |
Durable decisions (0001–0012) |
Obsidian investor-flow.md |
Session orientation wiki (status here wins) |
Deprecated as status sources: root HANDOFF.md (2026-06-30 orchestrator snapshot), slice DECOMPOSITION checkbox state.