diff --git a/apps/qdb-advisory/README.md b/apps/qdb-advisory/README.md new file mode 100644 index 0000000..9a89e37 --- /dev/null +++ b/apps/qdb-advisory/README.md @@ -0,0 +1,98 @@ +# QDB Advisory v0 + +Code-first founder advisory execution loop derived from the Sophie/QDB case. + +## Product boundary + +This is not a mentor AI, CRM, autonomous advisor, or portfolio-intelligence product. + +Closed loop: + +`interaction -> evidence -> state change -> decision -> commitment/experiment -> follow-up -> outcome -> next-session brief` + +AI proposes semantic structure. Code controls state. Evidence controls truth. + +## v0 stack + +- Next.js + TypeScript UI/API +- Vercel Workflows for durable orchestration, retries, sleeps, and human/event waits +- Supabase/Postgres as the only durable product source of truth +- Structured LLM extraction behind a strict schema +- Git branches/PRs + preview deployments + +No vector DB. No RAG. No autonomous tool calling. No portfolio clustering in v0. + +## v0 screens + +1. Founder dashboard: current state, commitments, experiments, blockers, trajectory. +2. Session record: source interaction, evidence, decisions, commitments, experiments. +3. Advisor brief: what changed, what did not happen, unresolved commitments, state changes, and 3-5 questions requiring advisor judgment. + +Portfolio intelligence is explicitly deferred until repeated real use. + +## Runtime flows + +### Interaction processing + +`POST /api/interactions -> persist immutable interaction -> start processInteraction(interactionId)` + +Workflow steps: + +1. Load immutable interaction. +2. Extract bounded structured objects. +3. Reject unsupported/low-confidence objects. +4. Persist accepted observations/assumptions/decisions/commitments/experiments transactionally. +5. Rebuild/propose founder state from accepted evidence. +6. Persist a new state version. +7. Start a commitment lifecycle workflow for each new commitment with a due date. + +### Commitment lifecycle + +`commitment created -> durable wait -> reminder if still open -> due-date check -> founder response -> outcome evidence -> interaction workflow again` + +Completion is never inferred. An open commitment whose due date has passed is displayed as overdue; its canonical status remains open until explicit evidence changes it. + +### Next-session brief + +v0 generates the brief on demand from immutable history + current state. Calendar-triggered generation is added only after real workflow access is earned. + +## Non-negotiable invariants + +- Raw interactions are immutable. +- Every extracted observation links to an interaction and a supporting source excerpt. +- The LLM cannot directly update canonical state. +- Recommendations do not become commitments without explicit acceptance. +- Missing deadlines remain null. +- Completion is never inferred. +- Founder state is disposable and reconstructable from event history. +- Duplicate interaction ingestion is idempotent. +- Cross-founder writes are impossible by construction. + +## Demo dataset + +Exactly one fictional founder and three sessions. The third session must prove the chain from prior commitment -> actual execution -> evidence -> state transition -> advisor brief. + +## Done when + +A raw session passes end-to-end with no manual database edits: + +1. Interaction persisted. +2. Evidence extracted. +3. Decisions captured. +4. Explicit commitments created. +5. Previous state retrieved. +6. Evidence-supported state transition proposed. +7. Current state version updated. +8. Due commitments are visible. +9. Founder update reconciles against the prior commitment. +10. Outcome becomes new evidence. +11. State updates again. +12. Next-session brief reflects the whole chain. + +Quality floor before external use: + +- commitments >95% precision +- decisions >90% precision +- metrics >95% precision +- observations >90% precision +- hallucinated critical objects = 0 diff --git a/apps/qdb-advisory/db/migrations/001_core.sql b/apps/qdb-advisory/db/migrations/001_core.sql new file mode 100644 index 0000000..67e81ee --- /dev/null +++ b/apps/qdb-advisory/db/migrations/001_core.sql @@ -0,0 +1,114 @@ +create extension if not exists pgcrypto; + +create table if not exists founders ( + id uuid primary key default gen_random_uuid(), + name text not null, + company_name text, + stage text, + advisor_id uuid, + created_at timestamptz not null default now() +); + +create table if not exists interactions ( + id uuid primary key default gen_random_uuid(), + founder_id uuid not null references founders(id) on delete cascade, + occurred_at timestamptz not null, + interaction_type text not null check (interaction_type in ('advisory_session','founder_update','advisor_note','follow_up_response')), + source_type text not null, + raw_text text not null, + source_url text, + source_hash text not null, + created_at timestamptz not null default now(), + unique (founder_id, source_hash) +); + +create table if not exists observations ( + id uuid primary key default gen_random_uuid(), + founder_id uuid not null references founders(id) on delete cascade, + interaction_id uuid not null references interactions(id) on delete cascade, + category text not null, + claim text not null, + evidence_quote text not null, + evidence_type text not null check (evidence_type in ('reported_fact','customer_signal','behavioral_evidence','metric','advisor_observation','founder_belief')), + confidence numeric(4,3) not null check (confidence >= 0.6 and confidence <= 1.0), + occurred_at timestamptz not null, + created_at timestamptz not null default now() +); + +create table if not exists assumptions ( + id uuid primary key default gen_random_uuid(), + founder_id uuid not null references founders(id) on delete cascade, + statement text not null, + domain text not null, + status text not null check (status in ('untested','testing','supported','weakened','invalidated','superseded')), + confidence numeric(4,3) not null check (confidence >= 0 and confidence <= 1.0), + created_at timestamptz not null default now(), + updated_at timestamptz not null default now() +); + +create table if not exists decisions ( + id uuid primary key default gen_random_uuid(), + founder_id uuid not null references founders(id) on delete cascade, + interaction_id uuid not null references interactions(id) on delete cascade, + decision text not null, + rationale text, + created_at timestamptz not null default now() +); + +create table if not exists commitments ( + id uuid primary key default gen_random_uuid(), + founder_id uuid not null references founders(id) on delete cascade, + interaction_id uuid not null references interactions(id) on delete cascade, + owner text not null, + action text not null, + success_criterion text, + due_at timestamptz, + status text not null default 'open' check (status in ('open','completed','blocked','abandoned','superseded')), + completed_at timestamptz, + blocker text, + created_at timestamptz not null default now(), + check ((status = 'completed' and completed_at is not null) or status <> 'completed') +); + +create table if not exists experiments ( + id uuid primary key default gen_random_uuid(), + founder_id uuid not null references founders(id) on delete cascade, + interaction_id uuid not null references interactions(id) on delete cascade, + hypothesis text not null, + method text not null, + success_threshold text, + deadline timestamptz, + status text not null default 'planned' check (status in ('planned','running','completed','inconclusive','cancelled')), + result text, + learning text, + created_at timestamptz not null default now(), + completed_at timestamptz +); + +create table if not exists founder_state ( + founder_id uuid primary key references founders(id) on delete cascade, + state_version integer not null default 0 check (state_version >= 0), + primary_bottleneck text, + current_goal text, + current_milestone text, + icp_state text, + problem_state text, + wtp_state text, + traction_state text, + distribution_state text, + execution_state text, + open_questions jsonb not null default '[]'::jsonb, + key_risks jsonb not null default '[]'::jsonb, + last_updated_at timestamptz not null default now() +); + +create index if not exists idx_interactions_founder_time on interactions(founder_id, occurred_at desc); +create index if not exists idx_observations_founder_time on observations(founder_id, occurred_at desc); +create index if not exists idx_commitments_founder_status_due on commitments(founder_id, status, due_at); +create index if not exists idx_experiments_founder_status on experiments(founder_id, status); + +-- Overdue is derived, never a canonical commitment status. +create or replace view overdue_commitments as +select * +from commitments +where status = 'open' and due_at is not null and due_at < now();