KnowledgeVault AI module-spec M-07
M-07 gives company-authenticated field technicians a semantic search interface over the published knowledge base. A POST /api/query endpoint embeds the natural-language query via text-embedding-3-small (1536d), runs pgvector cosine similarity against knowledge_chunks WHERE is_published=true, ranks results by a fixed formula (cosine_similarity×0.7 + ground_truth_verified::int×0.2 + novelty_score×0.1), and returns the top-5 chunks with expert attribution, chunk type, field-verified badge, and rele
| id | owner | blocks | question | resolved | resolution |
|---|---|---|---|---|---|
| OQ-M-07-01 | justin | D-07-04 ranking SQL query | The implementation scope blurb states ranking as cosine_similarity×0.7 + recency_weight×0.3 but the functional spec and approved blueprint both specify (cosine_similarity×0.7)+(ground_truth_verified::int×0.2)+(novelty_score×0.1). Which formula is authoritative? | true | 3-factor formula is authoritative: (cosine_similarity×0.7 + ground_truth_verified::int×0.2 + novelty_score×0.1). 2-factor was a stale implementation scope note. |
M-07 gives company-authenticated field technicians a semantic search interface over the published knowledge base. A POST /api/query endpoint embeds the natural-language query via text-embedding-3-small (1536d), runs pgvector cosine similarity against knowledge_chunks WHERE is_published=true, ranks results by a fixed formula (cosine_similarity×0.7 + ground_truth_verified::int×0.2 + novelty_score×0.1), and returns the top-5 chunks with expert attribution, chunk type, field-verified badge, and rele
No explicit evidence field yet. Require tests, screenshots, linked PRs, or reviewed outputs before marking complete.
- OQ-M-07-01: id: string, owner: string, blocks: string, question: string, resolved: boolean, resolution: string
Machine-readable source fields
M-07 gives company-authenticated field technicians a semantic search interface over the published knowledge base. A POST /api/query endpoint embeds the natural-language query via text-embedding-3-small (1536d), runs pgvector cosine similarity against knowledge_chunks WHERE is_published=true, ranks results by a fixed formula (cosine_similarity×0.7 + ground_truth_verified::int×0.2 + novelty_score×0.1), and returns the top-5 chunks with expert attribution, chunk type, field-verified badge, and relevance score. Every query is logged to the new field_queries table for downstream use by M-09. This module also establishes the company JWT middleware (Supabase Auth role=company + company_id claim) and a demo company-login endpoint; both are reused by M-08 and M-09 without modification.
M-07
| auth | path | errors | method | request | response |
|---|---|---|---|---|---|
| public — no JWT required | /api/auth/company-login | - Missing company_id => 422 {error: "company_id required", code: "VALIDATION_ERROR", status: 422} - Invalid demo_secret => 401 {error: "Invalid credentials", code: "INVALID_CREDENTIALS", status: 401} - company_id not in companies table => 404 {error: "Company not found", code: "COMPANY_NOT_FOUND", status: 404} - COMPANY_DEMO_SECRET env var not set => 500 {error: "Service misconfigured", code: "SERVER_ERROR", status: 500} | POST | {company_id: string (uuid), demo_secret: string} | {access_token: string, token_type: "bearer", expires_in: 3600, company_id: string} |
| company JWT required — Authorization: Bearer <token> with role=company, company_id claim | /api/query | - Missing Authorization header => 401 {error: "Authentication required", code: "MISSING_AUTH", status: 401} - Expired/invalid JWT => 401 {error: "Invalid token", code: "INVALID_TOKEN", status: 401} - company_id not in companies table => 403 {error: "Company not found", code: "COMPANY_NOT_FOUND", status: 403} - Missing query field => 422 {error: "query is required", code: "VALIDATION_ERROR", status: 422} - query > 500 chars => 422 {error: "query must be 500 characters or fewer", code: "VALIDATION_ERROR", status: 422} - OpenAI embedding failure => 503 {error: "Embedding service unavailable", cod | POST | {query: string (required, max 500 chars), sku_filter?: string, chunk_type_filter?: string, verified_only?: boolean} | {results: [{chunk_id: string, content: string, chunk_type: string, sku: string, expert_name: string, ground_truth_verified: boolean, captured_at: string (ISO8601), relevance_score: number, field_verified_badge: boolean}], query_id: string} |
| company JWT required | /api/query/recent | - Unauthenticated => 401 (from companyAuth middleware) - No queries exist for company => 200 [] | GET | No body. Optional query param: limit (integer, default 5, max 10) | [{query_id: string, query_text: string, created_at: string}] ordered by created_at DESC |
knowledge_chunks table is read-only in this module — no mutations. field_queries.results stores the full top-5 response as JSONB for M-09 replay without re-embedding. query_embedding nullable: if OpenAI fails, query_text row still persisted with null embedding.
| rls | name | indexes | purpose | key columns |
|---|---|---|---|---|
| RLS enabled. Service-role key: full access. Company JWT: SELECT and INSERT WHERE company_id = (auth.jwt()->>'company_id')::uuid. No UPDATE or DELETE for company role. | field_queries | - idx_field_queries_company_id ON field_queries(company_id) - idx_field_queries_created_at ON field_queries(created_at DESC) | Logs every company search query for analytics and M-09 coverage dashboard (top_queries, slang_cloud). | - id uuid PK DEFAULT gen_random_uuid() - company_id uuid NOT NULL REFERENCES companies(id) - query_text text NOT NULL - query_embedding vector(1536) nullable - results jsonb nullable - created_at timestamptz NOT NULL DEFAULT now() |
Knowledge Retrieval
| id | name | type | inputs | outputs | description |
|---|---|---|---|---|---|
| D-07-01 | 0007_field_queries.sql | migration | - companies table must exist (FK reference) | - field_queries table created in public schema - idx_field_queries_company_id index created - idx_field_queries_created_at index created | Creates the field_queries table to persist every search query submitted via POST /api/query. Consumed by M-09 (top_queries, slang_cloud analytics). Includes indexes on company_id and created_at. |
| D-07-02 | lib/companyAuth.js | service | - HTTP request with Authorization: Bearer <jwt> header - SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY environment variables - companies table with id uuid column | - req.companyId set to UUID string on success - next() called on success - JSON error response on failure | Express/Next.js middleware that validates a company JWT on every request to /api/query, /api/virtual-experts/*, and /api/dashboard/*. Extracts Supabase Auth JWT from Authorization: Bearer header, verifies signature, checks role=company claim, reads company_id claim, queries companies table to confirm company exists. Attaches req.companyId to request context. Returns 401 on missing/invalid/expired JWT. Returns 403 if company_id is not found in companies table. Authored once here and imported without duplication by M-08 and M-09. |
| D-07-03 | POST /api/auth/company-login | api_route | - Request body: {company_id: string (uuid), demo_secret: string} | - 200: {access_token: string, token_type: "bearer", expires_in: 3600, company_id: string} - 401 on invalid demo_secret - 404 if company_id not in companies table - 422 on missing/malformed body | Demo login endpoint that mints a Supabase Auth session for a company user. Accepts company_id and shared demo_secret env var. Uses Supabase Admin API to create/retrieve a Supabase Auth user keyed to the company (email: company-<company_id>@demo.knowledgevault.internal), then issues a JWT with custom claims {role: "company", company_id: <company_id>}. Intended for demo flows and internal testing only. Does not require existing JWT. |
| D-07-04 | POST /api/query | api_route | - Authorization: Bearer <company_jwt> header - Request body: {query: string (required, 1-500 chars), sku_filter?: string, chunk_type_filter?: string, verified_only?: boolean} | - 200: {results: [{chunk_id, content, chunk_type, sku, expert_name, ground_truth_verified, captured_at, relevance_score, field_verified_badge}], query_id: string} - 200: {results: [], query_id: string} when no published chunks match - 401 on missing/invalid JWT (from middleware) - 403 if company_id not in companies table (from middleware) - 422 on missing query field or query > 500 chars - 503 on OpenAI embedding failure with Retry-After: 10 header | Core semantic search endpoint. Protected by companyAuth middleware. Embeds query_text via OpenAI text-embedding-3-small (1536d). Queries knowledge_chunks using pgvector cosine similarity (1 - (embedding <=> query_embedding)) WHERE is_published=true. Optional filters: sku_filter (WHERE sku = ), chunk_type_filter (WHERE chunk_type = ), verified_only (WHERE ground_truth_verified = true). Ranking formula: relevance_score = (cosine_similarity * 0.7) + (ground_truth_verified::int * 0.2) + (COALESCE(novelty_score, 0) * 0.1). Returns top-5 ordered by relevance_score DESC. field_verified_badge=true whe |
| D-07-05 | Knowledge Search UI (S-06-a + S-06-b) | component | - company JWT stored in session/cookie - POST /api/query endpoint (D-07-04) - GET /api/query/recent endpoint (returns [{query_id, query_text, created_at}] for this company, limit 5) | - S-06-a and S-06-b screens rendered and routed at /search and /search/results - GET /api/query/recent API route returning recent queries for the authenticated company | Two-screen search UI for company-authenticated field technicians. S-06-a (KnowledgeSearchScreen): Full-screen, prominent centered search bar (placeholder: "Ask anything about a product or process…"), optional SKU filter input below, recent queries list (last 5 from GET /api/query/recent), Powered by KnowledgeVault badge at bottom. Submitting calls POST /api/query. Skeleton shown during request. S-06-b (SearchResultsScreen): Sticky search bar at top (pre-filled), scrollable results list of up to 5 result cards, Powered by badge at bottom. Each card shows: content, chunk type badge (Failure Mode |
| calls | purpose | service |
|---|---|---|
| - POST https://api.openai.com/v1/embeddings — model: text-embedding-3-small, input: query string, dimensions: 1536. Called once per POST /api/query. 10s timeout. On error => 503 response, query_text still logged to field_queries with null query_embedding. | Embed the field technician natural-language query as a 1536-dimension vector for pgvector cosine similarity search | OpenAI |
| - supabaseAdmin.auth.admin.createUser — create demo company auth user (email: company-<company_id>@demo.knowledgevault.internal) if not exists - supabaseAdmin.auth.admin.generateLink or signInWithPassword — mint access token with custom claims {role: "company", company_id: <uuid>} - supabase.auth.getUser(token) — verify JWT signature and extract claims in companyAuth middleware | Issue and verify company JWTs with custom claims (role=company, company_id). Mint demo sessions via Admin API for POST /api/auth/company-login. | Supabase Auth |
- experts table exists with columns: id uuid, name text, role_type text, years_experience integer
- knowledge_chunks table exists with columns: id uuid, session_id uuid, expert_id uuid, product_id uuid, sku text, chunk_type text, content text, embedding vector(1536), ground_truth_verified boolean, novelty_score float, is_published boolean, created_at timestamptz
- companies table exists with columns: id uuid, name text, plan text
- pgvector extension enabled on the Supabase database (CREATE EXTENSION IF NOT EXISTS vector already applied)
- Supabase Auth configured and capable of issuing JWTs with custom claims (role, company_id)
- M-05 Evaluation & Knowledge Publication is complete — knowledge_chunks.is_published can be set to true and knowledge_chunks.embedding is populated post-evaluation
- OPENAI_API_KEY environment variable set with access to text-embedding-3-small
| id | owner | blocks | question | resolved | resolution |
|---|---|---|---|---|---|
| OQ-M-07-01 | justin | D-07-04 ranking SQL query | The implementation scope blurb states ranking as cosine_similarity×0.7 + recency_weight×0.3 but the functional spec and approved blueprint both specify (cosine_similarity×0.7)+(ground_truth_verified::int×0.2)+(novelty_score×0.1). Which formula is authoritative? | true | 3-factor formula is authoritative: (cosine_similarity×0.7 + ground_truth_verified::int×0.2 + novelty_score×0.1). 2-factor was a stale implementation scope note. |
1.0
lib/companyAuth.js exported as single middleware function. M-08 and M-09 import it directly — no duplication per constraint.
POST /api/auth/company-login gated by COMPANY_DEMO_SECRET env var. Not for production multi-user auth. Production company auth is out of scope.
(cosine_similarity * 0.7) + (ground_truth_verified::int * 0.2) + (COALESCE(novelty_score, 0) * 0.1) — matches functional spec and approved blueprint. Proceeding with this; flagged as OQ-M-07-01.
Not used as a search filter in M-07 per out_of_scope. Query searches all is_published=true chunks globally for authenticated company.
field_verified_badge=true when knowledge_chunks.created_at > NOW() - INTERVAL 60 minutes — uses chunk own created_at. Consistent with done_when criteria.