-- ============================================================================ -- Frontend reconciliation gaps -- -- Three columns the frontend reads or writes but migration 000001 had no place -- for. All three were found by sweeping runtime write paths, not just seed data. -- -- ── 1. job_applications.interview_id ────────────────────────────────────── -- -- WRITE components/krow/AIInterviewModal.jsx:180 -- PATCH {status:'interview', interview_id, ai_score} — reachable from -- admin/CandidateProfile.jsx, admin/Candidates.jsx, PositionDetail.jsx -- READ components/krow/CandidateExpandedDetails.jsx:41,142 -- via CandidateCard.jsx ← admin/Candidates.jsx, PositionDetail.jsx -- SEED 5 of 24 applications in src/api/seed.js carry it -- -- Deliberately NOT a foreign key. `app_devon` references `int_devon`, for which -- no AIInterview record exists in the seed; a REFERENCES constraint would -- either fail the seed or force that value to NULL, and nulling it would be -- silently transforming source data. The column is a soft reference, which is -- also how the frontend treats it — every read is a truthiness check, never a -- lookup. -- ============================================================================ SET search_path = public; ALTER TABLE public.job_applications ADD COLUMN interview_id uuid; COMMENT ON COLUMN public.job_applications.interview_id IS 'Soft reference to ai_interviews.id. Intentionally unconstrained: seed data ' 'contains a dangling reference, and the frontend only ever tests it for ' 'presence.'; CREATE INDEX job_applications_interview_idx ON public.job_applications (interview_id) WHERE interview_id IS NOT NULL; -- ── 2. courses.training_outline ─────────────────────────────────────────── -- -- WRITE components/forge/AddSkillTraining.jsx:103 ← pages/University.jsx -- (mounted at /admin/university) -- READ components/forge/challengeMeta.js:139, ai-assistant/insights.js:177, -- ai-assistant/capabilities/admin.js:1547, lib/skills/actions.js:369 -- SEED 0 of 40 courses carry it — the Forge authoring flow writes it, and -- every shipped course predates that flow. -- -- text[] rather than jsonb: the writer is -- `form.outline.map((s) => s.trim()).filter(Boolean)`, a plain list of strings, -- and every reader guards with Array.isArray then filters. This matches the -- shape of completion_criteria and verification_criteria on the same table. ALTER TABLE public.courses ADD COLUMN training_outline text[] NOT NULL DEFAULT '{}'; COMMENT ON COLUMN public.courses.training_outline IS 'Ordered written training steps, authored through the Forge flow. Empty for ' 'every course that predates it.'; -- ── 3. worker_profiles.score_breakdown ──────────────────────────────────── -- -- WRITE lib/krowScore.js:64 recalcProfilePatch() → spread into the profile -- patch at lib/krowHooks.js:682, the live challenge-submission path -- reached through CourseDetail.jsx. -- READ No reader consumes it *from a profile*: every score_breakdown reader -- in the frontend works on a job application. The column exists so the -- write lands rather than being silently discarded. -- -- jsonb, mirroring job_applications.score_breakdown, which holds the output of -- the same scoring engine. ALTER TABLE public.worker_profiles ADD COLUMN score_breakdown jsonb NOT NULL DEFAULT '{}'::jsonb; ALTER TABLE public.worker_profiles ADD CONSTRAINT worker_profiles_score_breakdown_object CHECK (jsonb_typeof(score_breakdown) = 'object'); COMMENT ON COLUMN public.worker_profiles.score_breakdown IS 'Per-dimension KROW score detail from recalcProfilePatch(). Written by the ' 'challenge flow; no frontend reader consumes it from a profile yet.';