Skip to content

About

Temple Stuart

Resources

Stars

225 stars

Watchers

7 watching

Forks

Latest commit

 

History

4,698 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Temple Stuart

Twenty-five business tools on one data pipe that ends in one Ledger and one Calendar.

Source-available (BSL 1.1) · built and operated in production by its founder as User #1 · as of 2026-09-02: 116 Prisma models, 287 API route files, 121 feeds from 20 providers (counted August 24, 2026)

The system

A small business runs on about twenty-five tools — invoicing, payroll, banking, travel, trading, tax, projects — and none of them knows what the others did. Temple Stuart replaces that with one data pipe. By design: every provider answer lands once, word for word, fingerprinted; every feed is labeled by its kind and lands in the table for that kind; what happened to you is kept apart from what you did; every tool runs the same loop and stores its records in one master table; observed money is matched to authored documents; rules write the debits and credits; four answers are math on those lines; and the same rows show as a Ledger and a Calendar. Any number walks back to the provider's words. Search & book travel — free today, no account needed.

This is the blueprint

The landing deck at templestuart.com teaches the ideal pipe, step by step, with build-time proofs. This README carries the deck's shapes — the arrival row, the kinds, the loop, the document, the match key, the rule book, the lenses, the two windows — verbatim from the code that renders them. The GAP LEDGER below is the distance between the blueprint and today, taken from the deck's own honest-state lines. The rebuild closes the gaps, and a step moves to alive only with a dated census.

The pipe

Step Headline
01 / IDENTIFY THE PROBLEM, THE TOOLS, THE FAMILIES — AND SORT THEM IN Twenty-five tools. None of them knows what the others did.
02 / LOOK AT THE TOOLS AND PICK THE PROVIDERS BEHIND THEM Every job has more than one company. You pick yours.
03 / IMPORT THE DATA AND SEE HOW IT ARRIVES Store what arrived. Then decide what it means.
04 / LABEL EVERY FEED BY ITS KIND One rule per feed. Written down.
05 / CREATE ONE TABLE PER KIND AND MAP THE DATA IN The kind picks the table.
06 / SEPARATE WHAT HAPPENED TO YOU FROM WHAT YOU DID Some things happen to you. Some things you make happen.
07 / RUN THE LOOP Every tool runs the same four beats. Discover. Decide. Commit. Record.
08 / STORE EVERYTHING YOU DO IN ONE MASTER TABLE One table holds everything you do.
09 / MATCH WHAT HAPPENED TO WHAT YOU DID The deposit meets the invoice. The fill meets the order.
10 / LET THE RULES WRITE THE LINES Nobody sends them. Rules write them.
11 / TURN THE LINES INTO ANSWERS Every answer is math on the lines.
12 / OPEN THE TWO WINDOWS One Ledger. One Calendar. All twenty-five.
13 / WATCH ONE DOLLAR RUN THE WHOLE MACHINE One $100.00 sale runs the machine. Then every other door opens.
14 / PROVE EVERY NUMBER Click any number. Walk it back.

templestuart.com teaches the full pipe with build-time proofs: every drawing derives from these consts, and the layout laws throw at build, so a false slide is a failed build.

The data model

Every shape below is extracted from the consts the deck renders from — src/components/landing/Landing.tsx and the leaves it imports (src/lib/problemSheet.ts for the sheet, src/lib/providers.ts for the provider menu and the rule book, src/lib/answers.ts for the four lenses and their inputs).

The arrival row (step 3)

One row per provider answer, eleven fields:

Band Field Gloss
WHERE IT CAME FROM provider which company sent it
WHERE IT CAME FROM connection because you might have two or more banks
WHAT IT IS resource their own word
WHAT IT IS their id so we tie the row back to the provider and never import the same thing twice
WHAT IT IS our id two providers could accidentally use the same id
WHAT THEY ACTUALLY SENT payload saved word for word — THE REASON THIS TABLE EXISTS
WHAT THEY ACTUALLY SENT fingerprint a code made from the payload — proves we never changed it
WHEN AND HOW FAR asked when we asked
WHEN AND HOW FAR arrived when it arrived
WHEN AND HOW FAR read when we read it
WHEN AND HOW FAR status pending, done, or failed

The arrivals store (step 3, built)

The arrival row above as tables — prisma/migrations/20260903000000_arrivals/migration.sql and the provider_responses / arrivals models in prisma/schema.prisma, column for column (asserted at build). Two tables: the wire (one row per HTTP answer, exact bytes, sha256) and the deck's row (one per provider object). The arrival_provider enum is src/lib/providers.ts' code set — 27 providers, every one the deck names — and arrival_status is the deck's pending / done / failed. As of September 7, 2026 it holds 9,092 Plaid transactions, 712 investment transactions and 247 securities — every answer word for word, fingerprinted, status done; rows synced before September 3, 2026 carry no arrival.

provider_responses — the wire:

Column Type Rule
id text PRIMARY KEY
provider arrival_provider NOT NULL
resource text NOT NULL
user_id text NULL REFERENCES users(id)
guest_ref text NULL
http_status integer NOT NULL
body bytea NOT NULL
body_sha256 bytea NOT NULL
asked timestamptz NOT NULL
arrived timestamptz NOT NULL
redactions text[] NOT NULL DEFAULT '{}' — prisma/migrations/20260907220000_provider_responses_redactions/migration.sql: every path blanked in body; '{}' = the exact wire bytes
CHECK / KEY CHECK (octet_length(body_sha256) = 32)
CHECK / KEY CHECK (octet_length(body) <= 1048576)
CHECK / KEY CHECK (user_id IS NOT NULL OR guest_ref IS NOT NULL)

arrivals — the deck's row:

