plan-module · supabase_json
KnowledgeVault AI plan-module M-04
plan-module artifact · for KnowledgeVault AI · phase M-04 · status draft
tasks
| id | role | brief | title | completion | depends on |
|---|---|---|---|---|---|
| 1 | developer | Create file apps/knowledgevault/supabase/migrations/004_capture_sessions_and_chunks.sql. First inspect the existing migrations directory and use the correct next sequential number per POL-ARCH-001 (never modify existing migration files; no sequence gaps).
Create enum types idempotently (use DO $$ BEGIN … EXCEPTION WHEN duplicate_object THEN null; END $$): capture_session_status ('in_progress','complete','abandoned'); knowledge_chunk_type ('failure_mode','symptom','fix','psi_range','safety_warning','field_tip','tool_required','part_number_reference').
CREATE TABLE IF NOT EXISTS `capture | DB migration: capture_sessions, knowledge_chunks, evaluation_queue | - type: file - evaluate: Migration file applies idempotently: both enum types created, all three tables present with correct column definitions and ON DELETE behaviour, RLS enabled on capture_sessions and knowledge_chunks with all specified policies, all eight indexes created, commented rollback block present; passes POL-ARCH-001 (sequential naming, no existing file modified) and POL-SEC-001 (RLS on all user tables) and POL-DATA-001 (all _id columns have FK constraints with explicit ON DELETE). - min length: 1500 | |
| 2 | developer | Create apps/knowledgevault/src/lib/server/shared-expert-context.ts. This TypeScript module exports a singleton in-process Map-based service maintaining per-session live state during voice capture sessions. It is keyed by session_id (string). Use TypeScript strict mode; no bare any types (POL-STYLE-001).
Export interface SharedExpertContextObject: { session_id: string; question_sequence: string[]; answered_question_count: number; knowledge_surface_coverage_pct: number; context_summary: string; wer_compensation_applied: boolean; }.
Implement four exported functions:
1. init(session_id: st | SharedExpertContext in-process session state service | - type: file - evaluate: TypeScript module exports init(), update(), freeze(), get(), and abandonStaleSessions() with correct signatures; update() increments answered_question_count by 1 per call and appends next_question; freeze() evicts session from Map so subsequent get() returns null; auto-abandon timer fires at 300 seconds and sets capture_sessions.status='abandoned' plus inserts evaluation_queue row; passes POL-STYLE-001 (strict=true, no bare any, no @ts-ignore) and POL-STYLE-002 (no empty catch blocks). - min length: 400 | |
| 3 | developer | Create apps/knowledgevault/src/app/api/sessions/start/route.ts (Next.js 15 App Router, named export POST). This route creates a new CaptureSession and returns session context to the expert.
Auth: call supabase.auth.getUser() — if no user or JWT role claim ≠ 'expert' return 401 { error:'Unauthorized', code:'UNAUTHORIZED', status:401 } (POL-SEC-002). Never skip getUser.
Parse body JSON { product_id: string }. If product_id missing return 400 { error:'product_id required', code:'MISSING_PRODUCT_ID', status:400 }. Sanitize product_id via sanitizeInput() before any DB call (POL-SEC-003).
L | POST /api/sessions/start route: create capture session | - type: file - evaluate: Route exports POST handler that: calls supabase.auth.getUser() and verifies role=expert before any DB operation (POL-SEC-002); sanitizes product_id before DB writes (POL-SEC-003); returns 201 with all five required fields (session_id, first_question, product_context, earnings_estimate_range); inserts capture_sessions row with status='in_progress'; calls SharedExpertContext.init(); returns correct error codes for all specified failure cases (400/401/422/503); passes POL-STYLE-001. - min length: 350 | - 1 - 2 |
| 4 | developer | Create apps/knowledgevault/src/app/api/sessions/[id]/audio/route.ts (Next.js 15 App Router, named export POST). This route accepts a binary audio chunk, transcribes via Deepgram Nova-2, extracts micro-objects via GPT-4o, and updates session state.
Auth: supabase.auth.getUser() — reject 401 if no user or role≠expert (POL-SEC-002).
Read path param id (session uuid). Read raw binary body (Content-Type: audio/webm or audio/opus). If body empty return 400 { error:'Audio chunk required', code:'MISSING_AUDIO', status:400 }.
Validate session: query capture_sessions WHERE id=param via service | POST /api/sessions/:id/audio route: transcribe and extract | - type: file - evaluate: Route exports POST handler that: verifies JWT role=expert (POL-SEC-002); opens Deepgram Nova-2 WebSocket per call and closes after transcript; sets wer_compensation_applied=true when any word confidence < 0.75; calls GPT-4o with SharedExpertContext system prompt; inserts one knowledge_chunks row per micro_object (is_published=false); updates capture_sessions.session_context JSONB immediately; accumulates cost_breakdown fields without reset; broadcasts to Supabase Realtime channel session:<id>:chunks per micro_object; returns session_status='session_complete_ready' when | - 1 - 2 - 3 |
| 5 | developer | Create apps/knowledgevault/src/app/api/sessions/[id]/complete/route.ts (Next.js 15 App Router, named export POST). This route closes an in-progress capture session and enqueues it for M-05 evaluation.
Auth: supabase.auth.getUser() — reject 401 if no user or role≠expert (POL-SEC-002).
Read path param id (session uuid). Body is {} (empty — do not attempt to parse meaningful fields).
Validation: query capture_sessions WHERE id=param via service role. If no row or expert_id≠user.id return 404 { error:'Session not found', code:'SESSION_NOT_FOUND', status:404 }. If status is 'complete' or ' | POST /api/sessions/:id/complete route: close session | - type: file - evaluate: Route exports POST handler that: verifies JWT role=expert (POL-SEC-002); returns 400 when answered_question_count=0; sets capture_sessions.status='complete' and completed_at and session_duration_s; calls SharedExpertContext.freeze() so subsequent get() returns null; inserts evaluation_queue row; returns chunks_captured and earnings_estimate and session_duration_s; all error codes correct (400/401/404/409); passes POL-STYLE-001. - min length: 250 | - 1 - 2 - 3 - 4 |
| 6 | developer | Create apps/knowledgevault/src/screens/ActiveCaptureSessionScreen.tsx (React Native, TypeScript strict). Receives navigation params: session_id (string, uuid), first_question (string), product_context ({sku, name, category, manufacturer}), earnings_estimate_range ({min: number, max: number}).
Layout — full-screen two-panel:
- Top 60%: (1) Current question text — large centered, role='status', aria-live='polite' (C-03-a-1). (2) Audio waveform showing microphone state listening/processing/idle, aria-label='Microphone status: <state>' (C-03-a-2). (3) Live transcript feed, aria-live='polite', a | ActiveCaptureSessionScreen (S-03-a) React Native component | - type: file - evaluate: Component renders within 1000ms from nav param (no mount API call for first question); DONE button is disabled at answered_question_count=0 and enabled at ≥1; subscribes to Supabase Realtime channel session:<id>:chunks on mount and unsubscribes on unmount; auto-close prompt appears on session_status='session_complete_ready'; abandon requires confirmation modal; all eight C-03-a accessibility attributes present (role=status, aria-live=polite on question/transcript/panel, aria-label on waveform); all eight chunk type display labels exactly match spec; passes POL-STYLE-00 | - 3 - 4 - 5 |
| 7 | developer | Create apps/knowledgevault/src/screens/SessionCompleteScreen.tsx (React Native, TypeScript strict). Receives navigation params: chunks_captured (number/integer), earnings_estimate ({min: number, max: number}), session_duration_s (number/integer), session_id (string, uuid — passed through for CTA navigation).
This screen makes zero network calls. All content comes from navigation params. Renders immediately on mount with no loading state.
Layout — full-screen centered:
- Single summary card (C-03-b-1) with accessibilityRole='summary' or equivalent (role=status per spec). Card contains:
| SessionCompleteScreen (S-03-b) React Native component | - type: file - evaluate: Component makes zero network calls; headline text exactly equals 'Session submitted'; body renders '<n> knowledge objects captured' using chunks_captured param; earnings displays '$<min.toFixed(2)>–$<max.toFixed(2)>'; CTA label exactly 'View results when ready' and navigates to S-04-a with session_id; back navigation disabled; summary card has accessibility role=status; passes POL-STYLE-001 (strict=true, no bare any). - min length: 150 | - 5 - 6 |
Agent Handoff
Start Here
| id | role | brief | title | completion | depends on |
|---|---|---|---|---|---|
| 1 | developer | Create file apps/knowledgevault/supabase/migrations/004_capture_sessions_and_chunks.sql. First inspect the existing migrations directory and use the correct next sequential number per POL-ARCH-001 (never modify existing migration files; no sequence gaps).
Create enum types idempotently (use DO $$ BEGIN … EXCEPTION WHEN duplicate_object THEN null; END $$): capture_session_status ('in_progress','complete','abandoned'); knowledge_chunk_type ('failure_mode','symptom','fix','psi_range','safety_warning','field_tip','tool_required','part_number_reference').
CREATE TABLE IF NOT EXISTS `capture | DB migration: capture_sessions, knowledge_chunks, evaluation_queue | - type: file - evaluate: Migration file applies idempotently: both enum types created, all three tables present with correct column definitions and ON DELETE behaviour, RLS enabled on capture_sessions and knowledge_chunks with all specified policies, all eight indexes created, commented rollback block present; passes POL-ARCH-001 (sequential naming, no existing file modified) and POL-SEC-001 (RLS on all user tables) and POL-DATA-001 (all _id columns have FK constraints with explicit ON DELETE). - min length: 1500 | |
| 2 | developer | Create apps/knowledgevault/src/lib/server/shared-expert-context.ts. This TypeScript module exports a singleton in-process Map-based service maintaining per-session live state during voice capture sessions. It is keyed by session_id (string). Use TypeScript strict mode; no bare any types (POL-STYLE-001).
Export interface SharedExpertContextObject: { session_id: string; question_sequence: string[]; answered_question_count: number; knowledge_surface_coverage_pct: number; context_summary: string; wer_compensation_applied: boolean; }.
Implement four exported functions:
1. init(session_id: st | SharedExpertContext in-process session state service | - type: file - evaluate: TypeScript module exports init(), update(), freeze(), get(), and abandonStaleSessions() with correct signatures; update() increments answered_question_count by 1 per call and appends next_question; freeze() evicts session from Map so subsequent get() returns null; auto-abandon timer fires at 300 seconds and sets capture_sessions.status='abandoned' plus inserts evaluation_queue row; passes POL-STYLE-001 (strict=true, no bare any, no @ts-ignore) and POL-STYLE-002 (no empty catch blocks). - min length: 400 | |
| 3 | developer | Create apps/knowledgevault/src/app/api/sessions/start/route.ts (Next.js 15 App Router, named export POST). This route creates a new CaptureSession and returns session context to the expert.
Auth: call supabase.auth.getUser() — if no user or JWT role claim ≠ 'expert' return 401 { error:'Unauthorized', code:'UNAUTHORIZED', status:401 } (POL-SEC-002). Never skip getUser.
Parse body JSON { product_id: string }. If product_id missing return 400 { error:'product_id required', code:'MISSING_PRODUCT_ID', status:400 }. Sanitize product_id via sanitizeInput() before any DB call (POL-SEC-003).
L | POST /api/sessions/start route: create capture session | - type: file - evaluate: Route exports POST handler that: calls supabase.auth.getUser() and verifies role=expert before any DB operation (POL-SEC-002); sanitizes product_id before DB writes (POL-SEC-003); returns 201 with all five required fields (session_id, first_question, product_context, earnings_estimate_range); inserts capture_sessions row with status='in_progress'; calls SharedExpertContext.init(); returns correct error codes for all specified failure cases (400/401/422/503); passes POL-STYLE-001. - min length: 350 | - 1 - 2 |
| 4 | developer | Create apps/knowledgevault/src/app/api/sessions/[id]/audio/route.ts (Next.js 15 App Router, named export POST). This route accepts a binary audio chunk, transcribes via Deepgram Nova-2, extracts micro-objects via GPT-4o, and updates session state.
Auth: supabase.auth.getUser() — reject 401 if no user or role≠expert (POL-SEC-002).
Read path param id (session uuid). Read raw binary body (Content-Type: audio/webm or audio/opus). If body empty return 400 { error:'Audio chunk required', code:'MISSING_AUDIO', status:400 }.
Validate session: query capture_sessions WHERE id=param via service | POST /api/sessions/:id/audio route: transcribe and extract | - type: file - evaluate: Route exports POST handler that: verifies JWT role=expert (POL-SEC-002); opens Deepgram Nova-2 WebSocket per call and closes after transcript; sets wer_compensation_applied=true when any word confidence < 0.75; calls GPT-4o with SharedExpertContext system prompt; inserts one knowledge_chunks row per micro_object (is_published=false); updates capture_sessions.session_context JSONB immediately; accumulates cost_breakdown fields without reset; broadcasts to Supabase Realtime channel session:<id>:chunks per micro_object; returns session_status='session_complete_ready' when | - 1 - 2 - 3 |
| 5 | developer | Create apps/knowledgevault/src/app/api/sessions/[id]/complete/route.ts (Next.js 15 App Router, named export POST). This route closes an in-progress capture session and enqueues it for M-05 evaluation.
Auth: supabase.auth.getUser() — reject 401 if no user or role≠expert (POL-SEC-002).
Read path param id (session uuid). Body is {} (empty — do not attempt to parse meaningful fields).
Validation: query capture_sessions WHERE id=param via service role. If no row or expert_id≠user.id return 404 { error:'Session not found', code:'SESSION_NOT_FOUND', status:404 }. If status is 'complete' or ' | POST /api/sessions/:id/complete route: close session | - type: file - evaluate: Route exports POST handler that: verifies JWT role=expert (POL-SEC-002); returns 400 when answered_question_count=0; sets capture_sessions.status='complete' and completed_at and session_duration_s; calls SharedExpertContext.freeze() so subsequent get() returns null; inserts evaluation_queue row; returns chunks_captured and earnings_estimate and session_duration_s; all error codes correct (400/401/404/409); passes POL-STYLE-001. - min length: 250 | - 1 - 2 - 3 - 4 |
| 6 | developer | Create apps/knowledgevault/src/screens/ActiveCaptureSessionScreen.tsx (React Native, TypeScript strict). Receives navigation params: session_id (string, uuid), first_question (string), product_context ({sku, name, category, manufacturer}), earnings_estimate_range ({min: number, max: number}).
Layout — full-screen two-panel:
- Top 60%: (1) Current question text — large centered, role='status', aria-live='polite' (C-03-a-1). (2) Audio waveform showing microphone state listening/processing/idle, aria-label='Microphone status: <state>' (C-03-a-2). (3) Live transcript feed, aria-live='polite', a | ActiveCaptureSessionScreen (S-03-a) React Native component | - type: file - evaluate: Component renders within 1000ms from nav param (no mount API call for first question); DONE button is disabled at answered_question_count=0 and enabled at ≥1; subscribes to Supabase Realtime channel session:<id>:chunks on mount and unsubscribes on unmount; auto-close prompt appears on session_status='session_complete_ready'; abandon requires confirmation modal; all eight C-03-a accessibility attributes present (role=status, aria-live=polite on question/transcript/panel, aria-label on waveform); all eight chunk type display labels exactly match spec; passes POL-STYLE-00 | - 3 - 4 - 5 |
| 7 | developer | Create apps/knowledgevault/src/screens/SessionCompleteScreen.tsx (React Native, TypeScript strict). Receives navigation params: chunks_captured (number/integer), earnings_estimate ({min: number, max: number}), session_duration_s (number/integer), session_id (string, uuid — passed through for CTA navigation).
This screen makes zero network calls. All content comes from navigation params. Renders immediately on mount with no loading state.
Layout — full-screen centered:
- Single summary card (C-03-b-1) with accessibilityRole='summary' or equivalent (role=status per spec). Card contains:
| SessionCompleteScreen (S-03-b) React Native component | - type: file - evaluate: Component makes zero network calls; headline text exactly equals 'Session submitted'; body renders '<n> knowledge objects captured' using chunks_captured param; earnings displays '$<min.toFixed(2)>–$<max.toFixed(2)>'; CTA label exactly 'View results when ready' and navigates to S-04-a with session_id; back navigation disabled; summary card has accessibility role=status; passes POL-STYLE-001 (strict=true, no bare any). - min length: 150 | - 5 - 6 |
Completion Evidence
No explicit evidence field yet. Require tests, screenshots, linked PRs, or reviewed outputs before marking complete.
Artifact Shape
- tasks: 7 items
- module id: M-04
- module name: Capture Orchestrator
- schema version: 1.0
- depends on modules: 2 items
Structured Payload
Machine-readable source fields
tasks
| id | role | brief | title | completion | depends on |
|---|---|---|---|---|---|
| 1 | developer | Create file apps/knowledgevault/supabase/migrations/004_capture_sessions_and_chunks.sql. First inspect the existing migrations directory and use the correct next sequential number per POL-ARCH-001 (never modify existing migration files; no sequence gaps).
Create enum types idempotently (use DO $$ BEGIN … EXCEPTION WHEN duplicate_object THEN null; END $$): capture_session_status ('in_progress','complete','abandoned'); knowledge_chunk_type ('failure_mode','symptom','fix','psi_range','safety_warning','field_tip','tool_required','part_number_reference').
CREATE TABLE IF NOT EXISTS `capture | DB migration: capture_sessions, knowledge_chunks, evaluation_queue | - type: file - evaluate: Migration file applies idempotently: both enum types created, all three tables present with correct column definitions and ON DELETE behaviour, RLS enabled on capture_sessions and knowledge_chunks with all specified policies, all eight indexes created, commented rollback block present; passes POL-ARCH-001 (sequential naming, no existing file modified) and POL-SEC-001 (RLS on all user tables) and POL-DATA-001 (all _id columns have FK constraints with explicit ON DELETE). - min length: 1500 | |
| 2 | developer | Create apps/knowledgevault/src/lib/server/shared-expert-context.ts. This TypeScript module exports a singleton in-process Map-based service maintaining per-session live state during voice capture sessions. It is keyed by session_id (string). Use TypeScript strict mode; no bare any types (POL-STYLE-001).
Export interface SharedExpertContextObject: { session_id: string; question_sequence: string[]; answered_question_count: number; knowledge_surface_coverage_pct: number; context_summary: string; wer_compensation_applied: boolean; }.
Implement four exported functions:
1. init(session_id: st | SharedExpertContext in-process session state service | - type: file - evaluate: TypeScript module exports init(), update(), freeze(), get(), and abandonStaleSessions() with correct signatures; update() increments answered_question_count by 1 per call and appends next_question; freeze() evicts session from Map so subsequent get() returns null; auto-abandon timer fires at 300 seconds and sets capture_sessions.status='abandoned' plus inserts evaluation_queue row; passes POL-STYLE-001 (strict=true, no bare any, no @ts-ignore) and POL-STYLE-002 (no empty catch blocks). - min length: 400 | |
| 3 | developer | Create apps/knowledgevault/src/app/api/sessions/start/route.ts (Next.js 15 App Router, named export POST). This route creates a new CaptureSession and returns session context to the expert.
Auth: call supabase.auth.getUser() — if no user or JWT role claim ≠ 'expert' return 401 { error:'Unauthorized', code:'UNAUTHORIZED', status:401 } (POL-SEC-002). Never skip getUser.
Parse body JSON { product_id: string }. If product_id missing return 400 { error:'product_id required', code:'MISSING_PRODUCT_ID', status:400 }. Sanitize product_id via sanitizeInput() before any DB call (POL-SEC-003).
L | POST /api/sessions/start route: create capture session | - type: file - evaluate: Route exports POST handler that: calls supabase.auth.getUser() and verifies role=expert before any DB operation (POL-SEC-002); sanitizes product_id before DB writes (POL-SEC-003); returns 201 with all five required fields (session_id, first_question, product_context, earnings_estimate_range); inserts capture_sessions row with status='in_progress'; calls SharedExpertContext.init(); returns correct error codes for all specified failure cases (400/401/422/503); passes POL-STYLE-001. - min length: 350 | - 1 - 2 |
| 4 | developer | Create apps/knowledgevault/src/app/api/sessions/[id]/audio/route.ts (Next.js 15 App Router, named export POST). This route accepts a binary audio chunk, transcribes via Deepgram Nova-2, extracts micro-objects via GPT-4o, and updates session state.
Auth: supabase.auth.getUser() — reject 401 if no user or role≠expert (POL-SEC-002).
Read path param id (session uuid). Read raw binary body (Content-Type: audio/webm or audio/opus). If body empty return 400 { error:'Audio chunk required', code:'MISSING_AUDIO', status:400 }.
Validate session: query capture_sessions WHERE id=param via service | POST /api/sessions/:id/audio route: transcribe and extract | - type: file - evaluate: Route exports POST handler that: verifies JWT role=expert (POL-SEC-002); opens Deepgram Nova-2 WebSocket per call and closes after transcript; sets wer_compensation_applied=true when any word confidence < 0.75; calls GPT-4o with SharedExpertContext system prompt; inserts one knowledge_chunks row per micro_object (is_published=false); updates capture_sessions.session_context JSONB immediately; accumulates cost_breakdown fields without reset; broadcasts to Supabase Realtime channel session:<id>:chunks per micro_object; returns session_status='session_complete_ready' when | - 1 - 2 - 3 |
| 5 | developer | Create apps/knowledgevault/src/app/api/sessions/[id]/complete/route.ts (Next.js 15 App Router, named export POST). This route closes an in-progress capture session and enqueues it for M-05 evaluation.
Auth: supabase.auth.getUser() — reject 401 if no user or role≠expert (POL-SEC-002).
Read path param id (session uuid). Body is {} (empty — do not attempt to parse meaningful fields).
Validation: query capture_sessions WHERE id=param via service role. If no row or expert_id≠user.id return 404 { error:'Session not found', code:'SESSION_NOT_FOUND', status:404 }. If status is 'complete' or ' | POST /api/sessions/:id/complete route: close session | - type: file - evaluate: Route exports POST handler that: verifies JWT role=expert (POL-SEC-002); returns 400 when answered_question_count=0; sets capture_sessions.status='complete' and completed_at and session_duration_s; calls SharedExpertContext.freeze() so subsequent get() returns null; inserts evaluation_queue row; returns chunks_captured and earnings_estimate and session_duration_s; all error codes correct (400/401/404/409); passes POL-STYLE-001. - min length: 250 | - 1 - 2 - 3 - 4 |
| 6 | developer | Create apps/knowledgevault/src/screens/ActiveCaptureSessionScreen.tsx (React Native, TypeScript strict). Receives navigation params: session_id (string, uuid), first_question (string), product_context ({sku, name, category, manufacturer}), earnings_estimate_range ({min: number, max: number}).
Layout — full-screen two-panel:
- Top 60%: (1) Current question text — large centered, role='status', aria-live='polite' (C-03-a-1). (2) Audio waveform showing microphone state listening/processing/idle, aria-label='Microphone status: <state>' (C-03-a-2). (3) Live transcript feed, aria-live='polite', a | ActiveCaptureSessionScreen (S-03-a) React Native component | - type: file - evaluate: Component renders within 1000ms from nav param (no mount API call for first question); DONE button is disabled at answered_question_count=0 and enabled at ≥1; subscribes to Supabase Realtime channel session:<id>:chunks on mount and unsubscribes on unmount; auto-close prompt appears on session_status='session_complete_ready'; abandon requires confirmation modal; all eight C-03-a accessibility attributes present (role=status, aria-live=polite on question/transcript/panel, aria-label on waveform); all eight chunk type display labels exactly match spec; passes POL-STYLE-00 | - 3 - 4 - 5 |
| 7 | developer | Create apps/knowledgevault/src/screens/SessionCompleteScreen.tsx (React Native, TypeScript strict). Receives navigation params: chunks_captured (number/integer), earnings_estimate ({min: number, max: number}), session_duration_s (number/integer), session_id (string, uuid — passed through for CTA navigation).
This screen makes zero network calls. All content comes from navigation params. Renders immediately on mount with no loading state.
Layout — full-screen centered:
- Single summary card (C-03-b-1) with accessibilityRole='summary' or equivalent (role=status per spec). Card contains:
| SessionCompleteScreen (S-03-b) React Native component | - type: file - evaluate: Component makes zero network calls; headline text exactly equals 'Session submitted'; body renders '<n> knowledge objects captured' using chunks_captured param; earnings displays '$<min.toFixed(2)>–$<max.toFixed(2)>'; CTA label exactly 'View results when ready' and navigates to S-04-a with session_id; back navigation disabled; summary card has accessibility role=status; passes POL-STYLE-001 (strict=true, no bare any). - min length: 150 | - 5 - 6 |
module id
M-04
module name
Capture Orchestrator
schema version
1.0
depends on modules
- M-02
- M-03