Column Type Rule
id text PRIMARY KEY
provider arrival_provider NOT NULL
connection text NULL
resource text NOT NULL
their_id text NOT NULL
their_id_kind their_id_kind NOT NULL
payload jsonb NOT NULL
fingerprint bytea NOT NULL
redactions text[] NOT NULL DEFAULT '{}'
asked timestamptz NOT NULL
arrived timestamptz NULL
read timestamptz NULL
status arrival_status NOT NULL DEFAULT 'pending'
response_id text NULL REFERENCES provider_responses(id)
user_id text NULL REFERENCES users(id)
guest_ref text NULL
kind arrival_kind NOT NULL — prisma/migrations/20260907180000_arrival_kind/migration.sql: the rule book's kind, applied on landing and to every earlier row
CHECK / KEY CHECK (octet_length(fingerprint) = 32)
CHECK / KEY CHECK (pg_column_size(payload) <= 1048576)
CHECK / KEY CHECK (user_id IS NOT NULL OR guest_ref IS NOT NULL)
CHECK / KEY UNIQUE (provider, their_id, fingerprint)

Promise 1 is a database law, not a convention: the arrivals_promise_1 BEFORE UPDATE trigger raises if provider, connection, resource, their_id, their_id_kind, payload, fingerprint, redactions, asked, arrived, response_id, user_id, guest_ref or kind would change — only read and status may move, each once (read from NULL, status from pending). There is no updated_at column and no DELETE policy: keep forever is the default.

The write rule (PR-2 — one Plaid writer, src/app/api/transactions/sync-complete/route.ts, through src/lib/arrivals/land.ts):

  1. Every HTTP answer lands first in provider_responses — exact bytes, except declared redactions of secrets — then the canonical redacted bytes — with the sha256 of the bytes as stored and every blanked path on redactions (the Plaid SDK runs through an axios instance that keeps the body as bytes — src/lib/plaid/wire.ts; the Stripe webhook blanks client_secret — src/lib/arrivals/stripeWebhook.ts).
  2. Every object in the answer lands as one arrivals row: payload as sent, fingerprint = sha256 of its RFC 8785 canonical bytes (the canonicalize package), their_id = the provider's own id (their_id_kind provider) or one we composed and labeled as such (composed — the LiteAPI cancellation, whose answer carries no id of its own; src/lib/arrivals/liteapiBooking.ts), redactions [] — INSERT … ON CONFLICT (provider, their_id, fingerprint) DO NOTHING — the same thing is the same provider, id and content, so a duplicate is promise 2 working, not an error, and a provider's correction is a new row (promise 1); three outcomes, counted apart: landed · already_landed (linked, not re-parsed) · corrected (parsed — latest-arrived wins and transactions.arrival_id moves to the newest row); a non-2xx answer lands as a provider_responses row with no arrivals — a failed ask is evidence of the ask; a duplicate is promise 2 working, reported as already_landed, never an error.
  3. The parser reads the arrival row, never the HTTP object, and points the domain row at it (transactions.arrival_id).
  4. Landing, parsing and read / status (once each) happen in ONE database transaction per page; a parser failure rolls that page back and is declared in the response (stage, page, summarized error) while earlier pages stay.
  5. No synthetic backfill: rows synced before the cutoff carry arrival_id NULL.

The six kinds (steps 4–5)

Five kinds arrive from outside; the sixth is never sent — the system writes postings from events.

Kind Holds Means
reference facts about the world a fact about the world
registry your accounts and your people one of your accounts
event what happened something that happened
derived math we did math we did — never a source
snapshot how things stood at one moment how things stood at one moment
posting debits and credits never arrives — the system writes postings from events

The rule book (step 4, applied)

One written rule per feed — its kind — and the system applies it to every arrival of that feed: src/lib/providers.ts RULE_BOOK, 24 rows (the deck's 20 verbatim plus the 4 the deck's sample omits and the store lands). Landing consults it (src/lib/arrivals/land.ts): every arrival carries its kind (arrivals.kind, prisma/migrations/20260907180000_arrival_kind/migration.sql — the same rules applied to every row landed before it), and a feed with no rule is a loud failure, never a default. The build asserts the book against the deck, the schema's enum, the migration's UPDATEs and every landing call site.

Provider Resource Kind Means Row
plaid transaction event something that happened deck
plaid account registry one of your accounts deck
plaid holding snapshot how things stood at one moment deck
stripe payout event deck
tastytrade quote reference a fact about the world deck
finnhub fundamentals reference deck
fred series reference deck
sec filing reference deck
liteapi booking event deck
viator activity reference deck
google places place reference deck
travel buddy visa reference deck
anthropic classification derived math we did — never a source deck
openai insight derived deck
xai grok sentiment derived deck
voyage embedding derived deck
ecfr title reference deck
us code title reference deck
federal register document reference deck
irs bulletin reference deck
plaid security reference added
plaid investment_transaction event added
stripe event event added
liteapi cancellation event added

The six tables (step 5, views)

Six tables, six names, the kind picks the table — as VIEWS over the typed feed tables (prisma/migrations/20260907200000_kind_views/migration.sql, generated from the census in src/lib/kindViews.ts; the six are view models in prisma/schema.prisma under Prisma's views preview; a view changes only through a later migration that drops and recreates it — the snapshot view over holdings, prisma/migrations/20260908120000_holdings_snapshot/migration.sql — and the build checks each view's newest text). The feed tables keep their real columns, constraints and arrival_id; each view unions exactly the feed tables of its kind, the kind per table from the rule book, in one common shape. Posting is empty by the deck's own law; snapshot holds Plaid holdings (REBUILD-01 PR-2d). A table whose feed the rule book cannot name is reported, never viewed. Today the six are views over the feed tables — event: transactions, investment transactions, bookings; reference: securities, places, the law corpus; registry: accounts; snapshot: holdings; derived: none yet; posting: empty by law.

Column Type
kind arrival_kind
feed text
table_name text
row_id text
their_id text
arrival_id text
user_id text
arrived timestamptz
View Unions Feeds
reference securities, places_cache, regulatory_documents plaid · security · google_places · place · by s.domain: ecfr · title / us_code · title / federal_register · document / irs · bulletin
registry accounts plaid · account
event transactions, investment_transactions, reservations plaid · transaction · plaid · investment_transaction · liteapi · booking
derived (nothing)
snapshot holdings plaid · holding
posting (nothing)
Feed table Kind Feed arrival_id user arrived Outside Prisma
securities reference plaid · security yes no s."createdAt"
places_cache reference google_places · place no no p."cachedAt"
regulatory_documents reference by s.domain: ecfr · title / us_code · title / federal_register · document / irs · bulletin no no d.retrieved_at prisma/migrations/20260503000000_pr_f_corpus_foundation/migration.sql (CREATE TABLE regulatory_documents; not a Prisma model)
accounts registry plaid · account no yes a."createdAt"
transactions event plaid · transaction yes yes t."createdAt"
investment_transactions event plaid · investment_transaction yes yes i."createdAt"
reservations event liteapi · booking no yes r."createdAt"
holdings snapshot plaid · holding yes yes h."createdAt"

Reported, not viewed — rows from a provider answer whose feed the rule book does not name:

Table Why
reservations (provider duffel) history rows from a retired provider (LAUNCH-01 RETIRE-01 deleted its book route; nothing writes provider 'duffel' any more) — the rows are kept, labeled by their provider, and stay outside the event view (the bookings view reads provider 'liteapi' only)
trip_scanner_results src/app/api/trips/[id]/ai-assistant/route.ts writes AI recommendations per trip/category; the book names no such feed (anthropic · classification is the only anthropic row)
scan_snapshots src/lib/convergence/snapshot-logger.ts writes our own scores over quotes — math we did, not a provider answer; no rule-book feed
operations_ai_usage src/lib/ai/recordUsage.ts:158 stores our AI calls (purpose, tokens, full_response) — the book's anthropic row is classification, not a usage log
discovery_proposals AI-authored proposals (src/lib/discovery); not the book's anthropic · classification by name
plaid_items Plaid's item — a handshake (the access token lives here); the deck: handshakes never enter the tables; the book has no plaid · item row
tastytrade_connections a handshake (tokens); never data
observatoryHealthLog our probes of the providers (src/app/api/data-observatory) — not a provider feed

The loop (step 7)

Every tool runs the same four beats: DISCOVER → DECIDE → COMMIT → RECORD.

The document (step 8)

Four fields on every record in the master table:

# Field
1 What it is
2 Its life story
3 Its pieces
4 Who did it, and when
life draft → committed → settled
who you · the moment you committed

The match key (step 9)

An observed money event meets its authored document on amount · date · reference.

The posting rule book (step 10)

Fourteen matched events, one rule each — the rule writes two lines:

Tool Event Debit Credit
Invoicing invoice issued A/R Revenue
Payments payment received Cash A/R
Brokerage fill Investments Cash
Travel booking charged Travel Card Payable
Expenses expense charged Expense Card Payable
Fixed Assets asset bought Fixed Assets Cash
Bill Pay bill paid A/P Cash
Payroll payroll run Wages + employer taxes Cash + withholdings
Debt debt payment Loan Payable + Interest Cash
Sales Tax sales tax paid Sales Tax Payable Cash
Ent Filings filing fee paid Filing Fees Cash
Retirement contribution Retirement Cash
Banking transfer Cash (to) Cash (from)
Tax tax paid Tax Expense Cash

The four lenses (step 11)

Question The math Reads
What do I owe in tax? Income so far × the rules. Revenue · Expense · Wages + employer taxes · irs bulletin · us code title
How long can I last? Cash ÷ what I burn each month. Cash · Expense · Travel · Wages + employer taxes · A/P
How is my trading doing? Wins, losses, and open risk; from fills, positions, and live quotes. Investments · plaid holding · tastytrade quote
How is my business doing? Money in minus money out. Revenue · Expense · Travel · Wages + employer taxes · Filing Fees

The two windows (step 12)

Pull out WHEN it is: a moment — a dot — or a span — a bar. Every dated thing appears in both windows at once: as a row in the Ledger, and on its day in the Calendar.

The gap ledger

BLUEPRINT is the step's headline; TODAY is the deck's honest-state line, verbatim; GAP is the difference between the two and nothing more. Steps 1–6 appear only where the deck states a dated fact.

Step Blueprint Today Gap
03 Store what arrived. Then decide what it means. the arrivals store holds 9,092 Plaid transactions, 712 investment transactions and 247 securities — every answer word for word, fingerprinted, status done — counted September 7, 2026; rows before September 3, 2026 carry no arrival. the other providers (tastytrade, the market and law feeds) land parsed; Stripe events (PR-4), LiteAPI bookings and cancellations (PR-5) and Plaid holdings (PR-2d) land raw-first with no dated count yet; the three old Plaid writers are retired (PR-3).
04 One rule per feed. Written down. rules are rows the system applies — src/lib/providers.ts RULE_BOOK, 24 rows (the deck's 20 plus plaid · security → reference, plaid · investment_transaction → event, stripe · event → event, liteapi · cancellation → event); every Plaid, Stripe and LiteAPI arrival carries its kind (transaction · event, investment_transaction · event, security · reference, stripe event · event, liteapi booking · event, liteapi cancellation · event, holding · snapshot), stamped on landing and applied by the migration to every row landed before it; a feed with no rule cannot land. the other providers' arrivals wait on their landing — nothing of theirs has arrived to be labeled; the 121 feeds' classification stays the August 24 census until each lands.
05 The kind picks the table. Today the six are views over the feed tables — event: transactions, investment transactions, bookings; reference: securities, places, the law corpus; registry: accounts; snapshot: holdings; derived: none yet; posting: empty by law. derived has no feed table the rule book names — the AI tables carry no rule-book row; posting is empty by law; 8 tables are reported, not viewed.
07 Every tool runs the same four beats. Discover. Decide. Commit. Record. This loop is the blueprint — the shape we're building every tool toward. Today, hotel bookings commit for real and an accepted task fires its build; the rest run discover → decide → draft, and commit is the beat we're wiring to the same loop, tool by tool. commit is real for two tools (hotel bookings, accepted tasks); the other twenty-three stop at draft.
08 One table holds everything you do. The master table is the blueprint. Today each tool keeps its own table; one table holding every document is the shape we're building. no master table; each tool keeps its own.
09 The deposit meets the invoice. The fill meets the order. (A piece of this is already alive today: card charges find their bookings and propose the match — you approve it.) one of fourteen matches proposes today (card charge ↔ booking); the other thirteen do not.
10 Nobody sends them. Rules write them. The posting table is the blueprint — today it holds zero lines. The rules writing them is the bookkeeping pipe we're building. zero lines; no rule writes yet.
11 Every answer is math on the lines. These four lenses are the blueprint. Today all four compute — tax and the business result from the journal entries you commit by hand, runway from Plaid's account balances against those entries, trading from the positions and lots you committed, with no live quote in the number. The rules-written lines and the live quotes the blueprint reads wait on the posting pipe. all four compute from hand-committed lines and Plaid balances; the rules-written lines and live quotes wait on step 10.
12 One Ledger. One Calendar. All twenty-five. Two windows over one set of rows is the blueprint. Today the calendar window is live in the cockpit; the ledger window fills as the posting pipe lands its lines. the Calendar window is live; the Ledger window is empty until step 10 writes.
13 One $100.00 sale runs the machine. Then every other door opens. Alive today: the travel match — card charges find their bookings and propose the match; you approve it. The project lane is wired end to end: a task lands for your review, and accepting it fires the build that answers it. the travel match is alive; the project lane is wired end to end; the sale's lines and lenses wait on steps 10–11.
14 Click any number. Walk it back. Alive today: the fingerprint, on the rules and the audit log — every regulation pull is hashed the moment it lands, and the audit log hash-chains every entry it records; the citation re-check is written, but the hash it compares against is stored empty today, so it proves nothing yet. Today the Plaid feeds land in that table — every answer word for word, fingerprinted: 9,092 transactions, 712 investment transactions, 247 securities, counted September 7, 2026. The other providers still land parsed; the same table is the shape they're moving into. fingerprints on the rules corpus, the audit log and the Plaid feeds; none on the other providers' feeds yet; the citation re-check still compares against an empty hash.

Navigation

The app lands on /answers — HOME: the four answers, then the whole sheet (25 jobs, each with its true status and the door to its step). A collapsible rail walks the sheet in FLOW ORDER: 6 families, 12 steps, every job in exactly one step. A step's status is derived from its jobs (LIVE when every one is, NOT_BUILT when every one is, else PARTIAL); a step with no screen opens an honest page that names its jobs and says it is not built. The list below is generated from src/lib/steps.ts, whose law runs at build (scripts/assert-tool-registry.ts).

# Step Family Status Opens Jobs
1 ACCOUNTS WHAT YOU OWN PARTIAL /accounts Banking (PARTIAL), Retirement (NOT_BUILT), Fixed Assets (NOT_BUILT)
2 TRADING WHAT YOU OWN PARTIAL /trading Brokerage (PARTIAL), Trade Log (PARTIAL)
3 BOOKS THE PROOF LIVE /books Bookkeeping (LIVE)
4 TAX THE PROOF PARTIAL /tax Tax (PARTIAL)
5 COMPLIANCE THE PROOF PARTIAL /compliance Compliance (PARTIAL)
6 FP&A THE PROOF NOT_BUILT /step/fpa (honest page — not built) FP&A (NOT_BUILT)
7 OWED WHAT YOU OWE NOT_BUILT /step/owed (honest page — not built) Debt (NOT_BUILT), Sales Tax (NOT_BUILT), Ent Filings (NOT_BUILT)
8 SALES MONEY IN NOT_BUILT /step/sales (honest page — not built) CRM (NOT_BUILT), Contracts (NOT_BUILT), Invoicing (NOT_BUILT), Payments (NOT_BUILT)
9 SPEND MONEY OUT NOT_BUILT /step/spend (honest page — not built) Bill Pay (NOT_BUILT), Payroll (NOT_BUILT), Expenses (NOT_BUILT), Mileage (NOT_BUILT)
10 TRAVEL MONEY OUT LIVE /travel Travel (LIVE)
11 BUDGET MONEY OUT PARTIAL /business Budget (PARTIAL)
12 OPERATIONS THE WORK PARTIAL /projects Calendar (PARTIAL), Tasks (PARTIAL), Time (PARTIAL)

The nine modules

Module What exists in code
Travel stays and flights through LiteAPI, activities through Viator, visa checks through RapidAPI — public routes under src/app/api/travel (15 route files); models trips (schema:652) and reservations (schema:1414).
Runway the reservation matcher — src/app/api/runway/match/propose, queue, review (the step-9 piece alive today) — plus src/app/api/runway/route.ts; models budgets (schema:622) and home_expenses (schema:1668).
Books Plaid-synced transactions, a chart of accounts, journal and ledger entries — the one Plaid writer, src/app/api/transactions/sync-complete/route.ts, writes transactions (schema:460) through src/lib/arrivals/plaidTransactionsPage.ts:114; journal_entries (schema:186), ledger_entries (schema:225).
Trade tastytrade connection, quotes and backtests — src/app/api/tastytrade (13 route files); models trade_cards (schema:1799) and scan_snapshots (schema:1985).
Tax scenarios, documents and the 2025 export script (npm run tax:export:2025) — src/app/api/tax (7 route files); models tax_scenarios (schema:1588) and tax_documents (schema:1907).
Compliance the regulatory corpus (eCFR, US Code, Federal Register, IRS bulletins) ingested by Inngest functions with sha256 on write, citations re-verified, and the hash-chained audit log — models regulatory_sources (schema:2298), citations (schema:2362), audit_log (schema:2528).
Routines scheduled routines evaluated by the routine-evaluator Inngest function — models operations_routines (schema:3182) and hub_scheduled_items (schema:3288); the deck calls the calendar window live in the cockpit (step 12 honest line).
Projects projects and tasks; accepting a pending task fires the Execute-Task Routine (src/app/api/operations/projects/[id]/tasks/[taskId]/route.ts:395-405) — models operations_projects (schema:3006) and operations_project_tasks (schema:3051).
Content scene groups, scenes, pieces and takes — model operations_content_pieces (schema:3415); src/app/api/operations carries 50 route files across Routines, Projects and Content.

Architecture

Versions from package.json, read 2026-09-02:

Layer What
Framework Next.js 15.5.25 · React ^18.3.1 · TypeScript ^5
Data Prisma ^5.22.0 (client ^5.22.0) on Azure PostgreSQL
Hosting Vercel (one cron in vercel.json: /api/cron/auto-categorize at 02:00 UTC)
Jobs Inngest ^4.2.6 — 8 functions: ecfr-ingest, embed-pending, fedreg-ingest, health-check, irb-ingest, operations-pipe-run, routine-evaluator, uscode-ingest
Auth HMAC-signed cookie (src/lib/cookie-auth.ts) · next-auth ^4.24.15 for OAuth sign-in
Payments / banking Stripe ^20.3.0 · Plaid ^11.0.0
Styling Tailwind CSS ^3.4.18
Runtime Node (typed against @types/node ^20)

Observed versus authored (step 6): what the world sends is observed; what you do is authored; the blueprint keeps the two apart and matches them on one key. Today the Plaid feeds land word for word, fingerprinted — 9,092 transactions, 712 investment transactions, 247 securities, counted September 7, 2026; the other providers still land parsed — see the gap ledger, step 14.

Scale, as of 2026-09-02: 116 Prisma models, 34 enums, 287 API route files, 34 runtime dependencies, 17 dev dependencies, one test file (npm test).

Engineering discipline

These are the written laws in CLAUDE.md, stated as practice:

  • Audit before action: a read-only audit with file:line citations precedes any implementation.
  • Derive, never retype: the deck's drawings derive from their consts, and module-scope layout laws throw at build — a false slide is a failed build.
  • Census before claim: every public surface claims only what a dated count of the code supports.
  • Fail loud: no silent fallbacks, no silent catches, no placeholder data.
  • One concept per PR: each change is atomic and revertible.
  • All work lands on claude/* branches; only the founder merges to main — the merge is the SOC 2 change-management control.
  • Migrations: the raw SQL and schema.prisma move in lockstep; the repo holds no DATABASE_URL, so a session can author a migration but never apply one.
  • The README and the deck are the blueprint; code drifts, the blueprint doesn't — drift is a bug.

Security model

  • Sessions: an HMAC-SHA256-signed cookie; verification uses a timing-safe comparison — src/lib/cookie-auth.ts:16-18, 26-45.
  • Middleware: every path not in PUBLIC_PATHS requires the verified cookie — src/middleware.ts:50-160, 162, 165-170; the two Routine callbacks (…/audit-ingest, …/exec-ingest) bypass the cookie and validate a shared-secret bearer (AUDIT_INGEST_SECRET, EXEC_INGEST_SECRET) instead — src/middleware.ts:173-187.
  • Route gates: getCurrentUser (src/lib/auth-helpers.ts:11), requireTier (:42), requireAdmin (src/lib/require-admin.ts:8); the working law is that a paid external call is never made before the gate (CLAUDE.md, Security-first).
  • User scoping: every query is scoped to the authed user — e.g. where: { userId: user.id } at src/app/api/tastytrade/connect/route.ts:54.
  • Rate limits: a durable fixed-window limiter backed by the rate_limit_hits table (src/lib/rateLimit.ts:3-15; schema:1399), plus src/lib/scan-rate-limit.ts and src/lib/ai-rate-limit.ts; tuned by SEARCH_/BOOK_/SCAN_/AI_RATE_LIMIT and _WINDOW, with daily caps AI_PIPE_DAILY_CAP, AI_EXEC_DAILY_CAP, AI_ROUTINE_DAILY_CAP, AI_DISCOVERY_DAILY_CAP (USD, from recorded spend — src/lib/discovery/discoveryGate.ts), TRAVEL_SEARCH_DAILY_CAP, and GOOGLE_PLACES_MONTHLY_CAP.
  • Audit log: a hash chain — each entry stores prev_hash and its own sha256 content_hash with a sequence_number (prisma/schema.prisma:2441-2443; written at src/lib/audit/writeAuditLog.ts:99); src/lib/audit/verifyAuditChain.ts:47, 80 recomputes the chain.
  • Citations: src/lib/citations/verifyCitation.ts:97 re-fetches the source and re-hashes it against citations.retrieved_content_hash (prisma/schema.prisma:2287); the column's only writer stores an empty string today (src/lib/discovery/materializeProposal.ts:165), so the check is structurally dead until the arrivals rebuild lands the real hash.
  • Corpus: the four ingest persisters hash content with sha256 on write — src/lib/corpus/ingest/ecfr-persist.ts:28, uscode-persist.ts:32, fedreg-persist.ts:31, irb-persist.ts:32.
  • Provider tokens at rest: AES-256-GCM with a key held in the deployment env (src/lib/secrets/tokenCipher.ts); readers accept ciphertext only.
  • Report a vulnerability to astuart@templestuart.com.

Providers

The provider menu the deck draws at step 2 — who feeds each job today, and which doors are open next:

The job Today Next
banks & accounts plaid teller
card money stripe square
trades & market data tastytrade schwab · ibkr · alpaca · tradier · snaptrade
company numbers finnhub polygon
the economy fred —
filings sec —
flights liteapi amadeus
hotels liteapi amadeus
activities viator —
locations google places —
visas travel buddy —
our AI anthropic · openai · xai grok · voyage —
the law itself ecfr · us code · federal register · irs —

The deck's own count: today that is 121 feeds from 20 providers — counted August 24, 2026.

The scanner's gates

The scanner scores every candidate on ONE of two models — the seller model (the composite as it was, unchanged) or the buyer model (the recomposition per the input-sign table, src/lib/convergence/input-signs.ts) — never both, never a sign flip in place. Each gate has one card per side. PoP and EV on a card are pricing quantities under a lognormal model at scan-time IV, not forecasts; realized frequency is measured in EDGE-01. No weight below is tuned on an outcome: there are none yet.

Gate Side Purpose Inputs and sources Weight, date set Evidence What it is NOT
Vol Edge SELL (seller model) Are these options priced above what the stock actually moves — is there a premium to sell? Mispricing 0.40 (VRP own-history percentile 0.30; IV percentile/rank level 0.30; |IV − HV| 0.25; HV30/60/90 trend 0.15 — TastyTrade market-metrics and the ticker's own scan_snapshots history); term structure 0.25 (TastyTrade expiration IVs); technicals 0.15 (TastyTrade candles, Finnhub 52-week range); skew 0.10 and dealer gamma 0.10 (Finnhub option chain, FRED DGS10). 0.25 static, blended toward the regime table (0.15–0.30) by regime confidence — set at #1082 (2026-06-20); untuned on any outcome. Goyal & Saretto 2009 and Carr & Wu 2009 (the variance risk premium); Vasquez 2017 (term structure — the code cites it with the reverse sign of its result; reported in MODEL-01, not changed); Cremers & Weinbaum 2010 (skew). Not a forecast of realized volatility, and not the buyer's read — the same inputs invert for a buyer of premium. The VRP leg is excluded until 20 scan days of own history exist.
Vol Edge BUY (buyer model) Are these options priced below what the stock actually moves — is premium cheap enough to own? Six admitted components at equal weight: VRP percentile (inverted), IV percentile/rank level (inverted), HV over IV from the raw spread, the HV30/60/90 ladder (inverted — rising realized vol), dealer gamma (inverted), volume ratio (kept) — TastyTrade market-metrics and candles, Finnhub chain, FRED DGS10. 0.25 of the buy score, equal and untuned, set 2026-09-15 — awaiting EDGE-01's third book (LOG-01 scan_candidates, the BUY bucket). Goyal & Saretto 2009 (HV above IV predicts positive straddle returns); Coval & Shumway 2001 (unconditional long vol loses — hence the catalyst gate on this side). Not a sign flip of the seller's number: term structure, RSI, Bollinger position, trend, the 52-week high and skew do NOT enter — input-signs.ts says why for each; MODEL-02 owns term structure and the SKEW index.
Quality SELL (seller model) Is the underlying a company that will not blow up under a short-premium structure? Safety 0.40 (liquidity rating, size, share volume, lendability, beta, debt-to-equity; the Altman cap, the borrow-rate penalty and the revenue-concentration modifier), profitability 0.30 (margins, returns, valuation, free cash flow, earnings quality, plus the Piotroski change modifier), growth 0.15, fundamental risk 0.15; ±5 for insider MSPR — Finnhub fundamentals and financials, TastyTrade, SEC EDGAR. 0.25 static → 0.20–0.40 by regime — set at #1082 (2026-06-20); untuned on any outcome. Piotroski 2000; Altman 1968; Christoffersen, Goyenko, Jacobs & Karoui 2018 (options illiquidity); Cao & Han 2013 (idiosyncratic vol, not beta). Not a stock-picking score — a quality company can still carry expensive or cheap options. The ranking's 40-floor reads this gate; an ETF scores here on safety alone (profitability, growth and risk excluded, weights renormalized).
Quality BUY (buyer model) Can this option be bought and unwound at a fair price, on an underlying that will not vanish? The six safety components only, at equal weight: TastyTrade liquidity rating, market cap, 20-day share volume (candles), lendability, beta, debt-to-equity (Finnhub). 0.25 of the buy score, equal and untuned, set 2026-09-15. Christoffersen, Goyenko, Jacobs & Karoui 2018 (the buyer pays the illiquidity premium too); Altman 1968 (solvency). Not profitability, growth, fundamental risk, the Piotroski change or MSPR — those are a long-side return tilt (Asness, Frazzini & Pedersen 2019) and feed the directional overlay, never the buy score; the borrow penalty and the concentration modifier do not enter.
Regime SELL (seller model) Does the macro and volatility backdrop favor the best premium-selling structure right now? FRED growth (6 series) and inflation (5 series) → regime probabilities with the yield-curve, high-yield-spread and cross-asset modifiers → the best strategy's fit in the strategy-regime matrix 0.70, VIX/VIX3M 0.20 (FRED VIXCLS ÷ VXVCLS), VVIX 0.10 (Cboe VVIX daily file, 24h cache), then × the ticker's SPY correlation (TastyTrade). On the trace at WEIGHT 0, present and dated: the VIX term structure (Cboe VIX9D ÷ VIX, VIX ÷ VIX3M, VIX3M ÷ VIX6M) and SKEW (Cboe) — set 2026-09-16, tuned by nobody. 0.25 static → 0.20–0.30 by regime — set at #1082 (2026-06-20) and EDGE-6 (2026-07-08); untuned on any outcome. Hamilton 1989 (the regime framework); Bansal & Stivers 2023 (the VIX overlay); CBOE PUT and BXM index history behind the matrix. The VVIX leg was dead from 2026-07-08 (VVIXCLS is not a FRED series — DATA-01) and was restored 2026-09-16 from Cboe; a Cboe read that fails is declared on the brake (UNVERIFIED, with the reason), never imputed. The weight-0 term-structure and SKEW inputs move nothing. The survival brake (VIX/VIX3M > 1 or VVIX ≥ 110) is declared on every card and suppresses short-premium suggestions; it does not move this number.
Regime BUY (buyer model) Is the market in a state where owning volatility is affordable — the same brake, read for the buyer? The survival brake's two inputs only, same sign: VIX/VIX3M (FRED VIXCLS ÷ VXVCLS) and VVIX (Cboe VVIX daily file — restored 2026-09-16; a failed read is declared, the gate is VIX/VIX3M alone). 0.25 of the buy score, equal and untuned, set 2026-09-15. STRATEGY-EVIDENCE §6 (the anti-wipeout rule — a brake applies to both sides); Park 2015 (the tail-risk premium binds when VVIX is elevated). Not the macro classification, the strategy matrix or the SPY-correlation multiplier — those are seller-shaped and do not enter; the weight-0 Cboe term-structure and SKEW inputs do not enter either. When a brake input is missing this gate is the other alone, or EXCLUDED when both are.
Info Edge SELL (seller model) Do the informed footprints — insiders, institutions, analysts, the news — lean positive on this name? Analyst consensus 0.15, price target 0.10, upgrades/downgrades 0.10, insiders 0.15, earnings momentum 0.20, options flow 0.05, news 0.15, institutional ownership 0.05, fund flow 0.05, 8-K events 0.05, recommendation revision 0.05 (raw sum 1.10, renormalized over what is present); a filing-recency overlay of +8/−12 — Finnhub, SEC EDGAR. 0.25 static → 0.15–0.30 by regime — set at #1082 (2026-06-20) and EDGE-7b (2026-07-08); untuned on any outcome. Womack 1996; Da & Schaumburg 2011; Seyhun 1986; Bernard & Thomas 1989; Johnson & So 2012; Chen, Jegadeesh & Wermers 2000; Chan, Jegadeesh & Lakonishok 1996. Not direction-neutral — this gate is the directional overlay's source (BULLISH above 65, BEARISH below 35), so a seller's iron condor is scored by a bull/bear read here; MODEL-01 reports that and leaves the seller's composite unchanged.
Info Edge BUY (buyer model) Is there attention and activity on this name — reasons the stock may move, without saying which way? Four admitted components at equal weight: unusual option activity (Finnhub chain), news buzz and the tier-1 share of coverage (Finnhub company-news), the 8-K count (SEC EDGAR, inverted — more material events, more catalysts). 0.25 of the buy score, equal and untuned, set 2026-09-15. Gao, Xing & Zhang (straddles bought into the announcement window); the ruling's direction-neutral rule — a buyer of a straddle needs movement, not a side. Not sentiment, targets, upgrades, insiders, earnings beats, the put/call ratio, O/S, institutions, fund flows, revisions or the filing overlay — every one is direction-bearing and feeds the overlay (bull call vs bear put debit spread), never this score.

The filter panel offers exactly the 6 strategies the builder makes (set 2026-09-16): Iron Condor, Put Credit Spread, Short Strangle, Long Straddle, Long Strangle, Debit Spread. The 10 below were offered until then and are not built:

Not built Why
Call Credit Spread No builder — the SELL branch (strategy-builder.ts generateStrategies) builds the put credit spread, the iron condor and the short strangle; no call-side credit spread routine exists.
Short Straddle No builder — an unbounded two-leg structure at the money; the short strangle is the only unbounded structure built, and it carries the undefined-risk cap.
Jade Lizard No builder — three legs (short put + short call spread); no leg-combination routine exists for it.
Bull Call Spread No builder since MODEL-01 — the bullish debit structure is built on the BUY side as "Debit Spread"; the SELL branch emits a rejection line naming this one.
Bear Call Spread No builder — a synonym of the call credit spread; appears only in the custom-leg namer (detectStrategyName), which nothing in the app calls.
Bear Put Spread No builder — the BUY branch builds the debit spread for BULLISH direction only and declares this one not built (strategy-builder.ts rejection line).
Bull Put Spread No builder under this name — it is the put credit spread, which IS built as "Put Credit Spread"; the duplicate name is retired.
Calendar Spread No builder — needs two expirations; the builder evaluates one expiration at a time (chain-fetcher.ts calls generateStrategies per expiration).
Diagonal Spread No builder — two expirations and two strikes; the same one-expiration limit as the calendar.
Iron Butterfly No builder — an at-the-money condor variant; no strike-selection routine exists for it.

Self-hosting

You need Node (the code is typed against @types/node ^20), PostgreSQL, and a host — it is built for Vercel. Then the keys. Every name in the first table is read by the code (grep -rhoE "process\.env\.[A-Z0-9_]+" src, 2026-09-02); the second table is what a dependency reads for it, which that grep cannot see — declared in src/lib/envLaw.ts, and the build's env law (scripts/assert-tool-registry.ts) requires every name in both tables documented here and nothing documented that nothing reads.

Vendor / concern Keys Needed when
Core JWT_SECRET (the ONE session secret — the former NEXTAUTH_SECRET is retired: the server refuses to boot while it is set, src/lib/secretGuard.ts), OWNER_EMAIL, ADMIN_USER_ID (the admin's users.id; every admin gate throws when it is unset — src/lib/admin.ts), NEXT_PUBLIC_OWNER_EMAIL, NEXT_PUBLIC_APP_URL, NEXT_PUBLIC_BASE_URL Required
Set by the platform NODE_ENV, NEXT_RUNTIME (nodejs or edge, set by Next.js — the session-secret boot guard runs on the Node runtime only, src/instrumentation.ts), NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA Provided by Vercel / Node / Next; nothing to set
Plaid (bank sync) PLAID_CLIENT_ID, PLAID_SECRET, PLAID_REDIRECT_URI (the OAuth return URL, src/lib/plaid/oauth.ts) Required unless Books sync is disabled — no link token is created without the redirect URI
Provider tokens at rest TOKEN_ENCRYPTION_KEY (base64 of 32 bytes), TOKEN_ENCRYPTION_KEY_ID (src/lib/secrets/tokenCipher.ts) Required — every Plaid and tastytrade token read or write fails loud without both
Stripe (payments) STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET; per entitlement STRIPE_TAB_<KEY>_PRICE_ID, STRIPE_CAT_<KEY>_PRICE_ID, STRIPE_BUNDLE_ALL_PRICE_ID (src/lib/stripe.ts) Required to sell modules; skip to run everything unlocked
Flights and stays (LiteAPI) LITEAPI_SANDBOX_KEY, LITEAPI_PRODUCTION_KEY, LITEAPI_MODE, FLIGHTS_LANE (liteapi is the only lane — unset resolves to it, anything else refuses; src/lib/flightsLane.ts:41); LITEAPI_WEBHOOK_TOKEN — the "Authentication Token" registered with the booking webhook endpoint (/api/webhooks/liteapi) in the LiteAPI dashboard, sent as the authorization header of every delivery and compared in constant time (src/app/api/webhooks/liteapi/route.ts); unset → the endpoint answers 500 and lands nothing Required unless travel is disabled; the webhook token only if the endpoint is registered
Tours (Viator) VIATOR_API_KEY Required unless activities are disabled
Visa rules (RapidAPI) RAPIDAPI_VISA_KEY, RAPIDAPI_VISA_HOST Required unless the visa check is disabled
Places GOOGLE_PLACES_API_KEY, GOOGLE_PLACES_MONTHLY_CAP, PLACES_CACHE_TTL_DAYS Key required unless location search is disabled; caps optional
Markets (tastytrade) TASTYTRADE_CLIENT_SECRET, TASTYTRADE_REFRESH_TOKEN, TT_USERNAME, TT_PASSWORD Required unless Trade is disabled
Market data FINNHUB_API_KEY, FRED_API_KEY Required unless the scanner is disabled
AI ANTHROPIC_API_KEY, OPENAI_API_KEY, VOYAGE_API_KEY Required unless the AI features are disabled
Email (Resend) RESEND_API_KEY, EMAIL_FROM Required unless email is disabled
Jobs (Inngest) INNGEST_EVENT_KEY, INNGEST_SIGNING_KEY Required unless background jobs are disabled
OAuth sign-in GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET Optional; email login works without
Routine and audit hooks CRON_SECRET, ROUTINE_AUDIT_TOKEN, ROUTINE_AUDIT_FIRE_URL, EXEC_ROUTINE_TOKEN, EXEC_ROUTINE_FIRE_URL, EXEC_INGEST_SECRET, AUDIT_INGEST_SECRET Required unless scheduled routines are disabled
Rate limits and caps SEARCH_RATE_LIMIT, SEARCH_RATE_WINDOW, SCAN_RATE_LIMIT, SCAN_RATE_WINDOW, AI_RATE_LIMIT, AI_RATE_WINDOW, AI_PIPE_DAILY_CAP, AI_EXEC_DAILY_CAP, AI_ROUTINE_DAILY_CAP, AI_DISCOVERY_DAILY_CAP, TRAVEL_SEARCH_DAILY_CAP; per provider TRAVEL_SEARCH_DAILY_CAP_<PROVIDER> (src/lib/travelSearchQuota.ts:79) Optional; the code has defaults
Misc YELP_API_KEY Required unless its feature is disabled

Read by a library

Variables no line of this code reads — a dependency does. The night next-auth 4.24.15 started building every OAuth callback from NEXTAUTH_URL (its utils/detect-origin.js), the table above could not have known the variable existed; in production the server now refuses to boot unless NEXTAUTH_URL is the https origin whose host equals NEXT_PUBLIC_APP_URL's (src/lib/siteUrlGuard.ts).

Name Read by Where Needed Expected shape
DATABASE_URL prisma prisma/schema.prisma:12 url = env("DATABASE_URL") — the client and every migration Required postgresql://user:password@host:5432/db?sslmode=require (Azure Postgres requires SSL)
NEXTAUTH_URL next-auth utils/detect-origin.js:9 (the origin of every OAuth callback and redirect — since 4.24.15 it wins over the request host on Vercel), jwt/index.js:65 (secure-cookie decision), react/index.js:53-54 (client base URL) Required the deployment's https origin with no path — its host must equal NEXT_PUBLIC_APP_URL's (https://www.templestuart.com in production; a bare templestuart.com is refused at boot by src/lib/siteUrlGuard.ts)
NEXTAUTH_URL_INTERNAL next-auth react/index.js:55-56 (server-side fetch base when the public origin is not reachable from the server) Optional an http(s) origin; unset here
AUTH_TRUST_HOST next-auth utils/detect-origin.js:10 (trust x-forwarded-host when NEXTAUTH_URL is unset) Optional unset — NEXTAUTH_URL rules the origin; never set this to paper over a wrong NEXTAUTH_URL
AUTH_SECRET next-auth next/index.js:17,49,128 (the fallback when options.secret is unset — ours is JWT_SECRET, so this is never consulted) Optional unset — SECRET-01: JWT_SECRET is the one session secret
VERCEL next-auth utils/detect-origin.js:10 (trust the forwarded host when NEXTAUTH_URL is unset), jwt/index.js:65 (secure cookies) Set by the host "1" on Vercel; set by the host
VERCEL_URL next-auth react/index.js:53 (client base URL fallback when NEXTAUTH_URL is unset) Set by the host .vercel.app, set by the host — never the production origin
VERCEL_ENV the boot check src/lib/siteUrlGuard.ts (through the env record: production / preview / development — the NEXTAUTH_URL check runs for production only, a Preview has a per-deploy host) Set by the host production / preview / development; set by the host
PORT next dist/server/lib/start-server.js (next start — self-hosting only; Vercel runs its own runtime) Set by the host a port number; set by the host
HOSTNAME next dist/build/utils.js (next start bind host — self-hosting only) Set by the host a hostname; set by the host

Yes, that's a lot of keys. That's why the next section exists.

Work with me

I built this for me — but I'll set it up for you.

  • Done-for-you setup: your own hosted copy, every API wired, you own everything.
  • Maintenance: I keep it updated and running.
  • Custom builds: need a feature? I build it.
  • Embed: want just one piece (the booking engine, the books, the scanner) inside your existing system? I do that too.

Email a project proposal (the template's pre-filled): astuart@templestuart.com

License

Source-available under the Business Source License 1.1 — see LICENSE. Free to self-host for personal use; commercial use or a done-for-you setup: astuart@templestuart.com


Temple Stuart is not a CPA firm, tax preparer, or licensed financial advisor. All tax figures generated by this platform are estimates for informational purposes only and must be verified by a qualified tax professional before filing. Use of this software does not constitute tax advice.

About

Temple Stuart

Resources

Stars

225 stars

Watchers

7 watching

Forks

Releases

Packages

Contributors

Languages