MBO/changelog
MBO · v1.0

Changelog

90 entries · rendered from CHANGELOG.md at build time.

← Back

FIX-KYLE-COMPOSIO-QBO-AUTH-20260714 — Pin connected accounts into Kyle sessions

  • Files modified: supabase/functions/kyle-composio/index.ts, CHANGELOG.md
  • Fix: Kyle now passes the actual ACTIVE Composio connected-account ids into each direct-tools router session, instead of only exposing toolkit tool definitions. This addresses QuickBooks reads failing with No active connection found for toolkit(s) 'quickbooks' in this session even when QuickBooks is connected for the Kyle Composio user.
  • Safety: Logs only toolkit names and pinned account count, never account secrets or tokens.

PROMPT-KYLE-3.3-WORLDVIEW-20260707 — Standing worldview brief + operator profile

  • Files created: supabase/migrations/(kyle_worldview).sql, src/routes/cockpit.kyle.profile.tsx
  • Files modified: supabase/functions/kyle-agent-step/index.ts (buildBootBrief helper prepended to every run seed; snapshots pinned onto agent_runs), src/integrations/supabase/types.ts (auto)
  • Schema: new table kyle_operator_profiles (per user, per workspace: comms_style, working_hours_tz, working_hours, risk_appetite, current_okrs, notes). New MV kyle_worldview per workspace: P0/P1 incidents, top open atlas signals, freshness health, unhealthy sites, active/parked runs, today's spend. pg_cron kyle_worldview_refresh_60s runs kyle_worldview_refresh() every minute (CONCURRENTLY, logged in mv_refresh_log). RPCs: kyle_worldview_get(workspace), kyle_operator_profile_get(workspace,user), kyle_standing_rules_active(). Added columns to agent_runs: system_prompt_version, worldview_snapshot, operator_snapshot (pinned per run for reproducibility).
  • Orchestrator: on run open, kyle-agent-step builds a boot brief (worldview + operator + standing rules), snapshots it onto agent_runs, and prepends <worldview>…</worldview>\n<operator>…</operator>\n<standing_rules>…</standing_rules> to the seed user message for both monolith and durable modes. Hard caps: worldview 3200 chars, operator 800, standing_rules 1600 — well under the 800-token budget.
  • UI: /cockpit/kyle/profile — per-user editable profile (comms style, TZ, working hours, risk appetite, OKRs JSON, notes) that Kyle reads on every run open.

PROMPT-MBO-DOPPLER-SYNC-20260618-001 — Doppler 1:1 project sync for every Block/Site

  • Files created: supabase/migrations/(timestamp)_doppler_sync_001.sql, supabase/functions/dopplerSync/index.ts, src/lib/dopplerSync.functions.ts, src/components/sites/DopplerTab.tsx, src/routes/doppler-sync.tsx
  • Files modified: supabase/config.toml (verify_jwt=false for dopplerSync), src/components/sites/SiteTabs.tsx (add "doppler" tab), src/routes/sites.$siteId.tsx (wire DopplerTab), src/routes/sites-manager.tsx (add doppler_project_slug to CRITICAL jig fields → item #13), CHANGELOG.md
  • Schema: ALTER public.sites ADD doppler_project_slug text, doppler_project_id text, doppler_synced_at timestamptz. New trigger tg_sites_doppler_autosync (AFTER INSERT) fires pg_net.http_post to the dopplerSync edge function with action=create_project — best-effort, never blocks the insert.
  • Edge function dopplerSync: three actions — create_project (POST Doppler /v3/projects → ensure dev/stg/prd configs → write slug+id+timestamp back to sites), bulk_seed (sweeps every site where doppler_project_slug IS NULL), check_status (verifies project + lists configs with live secret counts). Doppler bearer token read from DOPPLER_API_KEY env var — never logged, never returned.
  • UI: New /doppler-sync bulk seed page — total / synced / missing stat cards, "Seed All Missing Projects" button, live per-site ✅/❌ progress, final summary. New "Doppler" tab on Site detail — status banner (Synced/Drifted/Not synced/Unknown), project info card, configs table with secret counts, three actions: Sync Now, Check Status, Open in Doppler.
  • Verification: Migration applied; edge function deployed; build clean.
  • Commit: (Lovable fills in)

PROMPT-MBO-20260618-BLOCK-DEPENDENCY-TAB-001 — Block Dependency & Readiness tab on Site detail

  • Files modified: supabase/migrations/(timestamp)_block_dep_001.sql, src/components/sites/SiteTabs.tsx, src/routes/sites.$siteId.tsx, src/components/sites/DependenciesTab.tsx (new), CHANGELOG.md
  • Schema: ALTER public.sites ADD fed_by text[] NOT NULL DEFAULT '{}', feeds_into text[] NOT NULL DEFAULT '{}', linked_block_codes text[] NOT NULL DEFAULT '{}', wave text NULL, layer text NULL. GIN indexes on the three arrays. No RLS / GRANT changes (existing sites policies apply).
  • UI: New "Dependencies" tab in Site detail tab bar (between Tasks and Deployments). Readiness banner pinned at top (green = ready / amber = blocked / neutral = no upstream). Three sections — Consumes (fed_by), Consumed by (feeds_into), Peers (linked_block_codes, hidden when empty). Each row: status dot, name, @code badge, wave/layer chips, status chip. Blocking rows in Consumes get amber border-left + BLOCKING flag. Rows resolve via sites.code lookup; unknown codes render as unknown and are non-navigable. Known rows link to /sites/$siteId.
  • Verification: Migration applied; types regenerated with new array columns; tab wired into SiteTabs union + route switch.
  • Commit: (Lovable fills in)

APP-CI-003 — Promote competitors to Sites (not Atlas)

  • Files modified: src/routes/competitive-intel.tsx, src/routes/sites.index.tsx, src/components/sites/SiteCard.tsx, CHANGELOG.md
  • Schema: ALTER public.sites ADD site_type (internal/external/holding/marketing/partner, default internal, CHECK), backfill NDA → holding. ALTER public.competitive_intel ADD promoted_site_id uuid FK → sites(id) ON DELETE SET NULL. Drop/recreate sites_status_check adding 'external'. New SECURITY DEFINER RPC public.promote_intel_to_site(...) — atomic site insert + intel link, idempotency guard via SELECT…FOR UPDATE, workspace write check via public.can_write_workspace(p_workspace_id), EXECUTE granted to authenticated.
  • Frontend: Promote button now calls RPC instead of writing to atlas_signals; toast reads "Promoted to Sites"; button shows "In Sites" + disabled when promoted_site_id set. Sites page gets a Type filter row (All · Internal · External · Holding · Marketing · Partner). SiteCard gets a colored type badge alongside the status chip.
  • Cleanup: Deleted leftover Origin HQ atlas_signals row (most recent by created_at, by title ILIKE 'Origin HQ%').
  • Verification: Migration applied. RPC + GRANT live. Origin HQ atlas row removed. Build clean.
  • Commit: (Lovable fills in)

APP-CI-002 — Competitive Intel enrichment pipeline (scrape → tech detect → AI analysis)

  • Files created: supabase/migrations/(timestamp)_app_ci_002.sql, supabase/functions/ci-pipeline/index.ts, supabase/functions/ci-pipeline/deno.json
  • Files modified: src/routes/competitive-intel.tsx, supabase/config.toml, CHANGELOG.md
  • Schema: ALTER public.competitive_intel ADD pipeline_status (idle/queued/running/done/error, CHECK + index), pipeline_error text, tech_stack jsonb, ai_analysis jsonb, raw_payload jsonb (no-op — already from CI-001), enriched_at timestamptz, enrichment_model text.
  • Edge function ci-pipeline: async pipeline (EdgeRuntime.waitUntil) — scrape source_url (15s timeout, UA Mozilla/WorldPortBot) → header+HTML tech-stack fingerprinting (Next.js, Gatsby [real fingerprints — not _app.tsx], WordPress, Webflow, Framer, React, Vue, Cloudflare, Vercel, AWS, Stripe, Intercom, Segment, GA) → Anthropic claude-sonnet-4-6 (configurable via AGENT_MODEL env) structured JSON analysis (snapshot, threat level, strengths/weaknesses, WorldPort opportunity, recommended_category) → admin write-back. Only fills category/summary when blank.
  • Auth model (FIX from prompt review): function does NOT use requireServiceRole. Instead verifies caller's user JWT with admin.auth.getUser(token), then re-queries the row through an RLS-scoped anon client using the user's bearer — existing SR-006 policies do the authorization. Admin client used only for the privileged write-back. Front end sends session.access_token, never anon/service keys.
  • verify_jwt = false set in supabase/config.toml (not deno.json) — function reachable but does its own auth check.
  • Frontend: Pipeline status chip column, Analyze/Re-run button (disabled if no source_url or running), expand row → renders ai_analysis sections + tech stack chips + enrichment timestamp/model. Auto-triggers pipeline on new entry creation when source_url provided. Smart polling (every 2s, stops on terminal state or 60s cap). All row-action buttons stopPropagation so they don't toggle the expand panel.
  • Secrets: ANTHROPIC_API_KEY (present).
  • Verification: Migration applied (pipeline_status column + index live). Build clean.
  • Commit: (Lovable fills in)

APP-CI-001 — Competitive Site Intelligence (ported from mbo-entityos-shell prompt)

  • Files created: supabase/migrations/(timestamp)_app_ci_001.sql, src/routes/competitive-intel.tsx, docs/agent-platform/APP-CI-001-RUNBOOK.md
  • Files modified: LOVABLE_RULES.md (Serial Dictionary: added CI prefix), CHANGELOG.md
  • Schema: public.competitive_intel — workspace_id NOT NULL → workspaces, optional site_id → sites, optional promoted_atlas_signal_id → atlas_signals. Serial prefix CI via canonical mbo_set_serial_tg('CI'). Registered in serial_registry.
  • RLS (SR-006 dual-clause): ci_select_members (SELECT via is_workspace_member), ci_insert_writers / ci_update_writers / ci_delete_writers (via can_write_workspace). GRANTs to authenticated + service_role issued before RLS enable (Pitfall #11).
  • Route: /competitive-intel — list + filter by site + create modal + promote-to-Atlas action. Uses direct supabase client (matches research-vault.tsx pattern) and worldport primitives.
  • Promotion: creates an atlas_signals row (kind=competitor_intel, severity=info, source_table=competitive_intel, source_id=row.id) and back-links via promoted_atlas_signal_id.
  • Port deltas from original prompt: dropped proposed mbo schema (lives in public), dropped proposed edge function (browser-side supabase call instead, RLS-enforced), serial format CI-YYYYMMDD-NNN (not PROMPT-MBO-SITE-INTEL-...), no sidebar nav wired (URL-only for now).
  • Verification: 5 gates pass — table exists, RLS=true, 4 policies (SELECT/INSERT/UPDATE/DELETE), set_serial + touch_updated_at triggers, CI prefix in serial_registry.
  • Commit: (Lovable fills in)

APP-012 — Acceptance runbook + formal pack closure

  • Files created: docs/agent-platform/APP-012-ACCEPTANCE-RUNBOOK.md
  • Files modified: CHANGELOG.md, LOVABLE_RULES.md (closing note appended at end)
  • Pack closure: BP-AGENT-PLATFORM-PACK-001 formally closed. Eight changes shipped (APP-001, APP-002-REVA, APP-002.5-REVA, APP-003-REVA, APP-004, APP-005, APP-009+010-UNIFIED, APP-RECONCILE-001). Four prompts deferred to TheCodex Block (APP-006, APP-006-REVA, APP-007, APP-008) on Frame+Settings architecture grounds. One dependent prompt deferred (APP-011 — needs run ledger data).
  • Verification: 16-check structural audit + 6 functional smoke tests on 2026-06-15. All five primary DB migrations + corrective APP-RECONCILE-001 verified structurally complete and functionally sound. Partial unique index enforcement on agent_prompt_packs confirmed for both INSERT and UPDATE.
  • Rules added/updated this pack: SR-006 (codified with named platform-global agents + RLS pattern + GRANT subsection + anti-patterns), SR-007 (Frame+Settings — codified during Kyle debug), SR-010 (audit-before-build standing law), Pitfall #11 (GRANT ordering reinforced), Pitfall #12 (filename auto-generation codified), Serial System (rewritten to canonical mbo_set_serial_tg pattern with prefix registry including MEM and APPK)
  • Findings queued for BP-RLS-FOLLOWUP-001: 5 open (none blocking), 3 resolved during execution by APP-RECONCILE-001
  • Architecture documents ratified: WD-LOVABLE-001 (Winding Down Lovable), SU-WORLDPORT-001 (Spinning Up TheCodex Block) — both incorporate four-pillar North Star foundation: Evidence Trail, Provenance Attestation, Policy Enforcement, Auditor Surface
  • Commit: (Lovable fills in)

APP-RECONCILE-001 — Reconcile shipped agent_* tables with codified rules

  • Files created: supabase/migrations/20260615180110_5e224552-67d7-47b2-9bb7-52013ab98fdf.sql (prompt-specified 20260615120000_app_reconcile_001.sql per Pitfall #12)
  • Files modified: LOVABLE_RULES.md (SR-006 council/council_seat reconciliation — 4 lines), CHANGELOG.md
  • Schema corrections:
    • agent_tool_calls RLS: dropped agent_tool_calls_select and agent_tool_calls_write; created agent_tool_calls_ws_read (SELECT, NULL OR member), agent_tool_calls_ws_write (INSERT, NOT NULL AND writer), agent_tool_calls_ws_update (UPDATE, NOT NULL AND writer in USING + WITH CHECK) — matches SR-006 dual-clause verbatim and parity with agent_runs/agent_memory_entries/agent_prompt_packs
    • agent_runs trigger: migrated trg_arn_serial → set_serial using canonical mbo_set_serial_tg('ARN'); produces ARN-GLOBAL-YYYYMMDD-NNN (was ARN-MBO-YYYYMMDD-NNN). Zero data risk — agent_runs has 0 rows.
    • tg_serial_arn_fn function: dropped (no remaining dependents per pre-DDL audit)
  • Rules update: LOVABLE_RULES.md SR-006 council members documented as kind: council (matches production data). Brian's decision (option A) — rules describe reality, not data correction.
  • BP-RLS-FOLLOWUP-001 findings resolved by this migration:
    • Finding #1 (agent_tool_calls missing UPDATE policy) — RESOLVED
    • Finding (agent_tool_calls SR-006 incompliance — SELECT missing NULL clause, INSERT missing NULL guard) — RESOLVED
    • Finding (agent_runs legacy trigger pattern) — RESOLVED
  • Rules cited: SR-006 (full + GRANT subsection), Serial System (canonical mbo_set_serial_tg), SR-010 (audit-before-build)
  • Verification: build · typecheck · 5 verify SQL outputs pasted
  • Commit: (Lovable fills in)

APP-012 — Acceptance runbook + formal deferral to TheCodex Block

  • Files created: docs/agent-platform/APP-012-ACCEPTANCE-RUNBOOK.md
  • Files modified: CHANGELOG.md, LOVABLE_RULES.md (one closing note added)
  • Pack closure: BP-AGENT-PLATFORM-PACK-001 formally closed. Six DB migrations + one unified UI amendment shipped (APP-001, APP-002-REVA, APP-002.5-REVA, APP-003-REVA, APP-004, APP-005, APP-009+010-UNIFIED). Four prompts deferred to TheCodex Block (APP-006, APP-006-REVA, APP-007, APP-008) on Frame+Settings architecture grounds. One dependent prompt deferred (APP-011 — needs run ledger data).
  • Rules added/updated this pack: SR-006 (codified), SR-007 (Frame+Settings, codified during Kyle debug), SR-010 (audit-before-build), Pitfall #11 (GRANT ordering, reinforced), Pitfall #12 (filename auto-generation), Serial System (canonical mbo_set_serial_tg), GRANT and authenticated-write policy subsection under SR-006, prefix registry updated with MEM and APPK
  • Findings queued for BP-RLS-FOLLOWUP-001: 8 items, none blocking
  • Architecture documents ratified: WD-LOVABLE-001 (Winding Down Lovable), SU-WORLDPORT-001 (Spinning Up TheCodex Block) — both incorporate four-pillar North Star foundation
  • Commit: (Lovable fills in)

APP-009+010-UNIFIED — Agents UI amended for SR-006 platform-global visibility

  • Files modified: src/routes/agents.index.tsx (SR-006 dual-clause query), src/routes/agents.$agentId.tsx (Prompt Packs + Memory tabs), CHANGELOG.md
  • Files created: none
  • Schema migrations: none
  • Architectural decisions: APP-009 (read-side server function layer) COLLAPSED INTO APP-010 (UI). Reasoning: existing codebase uses direct Supabase + React Query for reads, not createServerFn (which is used for writes only per agentWrites.functions.ts). Adding a new read-side server function layer would create an inconsistent pattern. Frame+Settings (SR-007) honored — UI renders platform-global agents identically to workspace-scoped agents.
  • Visibility: Kyle, Aria, Scanner, Schema Auditor, Regression Watcher now appear in /agents list per SR-006 dual-clause query
  • New tabs: Prompt Packs (queries agent_prompt_packs by agent_id), Memory (queries agent_memory_entries by agent_id) — both with empty states; agent_runs and agent_tool_calls panels DEFERRED to TheCodex Block
  • Rules cited: SR-006, SR-007, SR-010
  • Verification: build · typecheck · visual confirmation of Kyle's row and PROMOTED prompt pack
  • Commit: (Lovable fills in)

APP-005 — agent_prompt_packs registry — STRUCTURALLY COMPLETE

  • Files created: supabase/migrations/<lovable-auto-generated>_agent_prompt_packs.sql (prompt-specified 20260615000105_agent_prompt_packs.sql per Pitfall #12), docs/prompts/agents/kyle/v1/SYSTEM_PROMPT.md
  • Files modified: LOVABLE_RULES.md (prefix registry adds APPK), CHANGELOG.md
  • Schema migrations: creates agent_prompt_packs (APPK serial via mbo_set_serial_tg, DRAFT/SHADOW/CANARY/PROMOTED/DEPRECATED/ARCHIVED lifecycle with partial unique index for one PROMOTED per family, SR-006 RLS, Pitfall #11 GRANT ordering, Realtime publication, Kyle v1 PROMOTED backfill)
  • Rules update: LOVABLE_RULES.md prefix registry documents APPK (Serial System rule line 121 enforcement caught by Lovable rules-conflict gate — exactly what that gate is for)
  • Serves platform-global agents: Kyle, Aria, Scanner, Schema Auditor, Regression Watcher
  • Coexistence: independent of prompts (89 rows, Brian work queue), prompt_pack_documents (3 rows, PACK serial), prompt_pack_items (0 rows, extraction bridge) — disambiguation in migration comment
  • Rules cited: SR-006, Serial System (incl. prefix registry), Pitfall #11, Pitfall #12, SR-010
  • Verification: V1-V6 and V8 passed. V7 deferred (mbo_serial_counters permission in test environment — index structurally correct per V3). Lint failure is pre-existing repo-wide (~9,055 errors, queued as finding for BP-RLS-FOLLOWUP-001, not introduced by APP-005)
  • New findings queued for BP-RLS-FOLLOWUP-001: (1) mbo_set_serial_tg produces {PREFIX}-GLOBAL-{YYYYMMDD}-{SEQ3} not {PREFIX}-{YYYYMMDD}-{SEQ3} (rules doc wrong format — Kyle's APPK serial confirms 'GLOBAL' segment is present); (2) V7 partial-unique-index enforcement could not be tested due to mbo_serial_counters role permission; (3) Lint gate has been non-functional through APP-001 through APP-005 — 9,055 errors with 8,974 auto-fixable suggesting missing .eslintignore or out-of-date --fix
  • Commit: (Lovable fills in)

APP-004 — agent_memory_entries pgvector table

  • Files created: supabase/migrations/<auto-generated-by-lovable>_agent_memory_entries.sql (prompt-specified filename was 20260615000104_agent_memory_entries.sql; Lovable Cloud migration service rewrote per Pitfall #12 below)
  • Files modified: CHANGELOG.md, LOVABLE_RULES.md
  • Schema migrations: creates agent_memory_entries — MEM serial via mbo_set_serial_tg unified pattern, vector(1536) embedding, HNSW cosine index (m=16, ef_construction=64), 4 B-tree indexes, SR-006-compliant RLS (ws_read/ws_write/ws_update, no svc_all, no DELETE), supabase_realtime publication membership, search_agent_memory SECURITY INVOKER similarity search function
  • Serves platform-global agents: Kyle, Aria, Scanner, Schema Auditor, Regression Watcher (per SR-006 registered list)
  • Rules cited: SR-006 (full section), Serial System (canonical mbo_set_serial_tg), Pitfall #11 (GRANT ordering), SR-010 (discovery complete)
  • Discovery finding 1 — anon ACL behavior: Lovable Cloud sets permissive default ACLs at the public schema level. anon/authenticated/service_role receive full DML on every new public table by default. Explicit GRANTs in migrations are decorative in this environment. RLS is the sole access gate. SR-006's "NO grant to anon" rule is enforced via RLS policy absence (no policy covering anon → RLS rejects anon), not via GRANT absence. Codified in this prompt as a note appended to SR-006's GRANT subsection.
  • Discovery finding 2 — filename auto-generation: Lovable Cloud migration service auto-generated the filename rather than using the prompt-specified name. This is the second occurrence (APP-003-REVA was the first). Codified in this prompt as new Pitfall #12.
  • Verification: build · lint · typecheck · V1/V2/V3/V4/V6/V7 verbatim outputs verified · V5 explained via has_table_privilege audit (Check 4 returned all true)
  • Commit: (Lovable fills in)

APP-003-REVA — Enrich agent_tool_calls additively

  • Files created: supabase/migrations/20260614214658_d485957c-a6e0-4194-a464-c6474c9c440f.sql
  • Files modified: CHANGELOG.md
  • Schema migrations: added run_id, risk_class, proposed_action_id, lifecycle_status, result_summary, completed_at, and retry_count to agent_tool_calls; added four partial indexes; enabled Realtime
  • Edge functions: none
  • Verification: all seven additive columns present; four idx_atc_* indexes present; Realtime publication confirmed; all 37 existing rows retain serials
  • Commit: feat: APP-003-REVA agent_tool_calls additive enrichment (TCALL convention honored)
  • Notes: Option A investigation replaced the pack's proposed ATC prefix with the established production TCALL / TCALL-GLOBAL convention. The existing set_serial trigger calling mbo_set_serial_tg('TCALL') remains authoritative; no serial column, backfill, function, trigger, or toolkit auto-derivation work was added.

FIX-KYLE-COMPOSIO-ENV — Make optional Kyle environment values non-fatal

  • Files created: none
  • Files modified: supabase/functions/kyle-composio/index.ts, CHANGELOG.md
  • Outcome: COMPOSIO_USER_ID falls back to user_0qev3c; missing KYLE_MBO_API_KEY returns a structured configuration error before the MBO API fetch.
  • Commit: fix: FIX-KYLE-COMPOSIO-ENV guard optional Kyle environment values

FIX-KYLE-FUNCTION-INVOKE — Use authenticated function client for Kyle calls

  • Files created: none
  • Files modified: src/lib/agentChat.functions.ts, CHANGELOG.md
  • Outcome: kyle-composio and kyle-session-distill are invoked through the authenticated backend function client already in scope.
  • Commit: fix: FIX-KYLE-FUNCTION-INVOKE route Kyle calls through functions.invoke

SEC-007 — Make vault-documents private and workspace-scoped

  • Files created: storage policy migration
  • Files modified: src/components/research-vault/VaultEntryModal.tsx, src/routes/research-vault.tsx, CHANGELOG.md
  • Schema migrations: replace public/owner-folder storage policies with workspace membership policies
  • Storage: vault-documents changed from public to private
  • Verification: policy state, private bucket state, signed attachment reads, build checks
  • Commit: fix: SEC-007 secure vault document storage
  • Notes: New uploads use the workspace UUID as the first path segment. Attachment reads now generate one-hour authenticated signed URLs instead of persisting or opening public URLs.

FIX-KYLE-WORKSPACE-CHECK — Allow platform-global agents through chat router

  • Files created: none
  • Files modified: src/lib/agentChat.functions.ts, CHANGELOG.md
  • Schema migrations: none
  • Edge functions: none
  • Verification: build · lint · typecheck · Step 4 outcome
  • Commit: fix: allow platform-global agents through chat router (Kyle workspace mismatch)
  • Notes: The workspace check at src/lib/agentChat.functions.ts rejected agents with workspace_id IS NULL, blocking the canonical platform-global Kyle (seeded 2026-06-11). The thrown "Workspace mismatch" error surfaced in the chat UI as the misleading "Edge Function returned a non-2xx status code" because the server function threw before invoking the edge function. This is the actual root cause of Kyle returning errors since approximately 2026-06-11. The three code fixes shipped earlier today (model ID, provider revert, supabaseAdmin elevation) were all correct in isolation but addressed downstream paths that were never reached due to this earlier gate. SR-006 exemption (LOVABLE_RULES.md) already permits platform-global agent identities; this fix brings the chat router into alignment.

FIX-SEC-003-KYLES-SESSIONS-RLS-REVB — Replace permissive kyles_sessions policy with workspace-scoped policies

  • Files created: supabase/migrations/20260614154913_d27c91a4-f409-4b2f-a678-df273cb48812.sql
  • Files modified: CHANGELOG.md
  • Schema migrations: replace_kyles_sessions_permissive_policy
  • Edge functions: none (service_role bypass preserved by default; kyle-session-distill unchanged)
  • Verification: pre-state captured, migration applied, post-state confirmed, service-role bypass verified, security scan re-run, finding #3 resolved; cross-workspace isolation blocked because the database has only one workspace membership and no second-workspace test identity
  • Commit: fix(sec): replace permissive kyles_sessions RLS policy (finding #3 of 5)
  • Notes: Finding #3 (kyles_sessions_public_access) was the only finding addressed in this session. Per the convention established in commit 8f0543b earlier today, no service-role policy was created — service_role bypasses RLS by default in Supabase. The other 4 findings (#1 can_write_workspace_member_escalation, #2 email_ingest_body_exposure, #4 tenants_sensitive_contact_data, #5 workspace_accounts_credentials_exposure) are deferred to a future BP-RLS-HARDENING-PACK-001. The fresh scan also surfaced additional pre-existing error-level findings outside this prompt's scope; none were changed.

FIX-KYLE-MODEL-ID — Restore Kyle from non-2xx errors

  • Files created: none
  • Files modified: supabase/functions/kyle-composio/index.ts, supabase/functions/kyle-briefing-generate/index.ts, supabase/functions/kyle-session-distill/index.ts, CHANGELOG.md
  • Schema migrations: none
  • Edge functions: kyle-composio, kyle-briefing-generate, kyle-session-distill (all three redeployed)
  • Verification: build (no behavior change beyond the model identifier); Kyle returns 2xx after redeploy
  • Commit: fix: Kyle model identifier (claude-sonnet-4-6 → claude-sonnet-4-5) restores Anthropic 2xx responses
  • Notes: All three Kyle edge functions defaulted MODEL to "claude-sonnet-4-6", which is not a valid Anthropic model identifier. Anthropic returns 404 model_not_found on every invocation, surfacing in MBO as "Edge Function returned a non-2xx status code". Kyle has been broken since whatever commit introduced 4-6 (last successful agent_tool_calls write was 2026-06-10). Fix is a one-line default change in each of the three files to "claude-sonnet-4-5", matching the value backfilled into public.agents.model by APP-001. If a custom AGENT_MODEL env var is set in the Lovable console, it overrides the default — verify it is also a valid identifier or unset it. This fix is independent of the Agent Platform Execution Pack and unblocks Prompt 7 (APP-006-REVA kyle-composio wrapper) running against a healthy Kyle.

FIX-KYLE-INVOKE-AUTH — Elevate Kyle invocations to service-role from server function

  • Files created: none
  • Files modified: src/lib/agentChat.functions.ts, CHANGELOG.md
  • Schema migrations: none
  • Edge functions: none (no edge function code changed; kyle-composio and kyle-session-distill keep their requireServiceRole gates)
  • Verification: Kyle chat path returns 2xx; kyle-composio edge function logs show successful invocation; user-JWT-bound flow no longer reaches the service-role gate.
  • Commit: fix: elevate kyle-composio + kyle-session-distill invocations to service-role admin client
  • Notes: kyle-composio (L150) and kyle-session-distill both call requireServiceRole(req), which demands an exact Authorization: Bearer <SERVICE_ROLE_KEY> match. The supabase client from requireSupabaseAuth middleware is bound to the user's JWT and fails this check, returning 401 (surfacing in MBO as "Edge Function returned a non-2xx status code"). Predecessor commit PROMPT-MBO-20260613-KYLEFIXC-001 switched from raw process.env.fetch to supabase.functions.invoke() (correct move to server-side) but did not elevate the client — incomplete fix. This change imports supabaseAdmin from src/integrations/supabase/client.server.ts and switches both Kyle-related invocations to use it. The trust boundary remains sendAgentMessage's requireSupabaseAuth middleware; the elevation happens server-side, on the user's behalf, after auth has succeeded. No edge function code or auth model changed. Surgical unblock; full architectural cleanup (settings-driven kyle-composio, settings UI, explicit acting-as claim) lands in BP-KYLE-CONFIG-PACK-001 per SR-007.

RULES-SR-007-FRAME-SETTINGS — LOVABLE_RULES.md adds Frame + Settings architecture (SR-007)

  • Files created: none
  • Files modified: LOVABLE_RULES.md, CHANGELOG.md
  • Schema migrations: none
  • Edge functions: none
  • Verification: docs-only change; no build impact
  • Commit: docs: LOVABLE_RULES.md — SR-007 Frame + Settings architecture principle
  • Notes: Codifies the architectural principle Brian articulated 2026-06-14: agents are frames, their power is configuration, never code. Model provider, model identifier, system prompt, tool broker, allowed tools, risk policy, MBO entity access, and approval thresholds all live in database settings. Adding/retiring a provider is a row change, never a code change. New section under ARCHITECTURE LAW with implementation contract for edge functions, settings UI contract, violation history (APP-001 break), and enforcement guidance for Council review. This rule will land alongside BP-KYLE-CONFIG-PACK-001 (post-Agent-Platform-Pack) which builds the settings UI and refactors kyle-composio to read from rows instead of constants.

FIX-KYLE-PROVIDER-REVERT — Restore Kyle's provider = 'composio'

  • Files created: supabase/migrations/20260614142100_fix_kyle_provider_revert.sql
  • Files modified: CHANGELOG.md
  • Schema migrations: 20260614142100_fix_kyle_provider_revert.sql
  • Edge functions: none
  • Verification: one-row UPDATE on canonical Kyle agent row; idempotent
  • Commit: fix: revert Kyle's agents.provider to 'composio' (APP-001 break)
  • Notes: APP-001 set Kyle's provider to 'anthropic' (intent: model inference vendor). Five existing production code paths read provider as the tool-broker selector and filter on provider = 'composio' — src/lib/agentChat.functions.ts (lines 194 and 416), src/routes/kyle.tsx (line 61), src/components/dashboard/KyleActivityStrip.tsx (line 26), src/components/agents/KyleLauncher.tsx (line 50). After APP-001, all five surfaces stopped resolving Kyle, surfacing in MBO as "Edge Function returned a non-2xx status code". Minimum unblock for the in-flight pack: revert provider to 'composio'. Keeps model='claude-sonnet-4-5', kind='superagent', is_council_seat=true, tool_policy, and config exactly as APP-001 set them. The proper architectural cleanup (provider semantics, settings-UI, settings-driven kyle-composio refactor) lands in the post-pack follow-up BP-KYLE-CONFIG-PACK-001.

APP-002.5-REVA — Reconciliation note: agent_runs vs agent_chat_messages

  • Files created: docs/agent-platform/APP-002.5-RECONCILIATION.md
  • Files modified: CHANGELOG.md
  • Schema migrations: none
  • Edge functions: none
  • Verification: build · lint · typecheck
  • Commit: (Lovable fills in)

APP-002-REVA — agent_runs sibling table

  • Files created: supabase/migrations/20260614133634_4dc149f7-dc20-4811-9d78-41d9504dedf1.sql
  • Files modified: CHANGELOG.md
  • Schema migrations: created public.agent_runs as the execution-span sibling to agent_chat_messages, with SR-006 nullable workspace scoping, ARN serial generation, lineage, timing, outcome, cost, indexes, RLS, grants, and realtime publication
  • Edge functions: none
  • Verification: migration applied cleanly · generated types include agent_runs · build ✓ · lint blocked by pre-existing Prettier errors · typecheck blocked by pre-existing BlockDrawer.tsx error
  • Commit: feat: APP-002-REVA agent_runs sibling table
  • Notes: arn_svc_all was intentionally omitted because the service role bypasses RLS; grants appear immediately after CREATE TABLE per Pitfall #11.

APP-001 — agents table enrichment

  • Files created: supabase/migrations/20260614130439_8330ecab-5f54-4df9-8789-cb661b9c91ca.sql (prompt specified 20260615000101_agents_enrichment.sql; Lovable migration tool auto-generated timestamp+UUID filename)
  • Files modified: CHANGELOG.md
  • Schema migrations: agents table — added kind, is_council_seat, model, prompt_pack_serial, tool_policy, config; created agents_slug_workspace_unique and agents_slug_global_unique indexes; backfilled Kyle (superagent) and Council seats (council)
  • Edge functions: none
  • Verification: build ✓ · lint (8721 pre-existing prettier errors, 0 new) · typecheck ✓
  • Commit: feat: APP-001 agents table enrichment
  • Notes: The verbatim migration’s Council-seat backfill used and slug is null, but the 4 Council rows already had slugs assigned. A follow-up UPDATE was applied to complete the backfill for Aria, Scanner, Schema Auditor, and Regression Watcher.

RULES-PITFALL-9-PGVECTOR — LOVABLE_RULES pitfall #9 updated

  • Files created: none
  • Files modified: LOVABLE_RULES.md, CHANGELOG.md
  • Schema migrations: none
  • Edge functions: none
  • Verification: docs-only change; no build impact
  • Commit: docs: LOVABLE_RULES pitfall #9 — pgvector status reflects live DB (post APP-000 discovery)
  • Notes: APP-000 discovery (2026-06-14) confirmed pgvector is installed (migration 20260524224146). Pitfall #9 previously stated "not yet installed" — that guidance is now stale. Rule updated to reflect production reality and provide forward guidance for APP-005 (agent_memory_entries) and any future vector-using migration.

APP-000 — Discovery and Reconnaissance

  • Files created: docs/agent-platform/APP-000-DISCOVERY.md
  • Files modified: CHANGELOG.md
  • Schema migrations: none
  • Edge functions: none
  • Verification: build · lint · typecheck
  • Commit: docs: APP-000 discovery and reconnaissance — Agent Platform pre-flight inventory

2026-06-13 — MBO-CODEX-STATUSBAR-001

  • Codex Console: replaced horizontal scrolling status bar with two-row pill grid. No overflow scroll. All fields visible at once.

2026-06-13 — CODEX-FIX-DISPATCH-NULL-REPO

  • Removed DEFAULT_REPO hardcoded fallback (was worldport-platform).
  • Dispatch form now reads repo_url from active site record.
  • Dispatch button disabled with clear error if site has no repo_url.
  • Closes silent wrong-repo dispatch on misconfigured sites.

2026-06-13 — PROMPT-MBO-20260613-KYLEFIXC-001

  • agentChat.functions: replace raw process.env fetch calls to kyle-composio and kyle-session-distill with supabase.functions.invoke(). Fixes: Unauthorized — service-role key required on every Kyle message.

2026-06-13 — PROMPT-MBO-20260613-KYLEFIXB-001

  • kyle-composio: revert KYLE_MBO_API_KEY and COMPOSIO_USER_ID from requireEnv() back to safe Deno.env.get() fallbacks — prevents cold-start crash when custom secrets are absent. Restores graceful degradation guard on callMboApi.

2026-06-13 — PROMPT-MBO-20260613-KYLE-FIX-A-SITUATIONAL-AWARENESS

  • Kyle now receives workspace-level context when opened outside a site scope. Previously his only knowledge was the base system prompt and a 6-hour-stale WorldPort brief — he had no awareness of the current route, what sites exist, open incidents/tasks across the workspace, or code health state.
  • Added current_route to ChatInput schema and propagated it through AgentChatInterface, KyleLauncher, and the /kyle route. Both surfaces now pass pathname + search params so Kyle knows where the operator is.
  • Closed KYLE-MED-006: /kyle route now reads ?site= from URL params and passes siteId to AgentChatInterface, matching KyleLauncher's behavior. Previously the route never extracted siteId at all.
  • New buildWorkspaceContextBlock helper in siteContext.server.ts mirrors the buildSiteContextBlock pattern but pulls workspace-wide state: sites list, cross-site incidents/tasks/prompts, code health by severity, and the operator's current route.
  • sendAgentMessage now calls buildWorkspaceContextBlock when site_id is absent. The remote wire field site_context_block is reused for both scopes; the local variable was renamed to contextBlock for clarity.
  • Closes audit KYLE-HIGH-001 (situational awareness gap, Option A) and KYLE-MED-006 (siteId launcher/route inconsistency).

2026-06-13 — PROMPT-MBO-20260613-KYLE-FIX-B-ENV-HARDENING

  • Promoted COMPOSIO_USER_ID from hardcoded constant to required env var in both kyle-composio and agent-action-execute. Closes audit KYLE-CRIT-003. The platform-wide identity is now rotatable via Supabase edge function secrets without a code change.
  • Promoted KYLE_MBO_API_KEY from optional (Deno.env.get(...) ?? "") to required (requireEnv(...)) in both files. Removes the silent-failure mode where the function deployed and returned per-request errors instead of refusing to start. Closes audit KYLE-CRIT-002.
  • Deleted now-unreachable !KYLE_MBO_API_KEY null-checks (both files).
  • Operational pre-condition: both env vars (COMPOSIO_USER_ID, KYLE_MBO_API_KEY) must be set in the Supabase project before redeploy. Current Composio user ID in use: user_0qev3c (preserved for backward compat; rotate via env when ready).

2026-06-13 — PROMPT-MBO-20260613-KYLESESSGRAPH-001a (Kyle Sessions Graph Wire)

  • Added agent_id UUID FK from kyles_sessions → agents(id) ON DELETE SET NULL. Preserves session archive integrity even if Kyle's agent row is ever removed.
  • Added idx_kyles_sessions_agent_id index on the new FK column.
  • Added COMMENT ON TABLE for kyles_sessions and agents — pg_description now carries canonical descriptions including the architectural note that Kyle is platform-global (workspace_id IS NULL) while sessions are workspace-scoped.
  • Added COMMENT ON COLUMN for all primary kyles_sessions fields (agent_id, serial, session_id, app_tags, decisions, topics, prompt_ids, summary, workspace_id). app_tags remains free-form pending operator reconciliation of the apps/blocks/topics split.
  • Added "agents" to kyle-api ALLOWED_ENTITIES as read-only. Write operations (insert/update/delete) on agents return 403 from kyle-api.
  • Backfilled agent_id on all 7 existing kyles_sessions rows with Kyle's UUID.
  • Supersedes PROMPT-MBO-20260613-KYLESESSGRAPH-001 (original assumed Kyle was workspace-scoped; corrected to platform-global per migration 20260611165854).
  • Enables: 5am CT nightly session push automation can now write graph- connected rows from day one.

2026-06-13 — AUTOLOOP-001 / AL-001 (Nightly Code Reader trigger)

  • Added the authenticated /api/public/code-reader-nightly scheduler route. At 03:30 UTC it inspects each active site's latest HEAD~1 GitHub diff when the head commit is under 24 hours old, reviews changed patches for concrete defects, and sends findings through the existing code-reader-dispatch ingestion path. Empty scans are dispatched too, preserving the audit trail.
  • This stage does not submit findings to Council or dispatch a writer; those wires remain deferred to later AUTOLOOP stages by operator decision.

2026-06-12 — CODEX-FIX-D (Council pr_title — intentional assignment)

  • Removed TODO(COUNCIL-3) comment at council-deliberate line 694-696. pr_title now intentionally uses vertical_slug — codex_jobs has no title column. vertical_slug is human-readable and always present. Closes COUNCIL-4. COUNCIL-3 (entity_serial) already resolved per Lovable pre-flight — not modified. Required before AUTOLOOP-001 AL-001 ships (Daily Letter reads pr_title).

2026-06-13 — CODEX-FIX-B (Service-role auth hardening)

  • Created _shared/requireServiceRole.ts — guard for internal-only edge functions. Callers must present the service-role key; user JWTs are rejected.
  • Applied the guard to kyle-briefing-generate, kyle-composio, and the real kyle-session-distill implementation.
  • council-deliberate auth guard was already present — not modified.
  • Closes audit C-3 / KYLE-CRIT-001. Required before AUTOLOOP-001 AL-001 ships.

2026-06-12 — ATLAS-FIX-A (vw_atlas_blocks grant)

  • Added GRANT SELECT ON public.vw_atlas_blocks TO authenticated, service_role. The ATLAS-RESTORATION-001 migration (20260612201200) created the view with security_invoker = true but omitted this grant — the only view in the codebase missing it. PostgREST returned 42501 permission denied on every authenticated read, causing the Atlas canvas at /atlas to render the error state. Closes audit C-1.

  • Applied SR-005 three-role REVOKE discipline to record_resolve_evidence, assert_resolved, and pre_council_resolve_batch (closes audit RPC-1, RPC-2).

  • Removed @ts-nocheck from src/integrations/agentos/sanitize.test.ts (closes audit L-4).

  • Added hand-maintained CouncilAgentSeat union type in src/lib/councilTypes.ts and applied at council-deliberate write sites; generated types remain untouched (closes audit SCHEMA-2).

Deferred (explicit decisions needed):

  • H-2/TYPES-1 (platform_deferred_findings cross-workspace UPDATE) — pending operator decision on workspace scoping.
  • L-1 (agent_dispatch_log RLS) — accepted design risk per existing comment.
  • L-2 (codex_jobs.builder CHECK constraint) — minor; defer.
  • SOT-1/SOT-2 — documentation + runtime investigation; not code fixes.

2026-06-12 — CODEX-FIX-H (UI polish & cleanup)

  • Realtime subscription for codex_jobs now skipped when the seeded job is already terminal (output_rendered=true or pr_status=failed), preventing channel leaks for completed_inline dispatches (closes audit M-1).
  • prStatusTone now returns 'success' for completed_inline and not_applicable_inline instead of neutral gray (closes M-2).
  • CHAT-1's cited record(...).catch(console.error) call was absent from the live agentChat.functions.ts tree, so that change was skipped and logged as a deferred drift finding rather than applied to a different call site.
  • Composio non-JSON responses now log diagnostic info (status + parse error) while still propagating a generic error to the user (closes CHAT-2).
  • DeferredFindingModal now throws when finding is null instead of silently resolving with undefined (closes audit DEFER-1).
  • worldportGraph.ts now includes Council tables (deliberations, votes, amendments, resolve_evidence) in the TS manifest (closes audit GRAPH-1).

2026-06-12 — CODEX-FIX-F (AddressBar resolve guard)

  • AddressBar.submit() now returns early when SERIAL_RE.test() fails, preventing onResolve from firing on malformed serials. Previously the warning displayed but resolution still fired with the bad input, causing contradictory UX (closes audit RESOLVE-1, SOT-3).

2026-06-12 — CODEX-FIX-E (Site audit filter)

  • Fixed PostgREST .not("in", ...) filter syntax in siteAudit.core.ts at the task scan (line 137) and incident scan (line 165). Double-quoted values inside the parens caused the exclusion filter to match no rows, so done/cancelled tasks and resolved/closed incidents were being counted in site health scores. All site health scores were silently wrong (closes audit AUDIT-1).

2026-06-12 — CODEX-FIX-D (Council write correctness)

  • Council platform_state rows now emit the canonical CJOB serial from codex_jobs.serial instead of CJOB-<uuid>; previously these rows were unresolvable via public.resolve() (closes audit COUNCIL-4).
  • codex_jobs.pr_title is not present in the live schema, so COUNCIL-3 is documented as a deferred schema_drift finding and the vertical_slug fallback is explicitly marked with a follow-up TODO.
  • serialBySeat.get(seat)! non-null assertion replaced with an explicit invariant throw; protects persistVotes from silent TypeError (closes audit COUNCIL-6).
  • Added 'not_applicable_inline' to codex_council_status enum and dispatch jobs set it for inline builders so council_status no longer hangs at 'pending' forever for claude_code / perplexity jobs (closes audit H-4).
  • council-deliberate now throws instead of soft-approving when GITHUB_TOKEN is unset; it refuses to deliberate against an empty diff (closes audit M-6).

2026-06-12 — CODEX-FIX-C (Code Reader + dispatch enums)

  • Extended agent_dispatch_action enum with 'code_reader_scan' and 'canceled'.
  • Extended agent_dispatch_actor enum with 'code_reader'.
  • code-reader-dispatch now writes the dispatch log with valid enum values; the silent invalid_text_representation error is closed. No previous scans were logged (closes audit DISPATCH-1, SCHEMA-3).
  • code-reader-dispatch now surfaces dispatch-log insert errors via console instead of silently swallowing them.
  • cancelCodexJob writes action 'canceled' to the dispatch log instead of 'codex_dispatched' (closes audit M-3).
  • metadata.commit_sha now validated through the DispatchBody schema instead of an unsafe raw-body cast (closes audit DISPATCH-3).
  • upsert_code_health_issue now follows SR-005 three-role REVOKE discipline (closes audit DISPATCH-2).

2026-06-12 — CODEX-FIX-B (Security)

  • council-deliberate now requires Bearer COUNCIL_DELIBERATE_SECRET; previously any unauthenticated POST with valid job + workspace UUIDs could fire a full Council deliberation pass (closes audit C-3).
  • council-deliberate now verifies the caller's workspace_id contains the job's created_by user via workspace_members; previously workspace_id was accepted verbatim from the request body with no ownership check (closes audit COUNCIL-2).
  • codex-pr-completed updated to send the new COUNCIL_DELIBERATE_SECRET when invoking council-deliberate.
  • cancelCodexJob now scopes the update by created_by = context.userId, preventing cross-user cancellation (closes audit H-1).
  • Operator action required before this code runs: configure COUNCIL_DELIBERATE_SECRET in Edge Function secrets. Until configured, every Council invocation returns 500 server_misconfigured.

2026-06-12 — CODEX-FIX-A (Dispatch prompt + builder gating)

  • Inline builders (claude_code, perplexity) now receive the user's task_directive in their prompt (closes audit C-1).
  • GITHUB_DISPATCH_TOKEN check moved inside the codex_actions builder path so inline builders no longer 500 when the token is unset (closes audit C-2).
  • site_context (incidents, tasks, known issues, last prompt serials, health score) now forwarded to the builder prompt instead of being silently dropped (closes audit C-5).
  • DEFAULT_REPO in src/lib/codex.functions.ts is now read from CODEX_DEFAULT_REPO env var with the existing string as fallback (closes audit H-5).

2026-06-12 — TEST-INFRA-WIRE-001

  • Declared vitest, @playwright/test, jsdom, and @testing-library/* in devDependencies (previously imported but not declared; tests ran only because Lovable's runtime included them).
  • Added test, test:watch, test:ui, test:e2e, test:e2e:install scripts.
  • Created vitest.config.ts (scoped to src/**/*.{test,spec}.{ts,tsx}).
  • Created playwright.config.ts (scoped to tests/**/*.spec.ts).
  • No test files modified; no new tests written; E2E suite still requires live dev server + auth env vars to actually run.

2026-06-12 — WP-CODEX-FULLWIRE: Synchronous research and experiment builders

  • Wired Perplexity and Claude Code builders to return inline output, with atomic completed_inline job completion.
  • Extracted deferred-finding classification into a shared edge utility and applied it to both synchronous output and PR completion paths.
  • Added completed_inline to codex_pr_status; regenerated frontend types already include the new terminal value.
  • Files touched: supabase/functions/_shared/deferred-scan.ts, supabase/functions/dispatch-codex-job/index.ts, supabase/functions/codex-pr-completed/index.ts, migration, CHANGELOG.md.
  • Serial: PROMPT-WP_CODEX_FULLWIRE-20260612-0001.

2026-06-12 — CODEX-DISPATCH-SPEC-002a: Inline output + deferred findings

  • Added inline Codex output states with realtime job updates, markdown rendering, copy/clear, PR, Council, and failure actions.
  • Added the platform-global deferred findings registry, exact/fuzzy classifier, deterministic clustering, context sweep, console grouping, job counts, modal actions, and sidebar cluster badge.
  • Exact and fuzzy dedup use an atomic backend helper: occurrence counts increment on every hit, while source_jobs appends only when NOT (job_id = ANY(source_jobs)), preventing duplicate job IDs on retries.
  • Updated codex-pr-completed and dispatch-codex-job; dual-writer review remains deferred to 002b.
  • Serial: BP-CODEX-DISPATCH-SPEC-20260612-002a.

ATLAS-RESTORATION-003 — Pack 11 closeout

  • Files modified: docs/RETROSPECTIVE_ATLAS_RESTORATION_2026-06-12.md (new), docs/ATLAS_RESTORATION_CLOSED.md (new), docs/FOUNDATION_ROADMAP.md (pack 11 row added), CHANGELOG.md.
  • Schema migrations: vw_atlas_blocks view registered as platform artifact in pages (artifact_type=view, artifact_status=live, block_id=worldport); SWEEP-ATLAS-INTENT-DRIFT-001 closed in platform_sweeps with closed_at, closed_in_pack, resolution_notes; the AR-003 pages row for SWEEP-ATLAS-INTENT-DRIFT-001 flipped to artifact_status=deprecated; SR-008.application_count incremented 1 → 2 with notes append.
  • Verification: ✓ migration applied · smoke passed (vw_atlas_blocks registered, sweep closed + page row deprecated, SR-008.application_count=2, total platform rows=15, is_compliant=true).
  • Tag: atlas-restoration-closed-2026-06-12 (annotated; application reserved for Computer/Brian per repository state boundary).
  • Commit: Lovable + Computer co-applied; tag pushed by Computer.

2026-06-12 — ATLAS-RESTORATION-002: Visual Atlas + Q3-C artifact drawer

  • Ported the seven-file Atlas visualization scaffold into src/components/atlas/, reskinned to MBO tokens, with layer/vertical/wave/source filter pills derived directly from view data.
  • Replaced the /atlas placeholder with the 66-block React Flow architecture map and responsive mobile list.
  • Added the Q3-C block drawer with grouped iconned artifact rows, MBO status chips, and separate Marketplace navigation.
  • Added /atlas/$type/$name focused-artifact routing so all 14 AR-003 canonical paths resolve and reopen Atlas with the matching artifact highlighted.
  • No schema changes. Serial: PROMPT-ATLAS_RESTORATION-20260612-0002.

2026-06-12 — ATLAS-RESTORATION-001: Atlas projection and route scaffold

  • Created public.vw_atlas_blocks, a security-invoker projection of all 66 canonical blocks with dependency-slug arrays for the visual Atlas.
  • Installed @xyflow/react, react-window, and framer-motion for the ATLAS-002 visualization build.
  • Added the /atlas loading route and restored Atlas to the Operate navigation in src/components/worldport/Shell.tsx.
  • Inline SR-004 smoke passed: 66 projected rows, canonical ros dependencies, and is_compliant=true.
  • Serial: PROMPT-ATLAS_RESTORATION-20260612-0001.

ARTIFACT-REGISTRY-005 — Pack 10 closeout

  • Files modified: docs/RETROSPECTIVE_ARTIFACT_REGISTRY_2026-06-12.md (new), docs/ARTIFACT_REGISTRY_CLOSED.md (new), docs/FOUNDATION_ROADMAP.md (pack 10 row added, body bullets updated), CHANGELOG.md.
  • Schema migrations: SR-008 promotion CANDIDATE → ACTIVE in platform_standing_rules via UPDATE. application_count unchanged at 1 (promotion is governance, not application). promoted_at set to now(). Notes appended with promotion narrative.
  • Verification: ✓ migration applied · smoke passed (status='active', application_count=1 unchanged, total active rules=8, is_compliant=true).
  • Tag: artifact-registry-closed-2026-06-12 (annotated; application reserved for Computer/Brian per repository state boundary).
  • Commit: Lovable + Computer co-applied; tag pushed by Computer.

2026-06-12 — ARTIFACT-REGISTRY-004: SR-008 candidate filing

  • Filed SR-008 — Universal Artifact Registration in platform_standing_rules with candidate status and the ratified wording, rationale, and exclusions.
  • Recorded AR-003 as the first application with application_count=1, a populated first_applied_at, and no promotion timestamp.
  • Inline SR-004 smoke completed with exactly one SR-008 row, the required candidate shape, and is_compliant=true.
  • Files touched: supabase/migrations/20260612193654_artifact_registry_004_sr008_candidate.sql, CHANGELOG.md.
  • Serial: PROMPT-ARTIFACT_REGISTRY-20260612-0006.

2026-06-12 — ARTIFACT-REGISTRY-003: Pack 10 canonical artifact rows

  • Seeded 14 platform-scoped artifact rows into pages, all attributed to the worldport core block and addressed at /atlas/{artifact_type}/{artifact_name}.
  • Registered 3 tables, 1 RPC, 1 cron, 1 view, 2 guards, and 6 sweeps; 12 are live, G15 is planned, and the closed Aria seat-instantiation sweep is deprecated.
  • Inline SR-004 smoke completed with all 14 rows, the expected type distribution, correct platform scope and block attribution, matching Atlas paths, and is_compliant=true.
  • Files touched: supabase/migrations/20260612191213_49ceb811-a11e-4847-894e-dd30ce82d66d.sql, CHANGELOG.md.
  • Serial: PROMPT-ARTIFACT_REGISTRY-20260612-0005.

2026-06-12 — ARTIFACT-REGISTRY-002: Canonical block catalog + 63-block seed

  • Migrated the 8 legacy blocks_catalog rows to canonical slugs and layer vocabulary, including routeosros and esignossignos.
  • Added blocks_catalog_layer_valid, seeded all 63 canonical blocks, and fanned 12 valid fed_by relationships into block_dependencies; 5 documented dangling references were skipped.
  • Inline SR-004 smoke completed with 66 total blocks, 16 total dependency edges, legacy slugs removed, canonical layers enforced, and is_compliant=true.
  • Files touched: supabase/migrations/20260612184645_45567b00-52f7-42b7-a114-90719ca035af.sql, CHANGELOG.md.
  • Serial: PROMPT-ARTIFACT_REGISTRY-20260612-0004.

2026-06-12 — ARTIFACT-REGISTRY-001a: Universal artifact registry schema

  • Extended public.pages with artifact_type, artifact_status, artifact_name, artifact_subtype, and block_id, with schema comments defining the locked Q2b/Q3/Q4/Q5 semantics.
  • Backfilled existing page rows, relaxed workspace/site ownership for platform artifacts, and added four integrity constraints, two platform RLS policies, and four lookup indexes.
  • Corrected the superseded 001 comment syntax by using single PostgreSQL string literals; the migration and inline SR-004 smoke completed successfully with is_compliant=true.
  • Serial: PROMPT-ARTIFACT_REGISTRY-20260612-0002.

2026-06-12 — OPERATE-VISIBILITY-006: Pack 9 Closeout (SR-007 ACTIVE)

Pack 9 closed. Six sub-prompts. Zero retries. ~2 hours from ADR to closeout.

  • SR-007 (operational visibility) promoted CANDIDATE → ACTIVE in public.platform_standing_rules registry with 5 cited applications (OV-001 governance, OV-002 signals, OV-003 scheduler crons, OV-004 incidents, OV-005 daily letter). Total active Standing Rules: 7 (SR-001 through SR-007).
  • Retrospective document filed at docs/RETROSPECTIVE_OPERATE_VISIBILITY_2026-06-12.md (serial RETRO-OPERATE_VISIBILITY-20260612-0001). Documents: pack constants, SR-007 promotion case, three emerged patterns (registry+view+UI, idempotent upsert + auto-resolve, apply-time auth hardening as discipline), four named honest limits (no realtime, no manual cron trigger, no email digest, marginal G14 detection inefficiency in OV-005), what ships next (Pack 10 ATLAS-RESTORATION, PULSE-000 parked).
  • FOUNDATION_ROADMAP.md updated: Pack 9 row added with ✅ COMPLETE, §11 Foundation Closeout amended with pack 9 paragraph, §10 cross-references extended with retro + marker + 3 new registries.
  • Marker file docs/OPERATE_VISIBILITY_CLOSED.md created. Annotated git tag operate-visibility-closed-2026-06-12 to be applied by Brian locally (same operational boundary as foundation-closed-2026-06-12).
  • The "no hunting, no SQL" property is now structurally enforced through pack discipline. Every future pack that writes operational rows is bound by SR-007 to ship UI in the same pack or file a tracked deferral.

Apply-time hardening discipline note: five distinct events across foundation + pack 9 where Lovable tightened auth/safety surfaces beyond prompt specification (TH-001 three-role REVOKE, TH-003 inline preservation, TH-007 Part 3 search_path, OV-003 service-role tightening, OV-004 continuation). Worth a future SR-008 candidate filing.

What ships next:

  • ATLAS-RESTORATION (Pack 10) — visual Block/Vertical interactive tool restoration to the now-freed /atlas slot. Closes SWEEP-ATLAS-INTENT-DRIFT-001. Salvage from BP-WP-SITE-ATLAS-VIZ-001 scaffold (~70% reusable).
  • PULSE-000 — parked. 30-day operational data prerequisite started with 2026-06-12 03:00 UTC nightly batch. Reopens ≥2026-07-12.

2026-06-12 — OPERATE-VISIBILITY-005: Daily Letter Platform Overnight section

  • New server function composePlatformOvernight (src/lib/platformOvernight.functions.ts) — reads compliance check, G14 silence (25h window), last nightly_batch row, platform_incidents (open/opened-24h/auto-resolved-24h/top open), platform_sweeps (filed/closed/open), and get_platform_cron_jobs recent_runs to produce 2–4 narrative paragraphs matching the existing seedLetter voice. Same SR-005 posture as OV-003/004: requireSupabaseAuth + supabaseAdmin loaded inside the handler.
  • New component PlatformOvernightSection (src/components/letter/PlatformOvernightSection.tsx) — Activity-iconed card with is_compliant chip, optional G14 silence chip, paragraph stack, and a footer strip (generated time, last batch, open incidents, open sweeps).
  • /daily-letter now renders the Platform Overnight card directly under the workspace LetterView (Archive untouched). Page is otherwise unchanged. No schema migration.
  • Serial: PROMPT-OPERATE_VISIBILITY-20260612-0005. Fifth consecutive SR-007 application; pattern proven for OV-006 promotion retro.

2026-06-12 — OPERATE-VISIBILITY-004: platform_incidents + auto-elevate + auto-resolve

  • New table public.platform_incidents — platform-global incidents auto-elevated from platform_signals. Deterministic id md5(signal_kind || '|' || entity_serial) so incidents persist across hourly signal rollovers. SR-006 exempt (operational state registry).
  • New FK column source_signal_id references platform_signals(id) ON DELETE SET NULL; updated on each refresh to point at the latest matching signal.
  • New RPC public.refresh_platform_incidents() — idempotent upsert + auto-resolve. Allowlist: g14_silence → SEV2, compliance_failure → SEV2, cron_failure → SEV3 single / SEV2 if ≥3 distinct-hour failures in 24h. open_sweep and test_harness_attention stay signals-only.
  • Auto-resolve: incidents whose source signal condition no longer fires transition to status='auto_resolved' with auto_resolved_reason='signal_condition_cleared'. Recurrence reopens the incident.
  • New view public.vw_incidents_combined (security_invoker) — UNIONs workspace incidents and platform_incidents with a source_type discriminator.
  • /incidents page extended: new Source filter pill (All / Workspace / Platform), new "Refresh platform" header button calling refreshPlatformIncidents server fn, and a read-only Platform Incidents section above the existing workspace table. Workspace components (IncidentTable / IncidentDetail / IncidentModal / IncidentLinkChips) preserved verbatim.
  • /signals Recompute now chains refresh_platform_signalsrefresh_platform_incidents (signals must refresh first so incident elevation reads fresh state) and invalidates both caches.
  • SR-005 seventh application (three-role REVOKE on refresh_platform_incidents, service_role only — same hardening posture as OV-003).
  • Fourth SR-007 application: data layer + UI surface ship in same pack.
  • Inline migration smoke: RPC runs, idempotent on second call, view UNIONs cleanly, is_compliant=true post-apply.

2026-06-12 — OPERATE-VISIBILITY-003: Scheduler Platform Crons section

  • New RPC public.get_platform_cron_jobs() — read-only SECURITY DEFINER function surfacing cron.job + cron.job_run_details. Returns one row per job with last 5 runs as jsonb, computed last_success_at / last_failure_at timestamps, failures_last_24h count, and a uses_vault boolean derived from the cron command text. SR-005 + SECDEF-auth-leak guard: EXECUTE granted to service_role only (granting to authenticated trips the compliance check, as in OV-002).
  • New TanStack server function getPlatformCronJobs (src/lib/platformCrons.functions.ts) authorizes the caller via requireSupabaseAuth and invokes the RPC through supabaseAdmin.
  • /scheduler page wrapped in Tabs: "Operations" (existing user-created approval-gated jobs, preserved verbatim) and "Platform Crons" (new visibility surface).
  • New PlatformCronsPanel component renders active/inactive chip, vault chip, last 5 runs as a colored status strip, last success/failure relative timestamps, inline error message when the most recent run failed, and a failures_last_24h chip when >0. Refetch every 60s.
  • Read-only by design — manual trigger controls require their own ADR. Cron failures continue to auto-surface on /signals via OV-002's refresh_platform_signals (signal_kind=cron_failure).
  • Third SR-007 application: data layer (RPC) + UI surface (panel + tabs) shipped in the same pack.
  • Inline migration smoke verifies test-harness-nightly is visible and is_compliant=true post-apply.

2026-06-12 — OPERATE-VISIBILITY-002: Atlas → Signals rename + platform_signals layer

  • /atlas route renamed to /signals (route file, sidebar nav, Codex dialog labels). atlas_signals table is NOT renamed — its workspace-scoped semantics are correct; only the route was lying. /atlas slot is now free for ATLAS-RESTORATION pack 10.
  • New table public.platform_signals — platform-global signals layer (no workspace_id). Five signal kinds: test_harness_attention, g14_silence, open_sweep, cron_failure, compliance_failure. Deterministic id from md5(signal_kind || entity || time_bucket) (cron_failure also includes runid) for idempotent upsert. SR-006 exempt (operational state registry, no serial issuance).
  • New view public.vw_signals_combined — UNIONs atlas_signals (workspace) and platform_signals (platform) with source_type discriminator. security_invoker=true so workspace RLS is honored.
  • New RPC public.refresh_platform_signals() — idempotent upsert from 5 sources. SR-005 three-role REVOKE applied; service-role-only EXECUTE (granting to authenticated would trip the SECDEF-auth-leak compliance guard). Invoked from the new TanStack server function refreshPlatformSignals in src/lib/platformSignals.functions.ts, which authorizes the caller via requireSupabaseAuth and then calls the RPC with the admin client.
  • Auto-resolve logic: open_sweep signals whose underlying platform_sweeps row closes are marked resolved with reason=sweep_closed. OV-003/004/005 will add triggers for continuous refresh; OV-002 ships the pull model.
  • Signals page now shows mixed workspace+platform rows with a source_type chip and filter pills (All / Workspace / Platform). Dismiss button conditionally rendered only for workspace signals (platform signals auto-resolve).
  • Recompute button calls BOTH compute_atlas_signals (workspace) AND the refreshPlatformSignals server fn (platform) in parallel — one click refreshes both layers.
  • Second SR-007 application: new table shipped with UI surface in the same pack.
  • Inline migration smoke verifies: refresh runs cleanly, populates ≥5 rows (from OV-001's 5 open sweeps), view UNIONs correctly, second refresh is idempotent (no row stacking), is_compliant=true post-apply.

2026-06-12 — OPERATE-VISIBILITY-001: platform_sweeps + /governance UI

  • New table public.platform_sweeps — first durable home for sweep findings. Same registry pattern as platform_standing_rules and platform_properties (TH-007 Part 3). SR-006 exempt (configuration/registry table, no serial issuance).
  • Seeded with all 6 known foundation-era sweeps: ARIA-SEAT-INSTANTIATION (closed in TH-007 Part 1), ARIA-AMENDMENT (deferred), DISSENT-DETECTOR-COVERAGE (deferred), GRAPH-NODE-BACKFILL (investigating), PREFIX-EMPTY-AUDIT (open), ATLAS-INTENT-DRIFT (deferred to pack 10).
  • New route /governance — read-only UI surfacing three registries via tabbed interface: Standing Rules (6 ACTIVE + 1 CANDIDATE), Platform Properties (OWE-001 with 3 instantiations), Sweeps (5 open + 1 closed at first ship). Sidebar nav entry added under OPERATE with ShieldCheck icon.
  • First known SR-007 application: registries shipped at TH-007 Part 3 → UI shipped today. Same pack discipline that SR-007 promotion will require going forward.
  • Inline migration smoke verifies 6 sweep rows landed and is_compliant=true post-apply. SR-004 application (structural surface added with proactive smoke).
  • Mobile nav unchanged: MobileMenuSheet reads from shared NAV in Shell.tsx; MobileBottomNav has a fixed 4-slot taxonomy that intentionally excludes secondary OPERATE items.

2026-06-12 — TEST_HARNESS-007 Part 6: Foundation Closing Marker

Final sub-part of TH-007. Added docs/FOUNDATION_CLOSED.md — a 5-line human-discoverable marker pointing to the retrospective, roadmap, and the foundation-closed-2026-06-12 annotated git tag. No code, no migrations. Tag application (git tag -a foundation-closed-2026-06-12) is a stateful git operation reserved for Brian; the annotation body lives in TH007-PART6-CLOSING-COMMIT_PROMPT.md. Foundation now closed in every layer: structural (G14), narrative (retro + roadmap), and permanent (marker file + pending tag).

2026-06-12 — TEST_HARNESS-007: Foundation Closeout

Eight-pack foundation closed. 22-day arc. 151 migrations. Nine is_compliant-contributing structural guards active. The platform now structurally requires itself to observe itself.

Five sub-parts shipped today as part of TH-007:

  • Part 1 — Aria seat instantiation. SeatSlug union extended; seatAria() thin-approve stub added; INSTANTIATED_VOTING_SEATS updated to include aria. Resolves SWEEP-ARIA-SEAT-INSTANTIATION-001. Closes the loop on TH-002 scenario 7 (canonical_seat_drift).
  • Part 2 — G14 + G2 view-filter correction. New guard test_harness_scenario_drift (silence detector, 25-hour window) added to serial_compliance_check() as the ninth is_compliant-contributing guard. Same canonical reproduction also corrects a latent G2 misclassification bug where views exposing serial columns were flagged as unregistered tables. SR-003 application #10.
  • Part 3 — Standing Rules + Platform Properties registries. New tables public.platform_standing_rules and public.platform_properties. Six rules promoted to ACTIVE (SR-001-006); one CANDIDATE (SR-007). One named property (OWE-001 Observation Without Enforcement). Both tables are first known SR-007 application items pending UI in OPERATE-VISIBILITY pack 9.
  • Part 4 — Retrospective document. docs/RETROSPECTIVE_FOUNDATION_2026-06-12.md (serial RETRO-FOUNDATION-20260612-0001). ~3500 words across 11 sections including three honest-limit dimensions, sweep dispositions, patterns that worked, and what ships next.
  • Part 5 — Roadmap + STATE_OF_PLATFORM + CHANGELOG (this entry). Foundation closed in the canonical roadmap document; STATE_OF_PLATFORM points to retro and new registries; CHANGELOG records the closeout.

Next packs: OPERATE-VISIBILITY (Pack 9) wires UI to existing operational data and closes SR-007. ATLAS-RESTORATION (Pack 10) restores the visual Block/Vertical interactive tool to its proper nav slot. PULSE-000 (parked) opens after 30 days of operational TEST_HARNESS data accumulates.

Unreleased

  • TEST_HARNESS-007 Part 2 (G14 silence detector + latent G2 view-filter fix): PROMPT-TEST_HARNESS-20260612-0007-PART2-REV2. Single migration rewriting public.serial_compliance_check() (SR-003 application #10 — full body verbatim from 20260612003434_*.sql lines 6-294, five minimal additions marked NEW). G14 (test_harness_scenario_drift): silence detector requiring each of the 4 required scenario_kinds (synthetic_council, drift_simulation, resolver_prefix_sweep, nightly_batch) to have at least one council_test_runs row within the last 25 hours (24h cron cadence + 1h slack). If any kind goes silent, the corresponding finding row surfaces and is_compliant flips false. G14 is the 9th is_compliant-contributing guard (G2, G7, G8/G9, G10, G11, G12, G13, G14). Posture matches G13: future amendments adding a new scenario_kind MUST extend both the council_test_runs CHECK constraint and this required-kinds list. G2 view-filter correction (latent bug fix): added AND c.table_name IN (SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE') to the v_unregistered_tables query. Root cause: G2 read information_schema.columns without filtering to BASE TABLE, so views exposing serial via pass-through SELECT (TH-005's vw_test_harness_attention_required) were misclassified as unregistered tables. Pre-existing latent bug from TH-005 — is_compliant had been quietly false since TH-005 shipped; TH-005 omitted an inline DO $smoke$ ... is_compliant=true block (missed SR-004 application), so the regression survived until Part 2's smoke was the first apply-time check strict enough to demand it. Per Brian's clarification, both deltas count as one SR-003 application (rule contract: one canonical reproduction per migration). SR-003 candidate retro count remains 10 applications, zero drift. Refines SR-004 candidate wording for Part 3 promotion: scope explicitly includes "views that expose serial columns or other graph-participating data". Inline smoke: asserts response carries test_harness_scenario_drift key; that unregistered_tables does NOT contain vw_test_harness_attention_required (explicit G2 fix verification); G14 finding count = 0; is_compliant=true. Migration applied successfully — smoke passed. Discipline: SR-003 ✅ application #10 (full body verbatim, 5 NEW markers); SR-004 N/A for this migration (G2 was the missing-smoke case being corrected; this migration includes its own smoke); SR-005 N/A (no new SECDEF functions; existing REVOKE PUBLIC + GRANT authenticated/service_role preserved verbatim); SR-006 N/A (no new tables); D9 ✅ (G14 surfaces silence as compliance failure, not seat veto); D10 ✅ (G14 contributes to is_compliant, doesn't write to it); D12 ✅ (G14 reserved at TH-001 now realized). Foundation closeout guard set complete — next: TH-007 Part 3 (SR-003/004/005/006 registry promotion + SR-004 wording refinement).

  • TEST_HARNESS-007 Part 1 (Aria seat instantiation — constitutional gap close): PROMPT-TEST_HARNESS-20260612-0007-PART1. Resolves SWEEP-ARIA-SEAT-INSTANTIATION-001. supabase/functions/council-deliberate/index.ts: added aria to SeatSlug union (line 56), added seatAria() thin-approve stub directly after seatKyle (returns {verdict:"approve", reason_code:null, reason_detail:null} until SWEEP-ARIA-AMENDMENT-001 ships her real amendment-generation logic), added Aria to the Promise.all spawn block, added to the verdicts record, and added to the determineNextStatus voting array. Aria is now constitutionally present in every Council round; consensus outcomes are unaffected because the stub always approves. supabase/functions/_shared/council-seats.ts: updated CanonicalVotingSeat type union and INSTANTIATED_VOTING_SEATS constant to include aria; updated leading comment to note the TH-007 Part 1 application. No database migration required — aria already exists in public.agents with workspace_id IS NULL per G11 canonical seat seed (verified: 1 row). Closing-loop signal achieved. Post-apply smoke A (triggered_by=th007_part1_aria_smoke against test-harness-synthetic-council, both council-deliberate and test-harness-synthetic-council redeployed so the bundled _shared/council-seats.ts picks up aria): HTTP 200, total=7, passed=7, failed=0, by_action={auto:7, confirm:0, drop:0}. Scenario 7 (canonical_seat_drift) flipped from passed=false, confidence_action=confirm (pre-fix nightly) to passed=true, confidence_action=autocanonical_in_agents_table and instantiated_in_council_deliberate now both equal [aria, kyle, regression_watcher, schema_auditor, scanner], diff.canonical_not_instantiated=[], diff.instantiated_not_canonical=[]. Smoke B (test-harness-drift-simulators): HTTP 200, contaminated=false, 14 rows written, sentinel_residue=0; the 2 pre-existing confirm rows (drift_REGRESSION_RISK, drift_TRAVERSABILITY_VIOLATION) are invariant under this change per prompt §8B (Aria's stub dispatches no seat-detection scenarios). SWEEP-ARIA-SEAT-INSTANTIATION-001 ✅ closed. SWEEP-ARIA-AMENDMENT-001 remains open (deferred post-foundation sub-pack — the seat slot, type union, verdicts record entry, and Promise.all spawn are now in place; only the function body needs replacement when her real logic ships). Discipline: SR-003 N/A (no function body reproduced); SR-005 N/A (no new SECDEF functions); SR-006 N/A (no new tables); D9 ✅ (stub approves, doesn't veto); D10 ✅ (no is_compliant write path); no writes to council_deliberations/council_votes. Closing-loop property: scenario 7 in TH-002 will continue to pass on every subsequent nightly run; the first post-foundation nightly will show the flip as durable evidence in council_test_runs. TH-007 Part 2 (G14 migration) next.

  • TEST_HARNESS-006 (Nightly orchestrator + vault-backed pg_cron): PROMPT-TEST_HARNESS-20260612-0006. New edge function supabase/functions/test-harness-nightly/index.ts invokes the three TH sub-functions (test-harness-synthetic-council, test-harness-drift-simulators, test-harness-resolver-sweep) in parallel, waits up to 5 minutes for completion via verify-by-count polling on council_test_runs (scenario_kind + triggered_by + occurred_at >= run_started_at), and writes one scenario_kind='nightly_batch', scenario_name='nightly_orchestrator' summary row with full per-sub-function expected/actual/completed records. Expected counts queried at run start — constant 7 for synthetic_council (scenarios fixed in TH-002 source), count(*) FROM public.dissent_ontology for drift_simulation (14 today), TH-004's filter introduced_in <> 'reserved' AND graph_participant = true for resolver_sweep (63 today). No hardcoded counts; the orchestrator tracks platform growth automatically. Triggered_by wrapping: orchestrator suffixes the incoming triggered_by with __<8-char run_id> so verify-by-count is unambiguous across overlapping manual+cron runs. Confidence semantics for nightly_batch: all-completed → passed=true, score=0.95, action=auto (HTTP 200); partial completion (>0 actual, < expected) → passed=false, score=0.80, action=confirm (HTTP 207 Multi-Status); any sub-function 0 actual or dispatch 4xx/5xx → passed=false, score=0.30, action=drop (HTTP 500). Dispatch failures abort before waiting and persist a drop-band row immediately. New edge function supabase/functions/test-harness-bootstrap-vault/index.ts mirrors SOT-OPS-001's sot-bootstrap-vault pattern — reads TEST_HARNESS_NIGHTLY_WEBHOOK_SECRET from edge env and idempotently UPSERTs into vault.secrets via public.sot_seed_vault_secret RPC. New runtime secret TEST_HARNESS_NIGHTLY_WEBHOOK_SECRET registered via secrets--add_secret; seeded into vault (vault_seed: "created"). Migration: new pg_cron job test-harness-nightly at 0 3 * * * UTC, idempotent (unschedule-then-schedule), vault-backed via vault.decrypted_secrets WHERE name='TEST_HARNESS_NIGHTLY_WEBHOOK_SECRET'. Mirrors SOT-OPS-001's vault-backed cron pattern verbatim. Concurrent with mbo-scheduled-site-audit per ADR D7 (same cron expression, different jobname). Inline smoke asserts (a) job scheduled, (b) command does not contain REPLACE_WITH placeholder, (c) command references vault.decrypted_secrets (not literal secret), (d) command targets test-harness-nightly. Post-deploy smoke (triggered_by=th006_apply_smoke): HTTP 200, all_completed=true, elapsed_ms=33556 (~10× under the 5-min deadline — matches TH-006 prompt's ~30s expensive sub-function math). council_test_runs shows exactly 85 rows from one orchestrator invocation: synthetic_council=7, drift_simulation=14, resolver_prefix_sweep=63, nightly_batch=1. The single nightly_batch row: passed=true, confidence_action=auto, confidence_score=0.95, duration_ms=33556. Discipline: SR-003 N/A (no function body reproduced); SR-005 N/A (no new SECDEF functions; sot_seed_vault_secret already has SR-005 from SOT-OPS-001); D2 ✅ (nightly_batch scenario_kind reserved at TH-001 now in use); D3 ✅ (edge function, not test file); D7 ✅ (concurrent with mbo-scheduled-site-audit at 03:00 UTC); D9 ✅ (nightly_batch row writes regardless of sub-function outcome — evidence, not veto); D10 ✅ (no is_compliant write path); D11 ✅ (0.95/0.80/0.30 bands extended cleanly to orchestrator level); vault-backed cron pattern (SOT-OPS-001 lineage) ✅. Foundation pack 8 of 8 complete — TH-007 (G14 + SR-003/004/005/006 promotions + retro closeout) next.

  • TEST_HARNESS-005 (Query surface over council_test_runs): PROMPT-TEST_HARNESS-20260612-0005. New views and RPC providing the read surface over per-row confidence data already written by TH-002/003/004. New view public.vw_test_harness_daily_summary (security_invoker=true) — daily pass/fail rollup by scenario_kind, grain (UTC date, scenario_kind), exposes pass/fail/auto/confirm/drop counts plus avg/min/max confidence and pass_pct. New view public.vw_test_harness_attention_required (security_invoker=true) — rows where confidence_action IN ('confirm','drop'), sorted drop-first then confirm both newest-first; pairs with existing idx_council_test_runs_attention_required partial index from TH-001. New view public.vw_test_harness_pass_rate_trend (security_invoker=true) — 7-day rolling pass rate per scenario_kind over the trailing 30 days, function-of-now (no materialization, no staleness); sparse-window dates show rows_in_window=0 and rolling_7d_pass_pct=NULL. New RPC public.get_test_harness_summary(p_days int DEFAULT 7)SECURITY DEFINER, STABLE, search_path=public, typed RETURNS TABLE for programmatic dashboards and future PULSE consumption; clamps p_days via greatest(p_days, 1) so zero/negative input is treated as 1 day; returns aggregate counts and date ranges only, no row-level data, so the elevated privilege does not breach workspace isolation. SR-005 application #4 (sharpened three-role REVOKE/GRANT): REVOKE EXECUTE from PUBLIC, anon, AND authenticated; then GRANT EXECUTE to service_role and authenticated. Brings SR-005 candidate to 4 applications, promotion-eligible at TH-007 retro. Honest scope clarification: TH-005's original roadmap line read "confidence-scoring layer + D8 application" — both halves already shipped in TH-002/003/004 (per-row confidence_score numeric(3,2) + confidence_action; structural jsonb expected_outcome/actual_outcome). TH-005's actual remaining work was making the data queryable; that is what this delivers. Post-deploy smoke: vw_test_harness_daily_summary → 3 rows (one per scenario_kind seen so far), vw_test_harness_attention_required → 67 rows (matches TH-003 designed-evidence confirms + TH-004 skipped/failed prefixes), vw_test_harness_pass_rate_trend → 93 rows (3 scenario_kinds × 31 days, mostly rows_in_window=0); RPC executes for authenticated/service_role roles. New sweeps filed: SWEEP-GRAPH-NODE-BACKFILL-001 (umbrella for TH-004's resolve_AUDIT + resolve_RV found=false findings — root cause likely one of: rows pre-date graph_node registration, schema_auditor seat detector for TRAVERSABILITY_VIOLATION incomplete, or registry incorrectly marks those row types as graph_participant); SWEEP-PREFIX-EMPTY-AUDIT-001 (29 of 63 prefixes with no live rows yet — TH-007 retro review). Discipline: SR-003 N/A (no function body reproduced); SR-005 ✅ application #4; D3 ✅ (pure SQL); D9 ✅ (read-only consumer of evidence); D10 ✅ (no is_compliant write path); D11 ✅ (bands now queryable); no writes to council_deliberations/council_votes; no new tables.

  • TEST_HARNESS-004 (test-harness-resolver-sweep edge function): PROMPT-TEST_HARNESS-20260612-0004. New edge function supabase/functions/test-harness-resolver-sweep/index.ts sweeps every active serial prefix in public.serial_registry (filter mirrors G11: introduced_in <> 'reserved' AND graph_participant = true) through public.resolve(text) and writes one council_test_runs row per prefix with scenario_kind='resolver_prefix_sweep'. Prefix count at apply: 63 active graph-participating prefixes swept. Per-prefix assertions: samples most-recent live serial from the prefix's table_name (fallback to unordered when created_at is absent); calls public.resolve(p_serial:=sampled); asserts found=true, all 18 required envelope keys present, resolver_version='1.3.0', serial echoed correctly. Full resolver envelope stored in actual_outcome.envelope for diff detection over time and PULSE training data when PULSE ships. Confidence bands (D11): pass → 0.95 auto; envelope deviation → 0.85 confirm; empty-prefix skip → 0.85 confirm (surfaced for review, not silenced); RPC error or thrown → 0.30 drop. No contamination gate (per prompt §2): resolve() is STABLE SECURITY DEFINER with no write paths in canonical body; surface is too narrow to justify the symmetry cost; SR-003 audit independently catches resolver write-path regressions. Post-deploy smoke (triggered_by=th004_apply_smoke, two invocations because the first request held the HTTP socket past the curl tool's 60s timeout — server-side completed cleanly both times): 63/63 prefixes covered per run, 126 council_test_runs rows total across the two runs. Per-run shape: ~32 passed (resolver returned full envelope, version match, serial echo, found=true), ~29 skipped (no live rows in source table — passed=true, skipped=true, confidence_action=confirm), 2 failed (confidence_action=confirm): resolve_AUDIT and resolve_RV both returned found=false, serial_echo=true, version=ok, missing=[] — sampled serial exists in source table but is not registered in graph_node_by_serial. Per D9/D10 the failures are recorded as evidence (confirm band) and do not flip is_compliant; they are the kind of TRAVERSABILITY-adjacent finding TH-004 exists to surface for human follow-up. SR-001/SR-002 N/A (no migration); SR-003 N/A (asserts documented envelope shape, doesn't reproduce a function body); PRE-007 N/A.

  • TEST_HARNESS-003 (test-harness-drift-simulators edge function + synthetic workspace/codex_job seed): PROMPT-TEST_HARNESS-20260612-0003. New edge function supabase/functions/test-harness-drift-simulators/index.ts runs 14 simulators (one per active public.dissent_ontology code; SELF_MODIFICATION_BYPASS deferred to PULSE-004) and writes one council_test_runs row per scenario with scenario_kind='drift_simulation'. Hybrid design (Choice 3): 7 Option B simulators (SERIAL_VIOLATION, NAMING_VIOLATION, REGRESSION_RISK, HUMAN_REQUIRED, TRAVERSABILITY_VIOLATION, MISSING_SITE_TAB, RESOLVE_BYPASS) construct synthetic DiffContext + Envelope and invoke the corresponding seat function directly; 7 Option A simulators (RLS_GAP, SCHEMA_DRIFT, ARCHITECTURE_CONFLICT, MISSING_PAGE_RECORD, SCOPE_CREEP, DEPENDENCY_UNMET, INSUFFICIENT_DIAGNOSIS) are tally-only placeholders for codes with no seat detector — they construct a SeatVerdict[] with the target code and assert correct routing through determineNextStatus for round 1 (amend vs human_required) and round 3 (deadlock vs human_required). Inlining decision: Supabase Edge Functions deploy independently per-folder, so cross-function import from ../council-deliberate/index.ts fails to bundle. The 5 seat function bodies are inlined verbatim from council-deliberate/index.ts HEAD 0753e5f — same SR-003 canonical-preservation discipline as the tally block; when seat logic changes upstream, both copies must be updated together. Contamination gate (D9 critical defense, Brian directive): every run records run_started_at, then after all simulators complete asserts count(*) FROM council_deliberations WHERE created_at >= run_started_at = 0 AND count(*) FROM council_votes WHERE submitted_at >= run_started_at = 0 (both tables per refinement). If either trips → scenario rows NOT written, single contamination_gate_tripped row written with confidence_score=1.00, confidence_action=confirm (the only TH write path that speaks with maximum certainty), HTTP 500 returned. Sentinel scan (Q2B): scans council_deliberations.pr_title LIKE 'TH003_DRIFT_SIM__%' — if found after Q2A passed, writes a contamination_sentinel_residue evidence row (does not block run). Synthetic envelope pr_title='TH003_DRIFT_SIM__<scenarioName>__<runId>' is the sentinel signature. Migration: seeded synthetic public.workspaces row 00000000-0000-0000-0000-000000000003 (owner_user_id=first existing auth user, name 'TH003 Synthetic') + public.codex_jobs row 00000000-0000-0000-0000-000000000004 (vertical_slug='th003-synthetic', minimal resolved_vertical_contract). These are passed into seat envelopes as context only — no row written by any simulator references them. SR-003 (8th application): DissentCode, SeatVerdict, DiffContext, Envelope, HARD_VETO_CODES, determineNextStatus + all 5 seat-function bodies reproduced verbatim from council-deliberate/index.ts HEAD 0753e5f. Confidence bands (D11): Option B pass → 0.90 auto / fail → 0.80 confirm / thrown → 0.30 drop; Option A pass → 0.95 auto / fail → 0.85 confirm / thrown → 0.30 drop; contamination event → 1.00 confirm. Post-deploy smoke (triggered_by=th003_apply_smoke): HTTP 200 (gate clean — no contamination on apply), total=14, passed=10, failed=4, by_action={auto:10, confirm:4, drop:0}, by_mode={option_b_detection:7, option_a_tally:7}, sentinel_residue=0, contaminated=false. Passes (10): all 7 Option A tally checks + MISSING_SITE_TAB (diff-stage detection fires on synthetic CREATE TABLE ... site_id uuid without graph_node row) + NAMING_VIOLATION (scanner flags public.TH003SyntheticCamelCase) + RESOLVE_BYPASS (kyle's assert_resolved RPC detects synthetic serial with no resolve() evidence). Designed-evidence failures (4 → confirm): SERIAL_VIOLATION and TRAVERSABILITY_VIOLATION (only reachable via serial_compliance_check post-state — platform currently compliant, so seat returns approve); REGRESSION_RISK (synthetic workspace has no pages rows with known_issues, so seat approves); HUMAN_REQUIRED (state_verifier emits only when is_compliant=false — pass/fail correctly reflects platform compliance, not a simulator bug). Per D9/D10, these failures route to confirm for human review without flipping is_compliant. New sweep filed: SWEEP-DISSENT-DETECTOR-COVERAGE-001 — 7 dissent codes in the ontology have no seat detector. Promotion path: when a detector ships for any of these 7 codes, replace the corresponding Option A simulator with Option B in the same migration; council_test_runs is append-only and self-describing via actual_outcome.detector_mode, so no backfill is required. SR-001/SR-002 N/A (no FK/manifest changes); SR-003 satisfied (8th application — full canonical preservation across tally + 5 seats); PRE-007 N/A.

  • TEST_HARNESS-002 (test-harness-synthetic-council edge function + _shared/council-seats.ts): PROMPT-TEST_HARNESS-20260612-0002. New edge function supabase/functions/test-harness-synthetic-council/index.ts runs 7 deterministic scenarios over determineNextStatus and writes one council_test_runs row each per TH-000 D9 (evidence, not vetoes) and D10 (independent of guard outcomes). New shared module supabase/functions/_shared/council-seats.ts is the source of truth for instantiated voting seats (INSTANTIATED_VOTING_SEATS = kyle/scanner/schema_auditor/regression_watcher; INSTANTIATED_OBSERVER_SEATS = state_verifier). Imported by council-deliberate (documentation-only, no behavior change) and test-harness-synthetic-council (scenario 7). Scenarios 1–5 exercise the four tally outcomes (resolved, round_N_amending, deadlocked, human_required) and the hard-veto path; scenario 6 is the SOT-004 observer-filter regression test (with counterfactual sanity check); scenario 7 catches canonical-vs-instantiated voting seat drift. SR-003 (7th application): determineNextStatus and HARD_VETO_CODES reproduced verbatim from council-deliberate/index.ts HEAD 0753e5f. Confidence bands (D11): pure-tally scenarios → 0.95 auto (pass) / 0.90 auto (fail); scenario 7 drift → 0.95 auto (match) / 0.90 confirm (drift); thrown error → 0.30 drop. Post-deploy smoke (triggered_by=th002_apply_smoke): HTTP 200, total=7, passed=6, failed=1, by_action={auto:6, confirm:1, drop:0}. Scenario 7 currently fails (passed=false, confidence_action=confirm) because aria is canonical in public.agents per G11 but not instantiated in council-deliberate — exactly the planning-context drift TEST_HARNESS exists to surface. Per D10, this failure does not flip is_compliant. New sweeps filed: SWEEP-ARIA-SEAT-INSTANTIATION-001 (Gap 1, ~10-line fix). SWEEP-ARIA-AMENDMENT-001 already on file (Gap 2, full sub-pack from COUNCIL ADR Q7). No graph wiring changes (edge function, not a node; council_test_runs already wired in TH-001).

  • TEST_HARNESS-001 (council_test_runs table + CTR serial + graph wiring + RLS + indexes): PROMPT-TEST_HARNESS-20260611-0001, baseline 06e7495. SQL migration + companion TS edit. Migration: new public.council_test_runs table (append-only log of synthetic Council scenarios, drift simulations, resolver prefix sweeps, nightly batch) with locked vocab — scenario_kind CHECK (4 values), confidence_action CHECK (3 bands: auto/confirm/drop), confidence_score CHECK [0,1]. Nullable workspace_id FK with ON DELETE CASCADE (platform-global pattern mirroring platform_state). RLS enabled with 3 policies (workspace-scoped read, authenticated read of platform-global rows, service_role full); GRANT SELECT to authenticated, GRANT SELECT,INSERT,DELETE to service_role — no UPDATE grant, append-only by privilege. 4 indexes: (scenario_kind, occurred_at DESC), (passed, occurred_at DESC), (scenario_name, occurred_at DESC), partial (confidence_action, occurred_at DESC) WHERE confidence_action <> 'auto'. CTR serial trigger: public.tg_serial_council_test_run() SECURITY DEFINER with SET search_path = public, emits CTR-GLOBAL-YYYYMMDD-NNN via mbo_generate_serial('CTR','GLOBAL'). SR-005 candidate discipline applied: REVOKE EXECUTE FROM PUBLIC, anon, authenticated (smoke verified has_function_privilege('authenticated', ..., 'EXECUTE')=false). Serial registry: CTR prefix row inserted (graph_participant=true). Graph wiring (SR-002): graph_node row council_test_run (terminal, has_workspace_id, site_tab_state='infrastructure') + graph_manifest soft edge council_test_run → workspace via workspace_id. Companion TS edit to src/lib/worldportGraph.ts: added 'council_test_run' to GraphNodeType union, NODE_TABLE entry, and GRAPH_EDGES soft edge — SR-002 sync preserved. SR-006 candidate discipline: tg_ensure_graph_edge_status_row_council_test_runs AFTER INSERT trigger attached. Inline smoke (10th SR-004 application): asserts 5 indexes, 3 RLS policies, CTR registry row, graph_node row, manifest edge, SR-005 REVOKE, SR-006 ledger trigger, CTR-GLOBAL-* serial generation, 3 CHECK rejections (invalid scenario_kind, confidence_score>1, invalid confidence_action), cleanup, and is_compliant=true. Apply note: first attempt failed self-check because REVOKE ... FROM PUBLIC, anon left an implicit grant to authenticated; resolved by adding explicit REVOKE EXECUTE FROM authenticated. SR-001 N/A pending manifest_fk_sweep run; SR-002 satisfied (TS + DB landed together); SR-003 satisfied (all referenced objects verified vs canonical migrations per prompt SR-003 table); PRE-007 satisfied. 77 pre-existing linter warnings unchanged. Unblocks TH-002 (test-harness-synthetic-council edge function).

  • SOT-OPS-001 (vault-backed cron secret for state-verifierSWEEP-STATE_OF_TRUTH-CRON-SECRET-VAULTING-001 resolved for SOT): SQL migration + new edge function. Part A (migration): created public.sot_seed_vault_secret(p_name text, p_value text) SECURITY DEFINER helper (service_role only, REVOKE EXECUTE FROM PUBLIC/anon/authenticated) — idempotently UPSERTs into vault.secrets so the cron command can read via vault.decrypted_secrets without holding the literal in pg_cron's command text. Rescheduled sot-state-verifier-hourly: command now resolves Authorization: Bearer || (SELECT decrypted_secret FROM vault.decrypted_secrets WHERE name='STATE_VERIFIER_WEBHOOK_SECRET' LIMIT 1) (mirrors READER-004 / mbo-scheduled-site-audit pattern). Part B (edge function): supabase/functions/sot-bootstrap-vault/index.ts — one-shot env→vault mirror reads STATE_VERIFIER_WEBHOOK_SECRET from Deno env, calls the seeding RPC with service-role client, then exercises state-verifier with the live bearer to confirm end-to-end. Secret registration: STATE_VERIFIER_WEBHOOK_SECRET added via secrets--add_secret (auto-provisioned into Supabase env; not in repo). Verification (live): bootstrap returned {ok:true, vault_seed:"created", verifier_status:200}; platform_state shows one state_verifier_assertion row (event_source='state-verifier', metadata->>'source'='manual_ops_001_verification', is_critical=false, summary "1 non-critical check(s) failed" — C4 council throughput, expected); cron.job confirms command text references vault.decrypted_secrets (no literal secret). Removed stale src/routes/api/public/sot-bootstrap-vault.ts (edge function path chosen for deploy speed). SR-001/SR-002 N/A; PRE-007 satisfied (REVOKE on new SECDEF). SWEEP-STATE_OF_TRUTH-CRON-SECRET-VAULTING-001 → ✅ RESOLVED for the SOT-005 cron; READER-004 + future PULSE-005 still pending the same refactor.

  • STATE_OF_TRUTH-006 (pack closeout: G13 platform_state_event_kind_drift + retrospective + STATE_OF_PLATFORM.md archive): PROMPT-STATE_OF_TRUTH-20260611-0006, baseline 53eec52. SQL migration + docs. Sixth SR-003 application — full body of public.serial_compliance_check() preserved verbatim from canonical 20260611224500_*.sql lines 52–268; three minimal additions only: (a) declare v_event_kind_drift jsonb, (b) G13 SELECT block immediately after G12 computing unknown_event_kind rows from public.platform_state whose event_kind is NOT in the canonical 12-value list inlined as a VALUES list (same posture as G11's seat list — CHECK constraint + G13 NOT IN clause must be amended together), (c) platform_state_event_kind_drift added to RETURN map and to the is_compliant AND-chain. Seven is_compliant-contributing guards now active: G2, G7, G8/G9, G10, G11, G12, G13. SECDEF / SET search_path / REVOKE EXECUTE FROM PUBLIC / GRANTs preserved verbatim. Inline smoke (3 phases): asserts field present, G13 array empty (no event_kind drift in live data), is_compliant=true (9th SR-004 application). Docs: created docs/STATE_OF_TRUTH_PACK_RETROSPECTIVE.md (serial RETRO-STATE_OF_TRUTH-20260611-0001) — what shipped across 7 prompts, what we learned (live-tree probing, Lovable apply-time corrections as signal, placeholder→fulfillment contract pattern, Path A/B cleavage), Standing Rules status (SR-004 recommended for promotion to ACTIVE; SR-005 + SR-006 filed as new single-instance candidates from SOT-001's G7/G8 corrections), 7 deferred sweeps catalogued (amendment-emit, trigger-audit-rpc, cron-secret-vaulting, migration-events, deployment-events, confidence, retention), pack metrics + TEST_HARNESS handoff. Archived docs/STATE_OF_PLATFORM.mddocs/_archive/STATE_OF_PLATFORM_20260611.md; new stub at docs/STATE_OF_PLATFORM.md redirects to platform_state / mv_current_platform_state queries and the retrospective. Updated docs/FOUNDATION_ROADMAP.md row 7 → ✅ COMPLETE. Appended pack-closeout note to docs/COUNCIL_PR_REVIEW_CHECKLIST.md. SR-001/SR-002 N/A (no FK/manifest changes); SR-003 satisfied (sixth application — canonical body preserved verbatim, three additions only); PRE-007 satisfied (REVOKE EXECUTE preserved verbatim). 77 pre-existing linter warnings unchanged. STATE_OF_TRUTH pack CLOSED. Foundation status: 7 of 8 packs complete (SCHEMA_AUDITOR_v2, SERIAL, TRAVERSABILITY, RESOLVER, COUNCIL, SITE_PAGE_VIS, STATE_OF_TRUTH). Next: TEST_HARNESS (pack 8 — closes foundation).

  • STATE_OF_TRUTH-005 (state-verifier edge function + hourly pg_cron + pr_merged Path B): PROMPT-STATE_OF_TRUTH-20260611-0005, baseline 74e3344. Three-part commit: new edge function + SQL migration (cron schedule + smoke) + TS edit to existing codex-pr-completed. Part A: new supabase/functions/state-verifier/index.ts — Bearer-auth (STATE_VERIFIER_WEBHOOK_SECRET) Deno handler runs 5 checks in parallel and emits one state_verifier_assertion row to platform_state per invocation (entity_serial='STATE-VERIFIER-PLATFORM', event_source='state-verifier', emitted_by='state-verifier'). Checks: C1 compliance spine (serial_compliance_check ->> is_compliant, critical), C2 MV freshness (advisory), C3 refresh log health (medium), C4 council throughput (stuck non-terminal jobs >24h, low), C5 trigger audit (deferred to v1 info-only — SWEEP-STATE_OF_TRUTH-TRIGGER-AUDIT-RPC-001 files for v2 SECDEF RPC). is_critical metadata flag set when any critical check fails (evidence-only; no auto-escalation). Part B (migration): Block 1 idempotent cron.unschedule + cron.schedule('sot-state-verifier-hourly', '0 * * * *', ...) invokes net.http_post to the state-verifier function URL with Bearer placeholder REPLACE_WITH_STATE_VERIFIER_WEBHOOK_SECRET (mirrors READER-004 SCHEDULED_AUDIT_WEBHOOK_SECRET pattern; operator must substitute or refactor to vault.secrets). Block 2 inline smoke asserts cron job registered + is_compliant=true (8th SR-004 application). Part C (TS edit): codex-pr-completed/index.ts — after the existing agent_dispatch_log merged insert, added best-effort platform_state INSERT with event_kind='pr_merged', event_source='codex', metadata carrying codex_job_id/pr_number/pr_url/vertical_slug/merge_commit_sha/head_ref/github_action. Wrapped in try/catch — merge has already happened; audit-log failure must not block downstream. SR-001/SR-002 N/A (no FK/manifest changes); SR-003 satisfied (every column + RPC verified vs SOT-001/SOT-002/SOT-003 canonical sources). PRE-007 satisfied — no new SECDEF functions; pg_cron HTTP-call pattern mirrors READER-004. After this: state-verifier produces hourly evidence rows; all 3 Path B emitters live (council_deliberation_opened/council_deliberation_closed SOT-002 + pr_merged SOT-005); all 3 spec'd event sources active (trigger/council/codex/state-verifier). council_amendment_proposed remains vocabulary-only per deferred SWEEP-STATE_OF_TRUTH-AMENDMENT-EMIT-001. Env: STATE_VERIFIER_WEBHOOK_SECRET must be set in Supabase env before the first scheduled run. Unblocks SOT-006 (pack closeout: G13 platform_state_event_kind_drift, STATE_OF_PLATFORM archive, retrospective, FOUNDATION_ROADMAP row 7 → COMPLETE).

  • STATE_OF_TRUTH-004 (State Verifier observer seat: CHECK extensions + seed + seatStateVerifier() wiring): PROMPT-STATE_OF_TRUTH-20260611-0004, baseline 6d7aa75. SQL migration + TS edits to supabase/functions/council-deliberate/index.ts. Block 1: extended agent_seat CHECK constraints on both council_deliberations and council_votes to accept 'state_verifier' (DO-block discovers auto-named constraint via pg_constraint + pg_get_constraintdef LIKE %agent_seat%, drops it, re-adds named <table>_agent_seat_check with 6-value list). Block 2: seeded agents row (workspace_id=NULL, slug='state_verifier', name='State Verifier', role='observer', status='idle', provider='council') ON CONFLICT (slug) DO NOTHING — platform-global observer (NOT in G11 canonical 5-seat list, per architectural distinction: voting Council seats are constitutional, State Verifier is constitutional-adjacent infrastructure verified by SOT-006's G13). Block 3 (inline smoke): asserts seat present with role='observer' + workspace_id IS NULL, both CHECK constraints include state_verifier, CHECK still rejects invented invented_seat (real INSERT against any workspace, caught check_violation), and is_compliant=true (G11 unaffected). TS edits (3, same commit): (B1) SeatSlug union extended to add "state_verifier" (line 50). (B2) seatStateVerifier() added after seatRegressionWatcher — thin v1 probe: reads serial_compliance_check RPC; emits HUMAN_REQUIRED dissent if is_compliant=false or RPC errors, else approves. Observer reason code is HUMAN_REQUIRED not SERIAL_VIOLATION/TRAVERSABILITY_VIOLATION (those belong to Schema Auditor; State Verifier signals "recorded state vs reality"). (B3) Dispatch fan-out expanded 4→5 Promise.all awaits + verdicts record gained state_verifier key + persistVotes unchanged (all 5 persist as audit trail) + determineNextStatus call explicitly excludes observer ([kyle, scanner, schemaAuditor, regWatcher] — inline comment marks the architectural property audit-but-not-vote so future readers don't "fix" the apparent omission). SR-001/SR-002 N/A (no FK/manifest changes); SR-003 satisfied (every column + constraint value verified against canonical sources before draft). 77 pre-existing linter warnings unchanged. After this: State Verifier deliberation participation is wired; observer verdict persists for audit but is filtered from consensus; CHECK constraints accept 'state_verifier' for persistence. Unblocks SOT-005 (state-verifier edge function with richer periodic assertion + cron schedule + Path B pr_merged emission from codex-pr-completed).

  • STATE_OF_TRUTH-003 (Resolver v1.3.0 — replace recent_state_changes placeholder + add current_state field): PROMPT-STATE_OF_TRUTH-20260611-0003, baseline 5c05e73. SQL migration only. 5th application of SR-003 verbatim-canonical-preservation discipline: full body of public.resolve(text) preserved byte-for-byte from canonical 20260611185910_*.sql lines 1–217 with three minimal additions clearly marked NEW (SOT-003). Change 0: added v_current_state jsonb := NULL to DECLARE. Change 1: replaced placeholder recent_state_changes query (was council_deliberations LIKE-match, COUNCIL-006 placeholder) with read from public.platform_state WHERE entity_serial = p_serial ORDER BY occurred_at DESC LIMIT 10; outer jsonb_agg(jsonb_build_object(...)) shape preserved; field shape now kind/at/source/summary/metadata (kind-agnostic — replaces Council-specific agent_seat/verdict/reason_code). Change 2: added SELECT to_jsonb(c.*) - 'workspace_id' INTO v_current_state FROM public.mv_current_platform_state c WHERE c.entity_serial = p_serialworkspace_id stripped (redundant with parent response's has_workspace_id flag); NULL default distinguishes "no events" from "empty state". Change 3: added current_state key to both the main RETURN block and the malformed_serial early-return; bumped resolver_version from '1.2.0''1.3.0' in both places. SECDEF/STABLE/SET search_path preserved; REVOKE EXECUTE FROM PUBLIC + anon, GRANT EXECUTE to authenticated + service_role preserved verbatim from COUNCIL-006 (PRE-007 / G7 discipline). Inline smoke (3 phases): (1) structural — picks sample serial, asserts resolver_version='1.3.0', all three fields (recent_state_changes/open_deliberations/current_state) present, recent_state_changes is array, current_state is null|object. (2) data-level roundtrip — finds real incident serial, inserts synthetic platform_state row (state_verifier_assertion/manual), refreshes MV, asserts recent_state_changes contains the seed kind, current_state.current_event_kind = 'state_verifier_assertion', and current_state does NOT carry workspace_id; cleanup deletes seed + re-refreshes MV. (3) is_compliant=true assertion (7th SR-004 application). SR-001/SR-002 N/A (no schema/manifest changes); SR-003 satisfied (5th application — verification table cites every referenced object to canonical source). TS consumers forward-compatible by inspection (recent_state_changes: unknown[] at src/lib/resolve.functions.ts:47; no version-comparison consumers); UI placeholder text at ResolvedPanel.tsx:303 left intact — frontend wire-up deferred to SOT-006 (out of scope here). 77 pre-existing linter warnings unchanged. Closes COUNCIL-006 placeholder contract ("STATE_OF_TRUTH SOT-003 will replace with proper platform_state read"). After this: resolver returns current_state for any entity with a platform_state event; recent_state_changes surfaces the full 12-kind vocabulary, no longer council-only; resolver_version='1.3.0' makes the contract change auditable. Unblocks SOT-004 (State Verifier observer seat in council-deliberate).

  • STATE_OF_TRUTH-002 (mv_current_platform_state + wrapped refresh + pg_cron + Council Path B emission): PROMPT-STATE_OF_TRUTH-20260611-0002, baseline 96b3dab. SQL migration + TS edits to supabase/functions/council-deliberate/index.ts. Block 1: public.mv_current_platform_state materialized view — DISTINCT ON (entity_serial) ORDER BY entity_serial, occurred_at DESC projecting current_event_kind/current_event_source/current_state_snapshot/current_state_since/current_summary/current_metadata/workspace_id. Unique index on entity_serial (REFRESH CONCURRENTLY req), partial index on (workspace_id, current_state_since DESC) WHERE workspace_id IS NOT NULL, btree on current_event_kind. GRANT SELECT to authenticated + service_role. Block 2: public.refresh_mv_current_platform_state_logged() SECURITY DEFINER, mirrors SERIAL-008e — INSERTs mv_refresh_log row (running), runs REFRESH MATERIALIZED VIEW CONCURRENTLY, UPDATEs log row with duration_ms/rows_affected/status=success; on EXCEPTION updates log with status=error + SQLERRM (does NOT re-raise — error row IS the audit trail). REVOKE EXECUTE FROM PUBLIC/anon/authenticated; GRANT EXECUTE TO service_role (G7 discipline per SOT-001 Lovable correction). Block 3: pg_cron sot-mv-current-platform-state-refresh scheduled */5 * * * *; idempotent via cron.unschedule + cron.schedule wrap. Block 4: Initial non-CONCURRENT REFRESH primes the view. Block 5 (inline smoke): asserts MV exists, function exists, authenticated has NO EXECUTE, cron job scheduled, refresh logs success row, manual pr_merged seed gets PSTA serial + is queryable + appears in MV after refresh, cleanup leaves MV consistent, CHECK constraint rejects invented event_source, and is_compliant=true. Smoke deletes the seed row before completion. TS edits (Path B emission, 2 of 3 events — 3rd is council_amendment_proposed, deferred): (1) runDeliberationRound — captures priorCouncilStatus via best-effort SELECT before the codex_jobs.council_status UPDATE; after the UPDATE, if nextStatus ∈ {resolved,deadlocked,human_required,clean,flagged}, inserts platform_state row with entity_serial='CJOB-<id>', event_kind='council_deliberation_closed', event_source='council', prior_state, next_state, full verdicts in metadata, emitted_by='council-deliberate'. (2) Handler kickoff path — after the pending → round_1 UPDATE, inserts platform_state row with event_kind='council_deliberation_opened', event_source='council', next_state={council_status: round_1}, metadata={codex_job_id, pr_number, pr_url, round:1}. Both emissions wrapped in try/catch — Path B is best-effort, deliberation proceeds even if audit insert fails. SWEEP filed: SWEEP-STATE_OF_TRUTH-AMENDMENT-EMIT-001council_amendment_proposed has no emitter today (council-deliberate does not write to council_amendments); wires when Aria amendment generation ships (SWEEP-ARIA-AMENDMENT-001). SR-001/SR-002 N/A; SR-003 satisfied (every column verified against SOT-001 locked schema). 77 pre-existing linter warnings unchanged. After this: latest-state projection is live and refreshes every 5 min with audit; 2 of 3 Council Path B events emit explicitly; pr_merged vocabulary proved end-to-end. Unblocks SOT-003 (resolver bumps to 1.3.0; reads current_state from MV + recent_state_changes from platform_state).

  • STATE_OF_TRUTH-001 (platform_state table + 12-kind CHECK + RLS + indexes + PSTA serial + graph wiring + 5 trigger attachments): PROMPT-STATE_OF_TRUTH-20260611-0001, baseline 4dbd432. SQL migration + companion TS edit (SR-002). Block 1: public.platform_state (id, serial UNIQUE, entity_serial NOT NULL, workspace_id → workspaces ON DELETE CASCADE nullable, event_kind NOT NULL CHECK 12-enum locked at v1 [entity_created/entity_status_changed/entity_archived/council_deliberation_opened/council_deliberation_closed/council_amendment_proposed/pr_merged/migration_applied/deployment_completed/incident_opened/incident_resolved/state_verifier_assertion], event_source NOT NULL CHECK 7-enum [resolver/council/codex/cron/manual/state-verifier/trigger], prior_state/next_state jsonb, summary NOT NULL, metadata jsonb DEFAULT '{}', occurred_at, emitted_by). Block 2: RLS enabled; platform_state_ws_read (SELECT via is_workspace_member), platform_state_global_read (SELECT TO authenticated WHERE workspace_id IS NULL), platform_state_svc_all (ALL TO service_role). No UPDATE policy — append-only by privilege. GRANT SELECT to authenticated; GRANT SELECT,INSERT,DELETE to service_role (no UPDATE grant). Block 3: 5 explicit indexes (entity_serial+occurred_at DESC, kind, occurred_at DESC, workspace_id+occurred_at DESC partial WHERE NOT NULL, occurred_at DESC partial WHERE NULL). Block 4: PSTA prefix registered (graph_participant=true); tg_serial_platform_state BEFORE INSERT calls mbo_generate_serial('PSTA','GLOBAL') — format PSTA-GLOBAL-YYYYMMDD-NNN. Block 5: graph_node row (node_type=platform_state_event, is_terminal=true, has_workspace_id=true, site_tab_state='infrastructure') + 1 graph_manifest edge (platform_state_event→workspace via workspace_id, soft, forward). Companion TS edit to src/lib/worldportGraph.ts: extended GraphNodeType union + NODE_TABLE + appended GRAPH_EDGES row (SR-002 satisfied in same commit). Block 5b (correction added during apply): tg_ensure_graph_edge_status_row_platform_state() SECURITY DEFINER + trg_ges_platform_state AFTER INSERT/UPDATE — calls ges_upsert_edge() so G8 manifest-coverage stays at 0 from the first emitted row. Block 6: Two static SECURITY DEFINER trigger functions per ratification flag (no dynamic TG_TABLE_NAME inspection of columns): tg_emit_status_change_to_platform_state() (plain status column, defensive BEGIN ... EXCEPTION WHEN undefined_column for workspace_id) and tg_emit_council_status_change_to_platform_state() (codex_jobs.council_status; entity_serial keyed CJOB-<id> since codex_jobs has no serial column). Both REVOKE EXECUTE FROM PUBLIC/anon/authenticated; GRANT EXECUTE TO service_role — keeps G7 secdef_authenticated_leaks empty without amending AUDITOR allow-list. 5 trigger attachments: AFTER UPDATE OF status on deployments/incidents/tasks/agents; AFTER UPDATE OF council_status on codex_jobs. Block 7 (inline smoke): asserts 5 emit-triggers attached, PSTA serial_registry row present, graph_node + graph_manifest rows present, CHECK rejects invented event_kind, real incident status flip (open→investigating→open) emits a PSTA-prefixed row, and (public.serial_compliance_check() ->> 'is_compliant')::boolean = true. Apply note: first attempt failed with G7 (2 SECDEF leaks) + G8 (platform_state_event→workspace coverage gap, ledger_count=0) violations. Re-submission added EXECUTE revokes on all three new SECDEF functions + the Block 5b ledger trigger; smoke then asserted is_compliant=true and migration committed cleanly. SR-001 satisfied (Block 5b + smoke); SR-002 satisfied via companion TS edit; SR-003 satisfied — every column/function cited to canonical migration in the prompt's verification table. Path A (trigger emission) live. Path B (explicit emission in council-deliberate and codex-pr-completed) ships in SOT-002/005. After this: platform_state is real, queryable, append-only; 12-kind vocabulary locked at DB level; PSTA serials register events; constitutional-level audit trail begins now. Unblocks STATE_OF_TRUTH-002 (mv_current_platform_state + Path B emission in council-deliberate).

  • SITE_PAGE_VIS-005 (Pack closeout: G12 site_tab_coverage_gaps + site_tabs_canonical registry): PROMPT-SITE_PAGE_VIS-20260611-0005, baseline a4f141a. SQL migration. Block 1: Created public.site_tabs_canonical (tab_key PK, label, introduced_in, is_ui_only, created_at) with RLS — _read policy SELECT to authenticated, _svc policy ALL to service_role; GRANTs SELECT to authenticated, ALL to service_role. Seeded with all 13 current tab keys; notes/pipeline/preview flagged is_ui_only=true (deviation from prompt — see below). Block 2: Replaced public.serial_compliance_check() adding G12 (site_tab_coverage_gaps) with two-branch payload — pending_entity (graph_node rows with site_tab_state='pending' excluding site_tab_pending_exceptions) and orphan_tab (canonical tab_keys not referenced by any graph_node.site_tab_key, skipping is_ui_only=true). G12 added to the is_compliant AND-chain. Six is_compliant-contributing guards now active (G2, G7, G8/G9, G10, G11, G12). Block 3 (inline smoke): Asserts 13 canonical rows, G12 empty, is_compliant=true. Deviation from prompt: prompt's smoke fired on first apply — notes, pipeline, preview are UI-only surfaces not backed by any graph_node entity. Added is_ui_only boolean column to site_tabs_canonical; G12's orphan-tab branch excludes ui-only rows. Filed SWEEP-SITE_PAGE_VIS-UI-ONLY-TABS-001. SR-001/SR-002 N/A; SR-003 satisfied. 76 pre-existing linter warnings unchanged. Closes SITE_PAGE_VIS pack (6 of 8). Retrospective: docs/SITE_PAGE_VIS_PACK_RETROSPECTIVE.md (RETRO-SITE_PAGE_VIS-20260611-0001). Foundation status: 6 of 8 packs complete (SCHEMA_AUDITOR_v2, SERIAL, TRAVERSABILITY, RESOLVER, COUNCIL, SITE_PAGE_VIS). Next: STATE_OF_TRUTH.

  • SITE_PAGE_VIS-004 (Schema Auditor seat: MISSING_SITE_TAB diff inspection + verdict branching): PROMPT-SITE_PAGE_VIS-20260611-0004, baseline 3a8666a. TypeScript-only; single file edit to supabase/functions/council-deliberate/index.ts replacing seatSchemaAuditor body. Stage 1 (pre-merge diff inspection): scans added lines of each migration patch (+-prefixed lines only) for CREATE TABLE public.<name>(...) blocks containing site_id uuid; for each candidate, searches the entire PR diff for either INSERT INTO public.graph_node ... '<name>' OR an UPDATE setting site_tab_state='covered'|'infrastructure' near '<name>'. If neither is found → dissent MISSING_SITE_TAB with reason_detail.missing_site_tab_diff[] listing filename/table/needs. Stage 2 (compliance check, post-state): calls serial_compliance_check(). RPC error → HUMAN_REQUIRED (preserved escape hatch). If is_compliant=true → approve. Otherwise routes by populated field: site_tab_coverage_gapsMISSING_SITE_TAB (forward-compatible — field ships in SITE_PAGE_VIS-005's G12; until then this branch is dormant), manifest_coverage_gaps|dangling_edge_summaryTRAVERSABILITY_VIOLATION, else → SERIAL_VIOLATION (catch-all preserved). KNOWN_REASON_CODES and ReasonCode union already include both new codes (seeded by COUNCIL-001), so no type/registry edits needed. SR-001/SR-002/SR-003 N/A (no DB or manifest changes). After this: Schema Auditor has typed jurisdiction over MISSING_SITE_TAB/TRAVERSABILITY_VIOLATION/SERIAL_VIOLATION + HUMAN_REQUIRED fallback; missing site-tab coverage caught BEFORE merge via diff inspection; compliance branch ready to auto-route G12 findings when 005 ships.

  • SITE_PAGE_VIS-003 (Discussion + Vault tab surfaces + graph_node flip): PROMPT-SITE_PAGE_VIS-20260611-0003, baseline 2317c37. Mixed TS + SQL. SQL: Two UPDATEs flip graph_node rows for site_discussion→(covered,discussion) and research_vault→(covered,vault); DO-block smoke asserts both flips, pending count = 5 (Bucket C set), and is_compliant=true. TS: SiteTabs.tsxSiteTabKey union and SITE_TABS array gain vault (after notes) and discussion (after code_health), 13 entries total. sites.$siteId.tsx — dispatch lines for both new tabs; inline VaultTab component (reads research_vault filtered by site_id, ordered by created_at DESC; renders serial/doc_type chips, title, source_url, overview/content preview) with ResearchVaultEntry type adapted to actual table columns (content/overview/doc_type rather than the template's body/source_type); inline DiscussionTab (reads roots only via parent_id IS NULL, separate reply-count query, in-memory sort: announcements float → open first → newest; renders serial + kind + status chips, title, 200-char body preview, reply count). No worldportGraph.ts edit (entities already present). SR-001/SR-002 N/A; SR-003 satisfied. 76 pre-existing linter warnings unchanged. After this: two new site tabs render live, transitional pending bucket cleared, only the 5 Bucket C exceptions remain pending — ready for G12 in SITE_PAGE_VIS-005. Unblocks SITE_PAGE_VIS-004.

  • SITE_PAGE_VIS-002 (Site-scoped entity audit + graph_node extension + pages FK + pending-exception registry): PROMPT-SITE_PAGE_VIS-20260611-0002. SQL-only migration. Block 1: Added pages_site_id_fkey (pages.site_idsites.id ON DELETE CASCADE) — orphan check ran first (0 found); DO-block guarded for idempotency. Block 2: Extended public.graph_node with site_tab_key text and site_tab_state text NOT NULL DEFAULT 'unscoped', plus graph_node_site_tab_state_check CHECK enforcing the 4-state enum (covered|unscoped|infrastructure|pending) per Locked Call 1 (D1). Block 3: Created public.site_tab_pending_exceptions (table_name PK, reason, sweep_serial, introduced_in, expected_resolution, created_at) — RLS enabled, _read policy SELECT to authenticated, _svc policy ALL to service_role; GRANTs SELECT to authenticated, ALL to service_role. Per Brian's Call 3 spec + expected_resolution nullable column. Block 4: 25 explicit UPDATEs ratified by Brian — Bucket A covered (9: pages, deployments, design_bridge, code_health_issues, prompt_pack_documents, prompts, blueprints, incidents, tasks); Bucket A-transitional pending (2: research_vault, site_discussion — 003 flips to covered with vault/discussion tab keys); Bucket B infrastructure (9: agentos_connections, browser_sessions, build_chains, context_pointers, documents, email_ingest, email_integration_config, site_integrations, revenue_events — revenue_events per Call 4 promotes to covered when revenue tab ships); Bucket C deferred pending (5: fleet_deployments, fleet_templates, github_pull_requests, loops, scheduled_operations). Per Call 5, tenants/telegram_chat_links/freshness_records intentionally not updated — no site_id column; default unscoped applies. Block 5: Seeded 5 exception rows with sweep serials SWEEP-SPV-TAB-{FLEET,GIT,LOOPS,SCHED}-001 — G12 (ships in 005) will read this to exclude rows from pending_entity violations. Block 6 (inline smoke): Asserted 0 orphan pages, FK installed, both new columns exist, bucket distribution (covered≥9, infrastructure≥9, pending=7), 5 exceptions seeded, and (public.serial_compliance_check() ->> 'is_compliant')::boolean = true. SR-001 satisfied (post-apply smoke confirms manifest↔FK sync); SR-002 N/A (graph_node extension server-side only); SR-003 satisfied — all 24 entity node_type values Python-parsed from migration tree and verified verbatim. 76 pre-existing linter warnings unchanged. Audit truth: ADR estimated 18–22 site-scoped tables; actual count is 24. After this: every site-scoped entity has explicit bucket assignment; pages.site_id audit-discovered drift fixed; exception registry mechanism live, ready for G12 in SITE_PAGE_VIS-005. Unblocks SITE_PAGE_VIS-003 (ship discussion + vault tabs, flip 2 transitional pending rows to covered).

  • SITE_PAGE_VIS-001 (site_discussions schema + DSC prefix + graph wiring): PROMPT-SITE_PAGE_VIS-20260611-0001, baseline 83117c5. SQL migration + companion TS manifest edit (SR-002). New table public.site_discussions (id, serial, workspace_id NOT NULL → workspaces ON DELETE CASCADE, site_id NOT NULL → sites ON DELETE CASCADE, parent_id self-FK ON DELETE CASCADE, kind CHECK in (discussion|announcement|answer), author_id → auth.users ON DELETE SET NULL, title, body NOT NULL, status DEFAULT 'open' CHECK in (open|resolved|archived), created_at, updated_at) with site_discussions_title_root_only CHECK enforcing Locked Call 4 (root posts require title; replies must have NULL title). 5 explicit indexes (site, workspace, parent partial, kind, site+kind+created_at DESC) plus PK. tg_touch_site_discussions_updated_at BEFORE UPDATE trigger; tg_serial_site_discussion BEFORE INSERT trigger calls mbo_generate_serial('DSC', sites.code WHERE id=NEW.site_id) — site-scoped serial format DSC-<site_code>-YYYYMMDD-NNN (Locked Call 5, pattern adapted from tg_serial_agent_fn). RLS enabled with 3 policies: site_discussions_ws_read (SELECT via is_workspace_member), site_discussions_ws_write (ALL via can_write_workspace), site_discussions_svc_all (ALL TO service_role) — Locked Call 2 (Q6=A). GRANT SELECT/INSERT/UPDATE/DELETE to authenticated; GRANT ALL to service_role. serial_registry row inserted: prefix=DSC, table=site_discussions, label=Discussion, introduced_in=SITE_PAGE_VIS-001. graph_node row inserted: node_type=site_discussion, table=site_discussions, is_terminal=false (threading self-edge), has_workspace_id=true (Locked Call 6). 3 graph_manifest edges inserted: site_discussion→workspace (workspace_id, hard, in_workspace), site_discussion→site (site_id, hard, on_site), site_discussion→site_discussion (parent_id, soft, reply_to). Companion src/lib/worldportGraph.ts edit (SR-002 Locked Call 7): added site_discussion + workspace to GraphNodeType union and NODE_TABLE, added 3 matching GRAPH_EDGES entries. Inline DO $smoke$ (Locked Call 8 — SR-004 candidate honored): asserts DSC registry row present, site_discussion graph_node row present, 3 manifest edges from site_discussion; picks live workspace+site, inserts root with title (verifies serial begins DSC-), inserts reply (parent_id), asserts both CHECK constraints fire on illegal inputs (title-on-reply, rootless-title), asserts CASCADE delete propagates to reply, and asserts (public.serial_compliance_check() ->> 'is_compliant')::boolean = true at end. SR-001 N/A pre-apply (table didn't exist); SR-002 satisfied via companion TS edit in same commit; SR-003 satisfied — every column/function/pattern cited to canonical migration in prompt's verification table. 76 pre-existing linter warnings unchanged. After this: DSC serials work end-to-end; resolve('DSC-...') returns Council Protocol Part VI shape once rows exist; threading auditable via soft self-edge; is_compliant=true holds (G2/G10/G11 green); TS↔DB manifest in sync. Unblocks SITE_PAGE_VIS-002 (site-scoped audit + pages FK fixup + site_tab_pending_exceptions table).

  • COUNCIL-007 (Pack closeout — G11 council_seats_unseeded + DISO drift fix): PROMPT-COUNCIL-20260611-0007, baseline c39ba0e. SQL migration + docs. Full canonical body of public.serial_compliance_check() preserved verbatim from 20260611160401_*.sql with only TWO semantic additions: (1) G7 SECDEF allow-list extended with record_resolve_evidence and assert_resolved (the 2 COUNCIL RPCs shipped in COUNCIL-002 — both confirmed SECURITY DEFINER in 20260611173634_*.sql); (2) new G11 block computes council_seats_unseeded from a static VALUES list of the 5 canonical seats (kyle, aria, scanner, schema_auditor, regression_watcher), flagging missing if absent from public.agents or workspace_scoped if present with workspace_id IS NOT NULL. G11 is included in the return object AND in the is_compliant AND-chain — there is no soft mode; constitutional infrastructure must be platform-global. Block 0 (executed BEFORE the function rewrite): UPDATE public.serial_registry SET introduced_in='reserved' WHERE prefix='DISO' AND table_name='dissent_ontology' — resolves pre-existing COUNCIL-001 drift (SWEEP-COUNCIL-DISO-REGISTRY-FIX-001) where DISO was registered as active but dissent_ontology is code-keyed with no serial column; grep-verified zero mbo_generate_serial('DISO',…) callers and zero dissent_ontology.serial readers. Inline DO $smoke$ block ran in-transaction: asserted DISO now reserved, response contains council_seats_unseeded, G11 count = 0, AND is_compliant=true (existence ≠ correctness — the 005b lesson re-applied). COMMENT ON FUNCTION updated. Permissions unchanged (REVOKE PUBLIC, GRANT EXECUTE TO authenticated, service_role). Trigger functions NOT added to G7 allow-list — structural RETURNS trigger carve-out (AUDITOR-002) already covers them. SR-001/SR-002 N/A; SR-003 satisfied — every referenced object cited to canonical migration in the prompt's verification table. 76 pre-existing linter warnings unchanged. Docs updates: created docs/COUNCIL_PACK_RETROSPECTIVE.md (serial RETRO-COUNCIL-20260611-0001) with full pack metrics + handoff brief to SITE_PAGE_VIS; docs/FOUNDATION_ROADMAP.md COUNCIL row flipped 🟡 ACTIVE → ✅ COMPLETE with closed-date 2026-06-11; appended §8 Closeout to docs/COUNCIL_PACK_ROADMAP.md; appended Pack closeout: COUNCIL section to docs/COUNCIL_PR_REVIEW_CHECKLIST.md. Filed SR-FOUNDATION-CANDIDATE-20260611-0004 (spine-function smokes must assert is_compliant=true) — hold for STATE_OF_TRUTH promotion. After this: 5 of 8 foundation packs COMPLETE (SCHEMA_AUDITOR_v2, SERIAL, TRAVERSABILITY, RESOLVER, COUNCIL). G11 now structurally enforces "the Council exists, platform-global, always." Next: SITE_PAGE_VIS.

  • COUNCIL-006 (Resolver v1.2.0 — light up recent_state_changes + open_deliberations): PROMPT-COUNCIL-20260611-0006, baseline 2520168. SQL-only migration; full canonical body of public.resolve(text) preserved verbatim from 20260611144507_*.sql with only two semantic changes: (a) bump resolver_version literal 1.1.0 → 1.2.0 in both return paths; (b) compute and populate the two previously-empty placeholder fields. New DECLARE vars: v_open_delibs, v_recent_changes, v_codex_job_id, v_council_status. Per Locked Call 1, open_deliberations populates ONLY when v_prefix='CJOB'; per Call 2, only when council_status NOT IN ('resolved','deadlocked','human_required','clean','flagged') (terminal + legacy); per Call 4, LIMIT 20 for open_delibs, LIMIT 10 for recent_changes. recent_state_changes is the thin v1 sourced from council_deliberations — surfaces rows for either the matched codex_job OR any deliberation whose reason_detail LIKE '%<serial>%'; each row shape {kind:'council_vote', at, agent_seat, verdict, reason_code, summary} with summary = reason_code ?? agent_seat verdict. STATE_OF_TRUTH SOT-003 will swap the data source to platform_state; response shape is forward-compatible. COMMENT ON FUNCTION updated to COUNCIL-006 (v1.2.0): .... Permissions unchanged (REVOKE PUBLIC/anon, GRANT EXECUTE TO authenticated, service_role). Inline DO $smoke$ block ran in-transaction: picked latest codex_jobs.serial + any graph_node.serial, asserted resolver_version='1.2.0', both fields present + array-typed, and non-CJOB serials return empty open_deliberations. SR-001/SR-002 N/A (no schema, no manifest); SR-003 satisfied — every column reference cited to canonical migration in the prompt's verification table. 76 pre-existing linter warnings unchanged (none introduced). After this: the address bar (RESOLVER-003) and CouncilPanel.tsx (COUNCIL-005) surface live Council activity when resolving CJOB serials; foundation feedback loop RESOLVER↔COUNCIL closes. Unblocks COUNCIL-007 (pack closeout — G11 council_seats_unseeded added to serial_compliance_check).

  • COUNCIL-005 (Deliberation viewer UI — replace CouncilPanel.tsx): PROMPT-COUNCIL-20260611-0005, baseline 46d562b. Frontend-only full replace of src/components/codex/CouncilPanel.tsx per ADR D8. Same prop shape {workspaceId, siteId, sessionIdBase?} keeps src/routes/codex.tsx integration intact (sessionIdBase accepted, unused). Two useQuery calls with refetchInterval: 10_000 (realtime deferred to SWEEP-COUNCIL-UI-REALTIME-001): (1) codex_jobs for site_id=siteId, excluding legacy clean, 20 most recent; (2) council_deliberations filtered to workspace_id + codex_job_id IN (...). Grouped client-side by codex_job_idround → seat (canonical order: kyle, scanner, schemaauditor, regression_watcher). Per ADR D8 read-only — no vote casting, amendment push, override, or delete (filed SWEEP-COUNCIL-ADMIN-UI-001). One card per job: header with PR title/number/URL + status chip (statusTone: resolved=success, deadlocked/human_required=danger, *_amending=warning, round_=info, else neutral); per-round vote rows with verdict chip (verdictTone: approve=success, dissent=danger, flag=warning), reason_code chip, reason_detail text; serial regex /[A-Z]+(?:-[A-Z0-9]+)?-\d{8}-\d{3,}/g extracts click-through chips routing to /codex/resolve?serial=... (RESOLVER-003). Amber _\_amendingfooter says "Awaiting amendment — Brian or Kyle must push manually" since Aria deferred (ADR D7). Red footers forhuman_requiredanddeadlocked terminal states. Empty state explains how to trigger Council review. Loading + error states with Retry button (refetches both queries). All colors via design tokens (text-text-secondary, hover:bg-surface-secondary); no neutral-* literals. SR-001/SR-002/SR-003 N/A (frontend, no SQL, no manifest). After this: first user-visible Council surface — /codex shows live deliberation state per site. Unblocks COUNCIL-006 (resolve placeholder fill).

  • COUNCIL-004 (Wire codex-pr-completedcouncil-deliberate): PROMPT-COUNCIL-20260611-0004, baseline 54700ec. Edge function only — modifies supabase/functions/codex-pr-completed/index.ts; no SQL, no frontend, no new edge function. Two edits: (1) header comment updated to note COUNCIL-004 fire-and-forget dispatch behavior; (2) inserted COUNCIL-004 dispatch block after the agent_dispatch_log insert and before the existing READER-009 merge-dispatch — gated on newStatus === "pr_open" so it fires exactly once per PR open event (PR synchronize re-fire deferred to SWEEP-COUNCIL-AMENDMENT-RETRIGGER-001). Resolves workspace via codex_jobs.site_id → sites(workspace_id) join (Locked Call 3 — codex_jobs has no workspace_id column); orphan codex_jobs (no site or no workspace) log "orphan codex_job (no workspace)" and skip dispatch (filed SWEEP-COUNCIL-ORPHAN-JOB-001). Invocation pattern mirrors READER-009 verbatim: void fetch(SUPABASE_URL/functions/v1/council-deliberate) with Authorization: Bearer ${SUPABASE_SERVICE_ROLE_KEY} and {codex_job_id, workspace_id} body. The void prefix prevents the webhook from awaiting deliberation — webhook returns 200 to GitHub immediately per ADR D3 and GitHub's retry semantics. .then logs structured "council-deliberate dispatched" success with dispatch_status; .catch logs failure but never bubbles up — dispatch failure does NOT fail the webhook (Locked Call 5). Outer try/catch ensures any unexpected error (workspace lookup, env var read) is logged via console.error and the webhook still acknowledges GitHub. Missing SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY logs error and skips dispatch (Locked Call 4 — service-role auth since council-deliberate is verify_jwt = false per COUNCIL-003 but requires service-role internally; HMAC hardening filed SWEEP-COUNCIL-WEBHOOK-HMAC-001). SR-001/SR-002 N/A (no schema/manifest); SR-003 satisfied — every column reference (codex_jobs.id, codex_jobs.site_id, sites.workspace_id, codex_jobs.council_status) cited to canonical migration in the prompt's SR-003 table. After this: end-to-end deliberation loop is wired — PR opens → codex-pr-completed matches codex_job + sets council_status='pending' → fire-and-forget dispatches council-deliberate → state machine advances → resolved/amending/human_required/deadlocked. Resolver Law is now ENFORCED on every real PR (Kyle's seat fires RESOLVE_BYPASS if serials are written without prior resolve). Unblocks COUNCIL-005 (deliberation viewer UI rebuild — CouncilPanel.tsx becomes read-only, vote-history-driven).

  • COUNCIL-003 (Deliberation orchestrator + 4 voting seat modules, v1): PROMPT-COUNCIL-20260611-0003, baseline 4182ca5. New edge function supabase/functions/council-deliberate/ (state-machine-driven per ADR D6 — one invocation advances ONE transition; no setTimeout, no polling, no blocking primitives). Single-file orchestrator: (1) fetchPrDiff(pr_url, pr_number) calls GitHub /repos/{o}/{r}/pulls/{n}/files with GITHUB_TOKEN, regex-extracts touched serials via /[A-Z]+(?:-[A-Z0-9]+)?-\d{8}-\d{3,}/g (soft-degrades to empty diff when GITHUB_TOKEN is absent — seats then APPROVE-by-default); (2) immutable Envelope (Object.freeze + Readonly<…>) carries {workspace_id, codex_job_id, round, snapshot_as_of, source:'council-deliberate', pr_number, pr_url, pr_title} per SITE*SUPERVISOR_INSIGHT — seat modules read only, no shared mutable state; (3) 4 seat modules run in Promise.all: Kyle calls assert_resolved(p_codex_job_id, p_round, p_serials_touched) and emits RESOLVE_BYPASS (hard veto) if all_resolved=false, APPROVE if no serials touched, HUMAN_REQUIRED if RPC errors; Schema Auditor short-circuits APPROVE unless diff touches supabase/migrations/, otherwise calls serial_compliance_check() and emits SERIAL_VIOLATION (hard veto) if non-compliant; Scanner scans migration patches for CREATE TABLE [IF NOT EXISTS] public.<Name> and emits NAMING_VIOLATION (interpretive) if any name is not lowercase; Regression Watcher reads pages rows in the workspace with known_issues IS NOT NULL and emits REGRESSION_RISK (interpretive) when a changed filename contains the page's last path segment (path-heuristic limitation documented in reason_detail; full-fidelity blocked on pages.source_file_path — filed SWEEP-PAGES-SOURCE-FILE-PATH-001); (4) runRound calls pre_council_resolve_batch(p_serials) for snapshot_as_of, then record_resolve_evidence(p_workspace_id, p_codex_job_id, p_round, p_resolved_serials, p_snapshot_as_of) (skipped when zero serials touched), then all 4 seats in parallel, then persistVotes — inserts 4 council_deliberations rows, maps agent_seat → deliberation_serial via the RETURNING clause, then inserts 4 council_votes rows referencing those serials; (5) determineNextStatus(round, verdicts) — any HARD_VETO_CODES member fires (SERIAL_VIOLATION, RLS_GAP, ARCHITECTURE_CONFLICT, MISSING_PAGE_RECORD, HUMAN_REQUIRED, TRAVERSABILITY_VIOLATION, MISSING_SITE_TAB, RESOLVE_BYPASS) → human_required; all APPROVE → resolved; round<3 + any DISSENT → round*${round}\_amending; round=3 + any DISSENT → deadlocked; single UPDATE to codex_jobs.council_status; (6) handler dispatches by council_status: terminal states (resolved/deadlocked/human_required) return immediately; \*\_amendingreturnsawaiting_amendment(Aria deferred per ADR D7);pendingadvances toround_1then runs round 1;round_1/round_2/round_3runs that round.supabase/functions/council-deliberate/deno.jsonmirrorsresolve/deno.json. supabase/config.tomlregisters[functions.council-deliberate] verify_jwt = false(service-role invoked by COUNCIL-004 webhook wiring; HMAC hardening filedSWEEP-COUNCIL-WEBHOOK-HMAC-001). No SQL migration. No frontend. SR-003 satisfied — every RPC signature + column reference cited to canonical migration in the prompt's SR-003 table. v1 ships 1 dissent code per seat (4 of 14); remaining 10 filed SWEEP-COUNCIL-SEAT-EXPANSION-001; LLM interpretive layer filed SWEEP-COUNCIL-LLM-INTERPRETIVE-001. Unblocks COUNCIL-004 (codex-pr-completed → council-deliberate webhook chain).

  • COUNCIL-002 (Resolver Law enforcement primitive): Ships the structural mechanism that lets Kyle's seat fire RESOLVE_BYPASS (PROMPT-COUNCIL-20260611-0002, baseline a18df3d). Single SQL migration, no edge function, no frontend. Six blocks in one transaction: (1) council_resolve_evidence table — serial (UNIQUE, RESOEV-GLOBAL-…), workspace_id NOT NULL REFERENCES workspaces ON DELETE CASCADE, codex_job_id NOT NULL REFERENCES codex_jobs ON DELETE CASCADE, round integer NOT NULL CHECK (round BETWEEN 1 AND 3), subject_serial text NOT NULL, snapshot_as_of timestamptz NOT NULL, recorded_at default now(), optional resolver_version + resolve_found captured per row from public.resolve(); council_resolve_evidence_unique UNIQUE (codex_job_id, round, subject_serial) enforces one row per (job, round, serial) — INSERT ... ON CONFLICT DO NOTHING makes record_resolve_evidence idempotent against webhook retries. Three indexes (codex_job+round, subject_serial, workspace+recorded). RLS enabled; council_resoev_read SELECT via is_workspace_member(workspace_id); council_resoev_svc FOR ALL TO service_role. GRANTs SELECT→authenticated, ALL→service_role per public-schema law. (2) serial_registry row for RESOEV prefix (introduced_in=COUNCIL-002, graph_participant=true, row_type_label Resolver Evidence) with ON CONFLICT (prefix) DO UPDATE; BEFORE INSERT trigger tg_serial_council_resolve_evidence calls public.mbo_generate_serial('RESOEV','GLOBAL') when NEW.serial IS NULL. (3) graph_node row (council_resolve_evidence, is_terminal=true, has_workspace_id=true) + one graph_manifest edge council_resolve_evidence → codex_job via codex_job_id (hard, forward, label evidence_for); subject_serial is intentionally NOT a manifest edge — it's a TEXT pointer to any serial-bearing row, resolved at read time via public.resolve(subject_serial). (4) public.record_resolve_evidence(p_workspace_id uuid, p_codex_job_id uuid, p_round integer, p_resolved_serials text[], p_snapshot_as_of timestamptz) → jsonb — VOLATILE, SECURITY DEFINER, search_path=public. Workspace_id passed explicitly per Locked Call 4 (codex_jobs has no workspace_id column — explicit beats heuristic). Validates all five inputs; for each serial in the array calls public.resolve(v_serial) to capture resolver_version + found, then inserts with ON CONFLICT DO NOTHING; per-iteration BEGIN/EXCEPTION block isolates failures (005b lesson — single bad serial doesn't abort the batch, surfaces via NOTICE). Returns {recorded, skipped_duplicates, total, codex_job_id, round, snapshot_as_of}. REVOKE FROM PUBLIC,anon; GRANT EXECUTE TO authenticated, service_role. (5) public.assert_resolved(p_codex_job_id uuid, p_round integer, p_serials_touched text[]) → jsonb — STABLE, SECURITY DEFINER, search_path=public (Locked Call 3 — read-only, planner can cache within statement). Reads council_resolve_evidence for the (codex_job, round) tuple into v_resolved_serials, partitions p_serials_touched into resolved (in evidence) and bypassed (not in evidence) via FOREACH+ANY, computes v_all_resolved := (array_length(v_bypassed,1) IS NULL). Returns {all_resolved, bypassed, resolved, evidence_count, touched_count, snapshot_as_of, codex_job_id, round} (Locked Call 6 — shape locked so Kyle's seat in COUNCIL-003 can serialize directly into reason_detail). REVOKE FROM PUBLIC,anon; GRANT EXECUTE TO authenticated, service_role. (6) Inline DO $smoke$ block ran in-transaction: created synthetic codex_job with vertical_slug='council_002_smoke' + council_status='pending', pulled one real serial from graph_node, then asserted (a) record_resolve_evidence with 1 serial → recorded=1, (b) duplicate call → skipped_duplicates=1 (idempotency), (c) assert_resolved with the recorded serial → all_resolved=true, (d) assert_resolved with the recorded serial + a fake FAKE-DIFF-20260101-001all_resolved=false and bypassed length=1; each branch RAISE EXCEPTION on failure so a regression would rollback the whole migration. Synthetic codex_job cleaned up at end; evidence rows cascade-delete via FK. Per ADR D6: this is review-layer enforcement only (not write-layer); per ADR Locked Call 7: orchestrator (COUNCIL-003) at pending → round_1 calls pre_council_resolve_batch(touched_serials), captures as_of, then record_resolve_evidence(workspace_id, codex_job_id, 1, touched_serials, as_of), then dispatches seats — Kyle calls assert_resolved and fires RESOLVE_BYPASS if all_resolved=false. SR-001/SR-002/SR-003 active per prompt header: every schema reference cited to canonical migration; new table + manifest edge land in the same migration as the FK; new graph_node + RESOEV registry row land in the same migration so RESOLVER-006 G10 (resolver_unreachable_tables) stays green. No RESOLVER primitive (resolve, pre_council_resolve_batch, graph_serials_by_ids) modified. No frontend/edge changes. 76 pre-existing linter warnings unchanged (none introduced — both new functions set search_path=public; service_role USING (true) mirrors existing COUNCIL-001 *_svc policies). Rollback per prompt §"Rollback Plan" — drop both RPCs, drop trigger + trigger fn, delete manifest edge + graph_node row, delete RESOEV registry row, DROP TABLE CASCADE. After this: Resolver Law has executable structural primitive; COUNCIL-003 (orchestrator + 4 seat modules) is unblocked — Kyle's seat module is a thin caller of assert_resolved.

  • COUNCIL-001 (schema spine, state machine, agent seeding): Ships the executable schema for the Council deliberation engine (PROMPT-COUNCIL-20260611-0001, baseline 3a9fa77). Split into two migrations because ALTER TYPE ... ADD VALUE values can't be used in the same transaction that adds them: (1) extends codex_council_status enum with 9 state-machine values (round_1, round_1_amending, round_2, round_2_amending, round_3, round_3_amending, resolved, deadlocked, human_required) preserving legacy pending/clean/flagged per Locked Call 1; (2) the 12-block spine — dissent_ontology (text PK, 14 codes seeded verbatim from Council Protocol Part V with enforcer_seat + kyle_veto flags), council_deliberations (per-vote audit log, FK to codex_jobs ON DELETE SET NULL, CHECK constraint enforcing dissent ⇒ reason_code IS NOT NULL), council_votes (structured decision record with superseded_by self-FK chain and a partial unique index on (codex_job_id, round, agent_seat) WHERE superseded_by IS NULL enforcing one active vote per seat per round), council_amendments (table ships per Locked Call 6 even though Aria auto-amend is deferred per D7 — v1 records manual amendments). All 4 tables: workspace_id NOT NULL REFERENCES workspaces ON DELETE CASCADE, RLS enabled, is_workspace_member read policy + service_role full policy, GRANTs to authenticated+service_role per public-schema law. dissent_ontology is platform-global (no workspace_id) with TO authenticated USING (true) read. Block 6 relaxes agents.workspace_id to nullable and rewrites ws_read (workspace_id IS NULL OR is_workspace_member(...)) and ws_write (workspace_id IS NOT NULL AND can_write_workspace(...)) so platform-global seats are readable by all authenticated users but only writable by service_role per ADR D4 + Locked Call 4. Block 6.5 (added at runtime — not in original prompt): the first attempt failed inline smoke (c) with found 3 because two pre-existing workspace-scoped agents squatted on the canonical slugs (kyle = kyle_base44/build_engineer, aria = aria_perplexity/research_lead in workspace 24b0d85a…) and ON CONFLICT (slug) DO NOTHING silently skipped them; resolved by renaming the legacy rows to kyle_legacy_ws / aria_legacy_ws (filed SWEEP-COUNCIL-LEGACY-SLUG-DEPRECATE-001 to evaluate full removal once any callers migrate). Block 7 idempotently adds agents_slug_key UNIQUE (slug) constraint via DO block and seeds the 5 canonical platform-global Council seats (kyle/aria/scanner/schema_auditor/regression_watcher) with workspace_id=NULL, provider='council'. Block 8 flips DELIB/VOTE/AMEND serial_registry rows from reserved → COUNCIL-001 with graph_participant=true and inserts DISO as graph_participant=false (text-PK table, not graph-resolved). Block 9 adds 4 graph_node rows (council_deliberation non-terminal, council_vote + council_amendment terminal, all has_workspace_id=true; dissent_ontology_code terminal has_workspace_id=false). Block 10 declares 7 graph_manifest edges (deliberation→codex_job soft, vote→deliberation hard, vote→codex_job hard, amendment→deliberation hard, amendment→codex_job hard, deliberation→dissent_code soft, vote→dissent_code soft). Block 11 installs 3 BEFORE INSERT serial triggers (tg_serial_council_deliberation/vote/amendment) calling public.mbo_generate_serial(prefix, 'GLOBAL') for DELIB-GLOBAL-… / VOTE-GLOBAL-… / AMEND-GLOBAL-… format. Block 12 inline smoke ran in-transaction and asserted (a) state machine accepts pending → round_1 → round_1_amending → round_2 → resolved, (b) enum rejects unknown value totally_not_a_real_status (per Brian's ratification — the test that proves enforceability; transition-jump rejection is the orchestrator's job in COUNCIL-003), (c) exactly 5 platform-global seats present — all three passed. POST verification: enum has all 12 values; 4 council tables RLS-enabled; 14 dissent codes seeded; serial_registry shows 4 rows (DELIB/VOTE/AMEND/DISO all introduced_in=COUNCIL-001, graph_participant=true except DISO); 4 graph_nodes + 7 graph_manifest edges; agents.workspace_id is_nullable='YES'; 5 platform-global seats present. SR-003 satisfied — every reference cited in prompt header to canonical migration. SR-001/SR-002 sweeps deferred to CI (TS worldportGraph.ts NODE_TABLE update for the 4 new node types filed as SWEEP-COUNCIL-TS-MANIFEST-001 if drift detected). 74 pre-existing linter warnings unchanged (none introduced by this migration). Rollback per prompt §"Rollback Plan" — drop triggers/functions, delete manifest/graph_node rows, restore serial_registry, delete seeded seats, restore agents NOT NULL + original policies, drop 4 tables CASCADE; enum values cannot be removed without recreating the type. After this: schema spine LIVE; state machine has executable values; 5 Council seats constitutionally present; 14 dissent codes queryable; COUNCIL-002 (Resolver Law primitive) is unblocked.

  • COUNCIL-000 (pack opens): Constitutional commit for the COUNCIL pack — documentation-only, no SQL, no edge functions, no frontend (PROMPT-COUNCIL-20260611-0000, baseline a7435b8, ratified 2026-06-11 11:36 CDT). Four artifacts shipped: (1) docs/COUNCIL_ADR.md new (ADR-COUNCIL-20260611-0001) locking D1–D9 corresponding to the nine ratified design questions — D1 one council-deliberate edge function with 4 internal seat modules, D2 rule-based v1 seats (no LLM in deliberation path; 8 hard veto codes + 6 interpretive), D3 webhook-driven via existing codex-pr-completed, D4 migration-seeded platform-global agents (workspace_id IS NULL, requires relaxing current NOT NULL + RLS update + 5 seed rows), D5 pack order SQL→law→orchestrator→wiring→UI→placeholder fill→closeout (8 prompts 000–007), D6 RESOLVE_BYPASS via diff-inspection in Kyle's seat (review-layer enforcement, not write-layer), D7 Aria deferred to SWEEP-ARIA-AMENDMENT-001, D8 read-only deliberation viewer fully replacing CouncilPanel.tsx (no sibling tab), D9 G11 (council_seats_unseeded) ships as is_compliant-contributing in COUNCIL-007; six open items closed with explicit answers (synthetic state-machine smoke + invalid-jump rejection assertion, github_mcp_direct with GITHUB_DISPATCH_TOKEN fallback verified pre-COUNCIL-003, full panel replace, state-machine-driven execution model non-negotiable, agents.workspace_id relax sequence, Site Supervisor forward-context insights folded into COUNCIL-003/006 headers); three additional considerations ratified (COUNCIL-006 single prompt not split, no per-workspace flag in v1 → SWEEP-COUNCIL-WORKSPACE-FLAG-001, rule-based v1 sufficient — revisit after 20-30 deliberations → SWEEP-COUNCIL-LLM-INTERPRETIVE-001); seven deferred items filed (Aria amendment, LLM interpretive seats, Brian admin UI SWEEP-COUNCIL-ADMIN-UI-001, per-seat edge isolation, Postgres-trigger RESOLVE_BYPASS, workspace flag, registerCodeReaderFleet removal SWEEP-COUNCIL-DEPRECATE-FLEET-REGISTRAR-001). (2) docs/COUNCIL_PACK_ROADMAP.md new (ROADMAP-COUNCIL-20260611-0001) — status ACTIVE, §2 pack composition table for all 8 prompts, §6 ratification form filled with ✅ Option A for Q1–Q9, closing line records 2026-06-11 11:36 CDT ratification. (3) docs/FOUNDATION_ROADMAP.md §2 pack table — COUNCIL row flipped NOT_STARTED → 🟡 ACTIVE — ADR locked 2026-06-11, prompt estimate ~10–12 → 8 prompts (000–007), date started 2026-06-11, narrative replaced with ADR/roadmap pointers + headline decisions. (4) docs/COUNCIL_PR_REVIEW_CHECKLIST.md — pack opening note appended below the existing two-gate map / RESOLVER closeout note, confirms PRE-001..006 cover COUNCIL prompts (no new gates added at pack open), G11 ships in COUNCIL-007, plus two reviewer heads-ups: COUNCIL-001 agents.workspace_id NULL relaxation + RLS update riskiest part, COUNCIL-003 state-machine-driven execution model (persist + return immediately, 30-min round timeout is wall-clock at function entry on subsequent invocations — reject blocking patterns). Pre-flight verification: SR-001/SR-002/SR-003/PRE-002/PRE-004 N/A (no triggers, no manifest, no SQL, no schema); PRE-003 baseline a7435b8. POST gates: POST-001..POST-005 documentation existence/serial/status/checklist content verified by file writes; POST-006 this CHANGELOG entry; POST-007 schema auditor unchanged (no SQL in this prompt — pre-existing is_compliant baseline preserved). After this: COUNCIL pack constitutional surface LOCKED; any deviation from D1–D9 in COUNCIL-001..007 requires explicit ADR amendment; COUNCIL-001 drafts next.

  • RESOLVER-006 (pack closeout): Extends public.serial_compliance_check() with G10 (resolver_unreachable_tables) as is_compliant-contributing and extends the G7 SECDEF allow-list with the three new RESOLVER functions (resolve, graph_serials_by_ids, pre_council_resolve_batch) — pack closes (PROMPT-RESOLVER-20260611-0006, baseline 248bc97). SQL-only CREATE OR REPLACE FUNCTION; canonical body from 20260610220415_*.sql preserved verbatim with two minimal additions: (a) 3 names appended to the G7 allow-list IN (...) literal so the new SECDEF functions stop registering as secdef_authenticated_leaks false positives, and (b) a new G10 block — v_resolver_unreachable jsonb variable declared, a LEFT JOIN public.serial_registry sr → public.graph_node gn ON gn.table_name = sr.table_name WHERE sr.introduced_in <> 'reserved' AND sr.graph_participant = true AND (gn.table_name IS NULL OR gn.node_type IS NULL) selector building {prefix, table_name, row_type_label, reason} findings (reason ∈ no_graph_node_row | graph_node_table_mismatch | unknown), the result wired into jsonb_build_object as resolver_unreachable_tables, and a new clause AND jsonb_array_length(v_resolver_unreachable) = 0 appended to the is_compliant predicate. Scope deliberately narrow per Locked Call 1 — only graph_participant=true rows are gated; RESOLVER-006a's flag explicitly excludes platform infrastructure (Bucket A), tenancy primitives (Bucket B), and deferred-sweep candidates (Buckets C-deferred, D), and the column default is true so future serial-bearing tables cannot bypass G10 without an explicit, documented disposition. G7 allow-list grew by 3, dropped 0 (Locked Call 2): all three new RESOLVER SECDEF functions legitimately need authenticated EXECUTEresolve(text) called by the Codex address bar with user JWT, graph_serials_by_ids(text, uuid[]) called by the resolve edge function with user JWT for hydration + redaction, pre_council_resolve_batch(text[]) primary caller is service-role (COUNCIL deliberation) but authenticated callers may invoke (defensive). G10 finding shape per Locked Call 4 — sample-serial validation is G1's responsibility, not G10's (single responsibility). Permissions reaffirmed: REVOKE ALL FROM PUBLIC; GRANT EXECUTE TO authenticated, service_role. COMMENT ON FUNCTION updated to document the RESOLVER-006 contract. Inline DO-block smoke ran in-transaction and asserted (a) the response contains resolver_unreachable_tables, (b) is_compliant=trueRAISE EXCEPTION on failure so the migration would rollback rather than ship a regression; migration committed cleanly so both held. SR-003 satisfied — every reference in the new body cited to its canonical migration in the prompt header verification table (preserved body from 20260610220415_*.sql lines 1–197; resolve signature from 20260611144507_*.sql lines 6–7; graph_serials_by_ids from 20260611131452_*.sql lines 1–6; pre_council_resolve_batch from 20260611145759_*.sql lines 1–6; serial_registry.graph_participant from RESOLVER-006a; graph_node.table_name/node_type from 20260610213926_*.sql lines 4–14). SR-001/SR-002 N/A (no domain triggers, no manifest changes). PRE-004 idempotency: CREATE OR REPLACE FUNCTION. Rollback: re-apply prior canonical body from 20260610220415_*.sql (without G10 and without the 3 allow-list entries); documentation rollback via git revert. Documentation closeout shipped same commit: docs/RESOLVER_PACK_RETROSPECTIVE.md (new, serial RETRO-RESOLVER-20260611-0001) — 8-prompt summary, 6 lessons learned, deferred-items ledger, pack-closing metrics, full COUNCIL hand-off brief; docs/RESOLVER_PACK_ROADMAP.md §6 closing line replaced to mark pack closed; docs/FOUNDATION_ROADMAP.md §2 pack table — RESOLVER row updated EXECUTING → ✅ COMPLETE, ended 2026-06-11, prompt count 7 → 8, narrative replaced; §5 SWEEP-RESOLVER-ADR-D4-MISMATCH status flipped 🟢 FILED → ✅ RESOLVED — closed by RESOLVER-004 shipping Option B; row-level soft-delete remains deferred to TRAVERSABILITY-SWEEP-BITEMPORAL as originally specified; docs/COUNCIL_PR_REVIEW_CHECKLIST.md carries the appended "Pack closeout: RESOLVER (closed 2026-06-11)" note referencing the retrospective serial. After this: RESOLVER pack closed; G10 ACTIVE in serial_compliance_check; is_compliant=true preserved through close; the Resolver Law is now executable AND structurally enforced; COUNCIL pack (pack 5) is unblocked.

  • RESOLVER-006a: Ships serial_registry.graph_participant triage + Bucket C graph_node backfill — pre-closeout prerequisite for RESOLVER-006 G10 (PROMPT-RESOLVER-20260611-0006a, baseline 1bd3700). SQL-only single migration with three semantic blocks in one transaction (Locked Call 6): (1) ALTER TABLE public.serial_registry ADD COLUMN IF NOT EXISTS graph_participant boolean NOT NULL DEFAULT true, ADD COLUMN IF NOT EXISTS graph_participant_reason text — default true inversion (Locked Call 1) so future serial-bearing tables cannot accidentally bypass G10; COMMENT ON COLUMN documents the fixed reason vocabulary at the schema layer (Locked Call 7). (2) Five UPDATE statements keyed by prefix (Locked Call 5) dispose all 26 RESOLVER-006-pre-flight violations across four buckets per Brian's 2026-06-11 10:44 CDT triage — Bucket A platform_infra (7: TRVN,TRVS,GEST,GSCAN,MVRL,ALOG,FRSH), Bucket B tenancy_primitive_adr_d12 (5: WS,WMEM,ACC,INV,APIK — TRAVERSABILITY ADR D12), Bucket C deferred deferred_sweep (5: BCAT,BPT,SNIP,ESCR,TDEP → SWEEP-RESOLVER-C-DEFERRED-001), Bucket D deferred_sweep_payos_graph (5: COMP,RSNAP,VSNAP,VLVR,SPARM → SWEEP-PAYOS-GRAPH-001), plus an explicit no-op preserving true for the 4 Bucket C real participants (PG,KSESS,IREG,ATL) for auditability. (3) INSERT INTO public.graph_node backfills 4 rows for the promoted participants — content_node→pages, agent_session→kyles_sessions, integration_node→integrations_registry, signal_node→atlas_signals — all is_terminal=true (Locked Call 3 — edges deferred to SWEEP-RESOLVER-PARTICIPANT-EDGES-001, no manifest/trigger work in this prompt) with has_workspace_id=true for the three workspace-scoped tables and false for integrations_registry only (Locked Call 4 — verified platform-global in types.ts); ON CONFLICT (node_type) DO NOTHING for idempotency. Inline DO-block smoke ran in-transaction and emitted NOTICE total_active=78 participants=56 excluded=22 remaining_g10_violations=0 (the equation RESOLVER-006 G10 will gate on); vocabulary integrity check used RAISE WARNING (not EXCEPTION) so unrecognized reasons would surface in logs without blocking — zero warnings emitted. POST-flight verified: both columns present with correct types/defaults, disposition counts exact (NULL=56 / deferred*sweep=5 / deferred_sweep_payos_graph=5 / platform_infra=7 / tenancy_primitive_adr_d12=5), all 4 backfill rows landed with correct has_workspace_id, remaining_violations=0, zero rows with bad-vocabulary reasons. Pre-existing is_compliant=false baseline (carrying secdef_authenticated_leaks for graph_serials_by_ids/pre_council_resolve_batch/resolve from RESOLVER-002/005) was NOT touched by this migration — orthogonal to G10 and tracked separately. SR-001/SR-002 N/A (no domain triggers, no manifest changes). SR-003 satisfied — no dynamic SQL; every referenced object cited to its canonical migration in the prompt header (serial_registry columns from 20260609183047*_.sql, graph*nodecolumns from20260610213926*_.sql). PRE-004 idempotency: ADD COLUMN IF NOT EXISTS×2,ON CONFLICT DO NOTHINGon backfill, prefix-keyed UPDATEs are repeatable. Rollback:DELETE FROM public.graph_node WHERE node_type IN ('content_node','agent_session','integration_node','signal_node');thenALTER TABLE public.serial_registry DROP COLUMN IF EXISTS graph_participant, DROP COLUMN IF EXISTS graph_participant_reason;. After this: RESOLVER-006 G10 can re-apply reading WHERE graph_participant = true; the 26-row hit drops to 0; pack closes.

  • RESOLVER-005: Ships public.pre_council_resolve_batch(p_serials text[]) → jsonb — the COUNCIL hand-off RPC (PROMPT-RESOLVER-20260611-0005, baseline e93634f). SQL-only migration; no edge function, no frontend changes (COUNCIL deliberation calls server-side as service*role per Locked Call 1; HTTP wrapper deferred until an external caller emerges). LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path=public. Returns an envelope {as_of, resolver_version, input_count, resolved_count, not_found_count, items: {serial: ResolvedSerial}} — counts at top level so callers reason about partial failures in O(1); single as_of := now() anchors all per-serial resolves to one snapshot moment for replay/audit. items is a JSONB object keyed by serial (O(1) caller lookup; last-occurrence-wins on duplicate keys per Locked Call 2 — caller dedupes upstream). Each item is a full ResolvedSerial (Locked Call 3 — not a summary; COUNCIL gets dangling edges, redacted counts, everything resolve() returns, no second round-trip). Hard cap 100 serials per call (Locked Call 4); over-cap returns {error:'batch_size_exceeded', hard_cap:100, items:{}}. NULL and empty input short-circuit to a clean empty envelope. Per-serial failure isolation (Locked Call 5): each public.resolve(v_serial) is wrapped BEGIN ... EXCEPTION WHEN OTHERS so a bad serial yields a synthetic {serial, found:false, reason:'resolve_call_failed', error: SQLERRM, resolver_version} entry instead of aborting the batch (distinct from the 005b failure mode — no dynamic SQL here, only a static composed function call, so any exception is a true runtime issue worth preserving in SQLERRM). resolver_version envelope field set from the first successful resolve() response (currently '1.1.0' per RESOLVER-004). SR-003 satisfied trivially — no dynamic SQL, no format(), no EXECUTE; only external reference is public.resolve(text) whose signature is cited in the prompt header to migration 20260611144507*\*.sqllines 6–7. Permissions:REVOKE EXECUTE FROM PUBLIC, anon; GRANT EXECUTE TO authenticated, service_role. Inline DO-block smoke ran in-transaction: gathered up to 3 real serials from non-reserved serial_registrytables plus one known-badZZZZ-FAKE-20260101-001, called the batch, asserted input_countmatched, envelope had{as_of, items, resolved_count, not_found_count}, itemswasjsonb_typeof='object', and the known-bad serial surfaced with found IS NOT TRUE— migration committed cleanly so smoke passed. Performance gate per roadmap: < 500ms p99 for batch of 20 (function is O(N) overpublic.resolve()at ~12.7msgraph_walkbaseline → ~250ms expected for N=20; if production breaches the gate, file SWEEP for parallel CTE walks or a combined query). Rollback:DROP FUNCTION IF EXISTS public.pre_council_resolve_batch(text[]);. RESOLVER pack now has its COUNCIL hand-off surface — one snapshot, one call, deterministic map keyed by serial.

  • RESOLVER-004: Surfaces dangling edges in resolve() and the Codex address bar (PROMPT-RESOLVER-20260611-0004, baseline d5e10c4). SQL: CREATE OR REPLACE FUNCTION public.resolve(p_serial text) → jsonb bumped to resolver_version='1.1.0' — confirmed outbound_edges/inbound_edges now explicitly filter status <> 'dangling', and two new sibling arrays dangling_outbound / dangling_inbound carry first-class scanner-flagged broken edges (one SELECT each over public.graph_edge_status filtered by status='dangling' and end-side split_part(edge,'→',N)=v_node_type). Each dangling row carries {id, edge, to_id|from_id, to_node_type|from_node_type, status:'dangling', suggestion_reason, last_seen_at} mapped to live columns (ges.id, ges.suggested_target_id, ges.source_id, ges.suggestion_reason, ges.updated_at) per SR-003 — prompt-draft names edge_serial / last_scanned_at / scanner_note were rejected as non-existent and replaced with their canonical equivalents. Dangling rows do NOT hydrate serials (the broken side is by definition unresolvable), so the RESOLVER-002 edge function's hydrateAndRedact() walks outbound_edges/inbound_edges only and the dangling arrays pass through untouched (no edge-function changes shipped). Comment rewritten to start with RESOLVER-004 (v1.1.0):. Grants restated (REVOKE … FROM PUBLIC, anon; GRANT EXECUTE TO authenticated, service_role). Inline DO-block smoke runs in-transaction: picks a sample serial, calls resolve(), asserts resolver_version='1.1.0' and that both dangling fields exist as JSON arrays — migration committed cleanly so smoke passed. Frontend: (1) src/lib/resolve.functions.ts extended with DanglingEdge type and two new array fields on ResolvedSerialResponse, plus a { forceFresh?: boolean } opt on resolveSerial() that appends ?_t=<ts> cache-buster (ADR D4 mitigation — heal re-resolves bypass the 60s edge-fn memory cache); shortCircuit() now emits empty dangling_outbound/dangling_inbound for shape consistency. (2) src/components/codex/ResolvedPanel.tsx adds DanglingEdgesCard rendered between the redaction banner and the confirmed-edges grid — warning-toned card titled Broken Edges (N), struck-through uuids, direction icons, a Run Heal button that calls supabase.functions.invoke("graph-heal-scan", { body: { workspace_id } }) using useWorkspace() (same shape as daily-letter-generate), spinner during heal, sonner toasts on success/error, calls back into onHealComplete. Card auto-hides when both arrays are empty. (3) src/routes/codex.resolve.tsx adds a refreshKey state bumped by onHealComplete; the useEffect keys on [serial, refreshKey] and passes forceFresh: refreshKey > 0 so post-heal re-resolves get a fresh response. No new edge function — graph-heal-scan reused as-is per Locked Call 5; per-row heal targeting filed as future SWEEP. SR-003 verification table in the prompt header cites every column to its canonical migration (20260531134542_*.sql lines 4–14 + 20260610190214_*.sql line 39 for the extended status CHECK). Rollback: re-apply migration 20260611125637_*.sql to restore v1.0.0; revert the three frontend files. Dark matter visible: the operator now sees scanner-flagged broken edges in the same address-bar response that shows confirmed ones, with one-click heal.

  • RESOLVER-003: Ships the Codex address bar — first user-visible TRAVERSABILITY surface (PROMPT-RESOLVER-20260611-0003, baseline 13fb2f4). Frontend only, no SQL, no edge function changes. Four files added: (1) src/lib/resolve.functions.ts — helper resolveSerial(serial) that mirrors the ADR D5 locked ResolvedSerialResponse shape verbatim (no transformation layer; shape drift surfaces loudly per Council Protocol Part VI), short-circuits malformed serials client-side with regex SERIAL_RE kept in lockstep with RESOLVER-002's edge regex, calls GET ${VITE_SUPABASE_URL}/functions/v1/resolve/v1/:serial with the user's supabase.auth.getSession() access_token in Authorization: Bearer, maps 401 → unauthorized throw, 400 → malformed short-circuit, !ok → resolve_failed: <error> throw; (2) src/components/codex/AddressBar.tsx — single pinned input, upper-cases on submit, soft warning when shape fails client regex but still submits (server confirms), Enter-to-submit; (3) src/components/codex/ResolvedPanel.tsx — renders identity block (row_type_label + serial + table_name + id), found/reason chip, workspace-scoped chip, amber redacted_edge_count banner per ADR D3, outbound/inbound edge cards with clickable EdgeRow that calls onNavigate(targetSerial), dashed-border placeholder card naming SOT-003 / COUNCIL-003 as the dependency packs per ADR D5/D6, footer with resolved_at + resolver_version; (4) src/routes/codex.resolve.tsx — TanStack file-route mounted at /codex/resolve (flat dot-separated convention matches the rest of src/routes/), URL-driven via Zod-validated ?serial= search param, useEffect re-resolves on serial change with cancel guard, navigate({ search: { serial } }) so browser back works and links are shareable, head() sets per-route title. ADR honoring: D1 single endpoint hit, D3 redaction banner shown honestly, D5 found/reason chip differentiates malformed_serial/unknown_prefix/row_not_found, D6 placeholder fields shown as known-incomplete. Explicitly NOT shipped: autocomplete, recents/breadcrumbs, dangling-status UI (RESOLVER-004), mobile polish beyond the responsive grid. Rollback: rm the four files; routeTree.gen.ts regenerates clean.

  • RESOLVER-002: Ships HTTP wrapper /functions/v1/resolve/v1/:serial plus helper RPC public.graph_serials_by_ids(p_node_type text, p_ids uuid[]) → jsonb (PROMPT-RESOLVER-20260611-0002, baseline 41643c6). Helper RPC is LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path=public; resolves (node_type → table_name, has_workspace_id) from public.graph_node then batch-selects {id: {serial, workspace_id|null}} via format('… public.%I …', v_table_name) with EXECUTE … USING $1; degradation-path EXCEPTION WHEN OTHERS / RAISE WARNING (not NOTICE) returns '{}'::jsonb so the edge function still produces a valid resolved response with to_serial=null for that neighbor (distinct from the 005b ghost-column failure mode — runtime/transient only, not structural). Permissions: REVOKE EXECUTE FROM PUBLIC, anon; GRANT EXECUTE TO authenticated, service_role. SR-003 verification table in the prompt header cites every schema reference (graph_node.node_type/table_name/has_workspace_id from migration 20260610213926_*.sql lines 4–14; public.resolve(text) from migration 20260611125637_*.sql lines 6–7; per-table id uuid / serial text columns rely on Schema Auditor G1 invariant, currently is_compliant=true). Edge function supabase/functions/resolve/index.ts is the thin HTTP wrapper per ADR D2: parses serial from /v1/:serial (regex ^[A-Z]+(-[A-Z0-9]+)?-\d{8}-\d{3,}$), three-tier auth per ADR D3 (anon → 401; service-role key escape hatch → unredacted; authenticated → auth.getUser(token) + workspace_members lookup), calls public.resolve() via caller-JWT-bound client so RLS audits log the real principal (service-role uses service-role key), then runs single-pass hydrateAndRedact() — collects (node_type, id) pairs from outbound+inbound edges, one graph_serials_by_ids RPC per distinct node_type, populates to_serial/from_serial and strips cross-workspace edges while incrementing redacted_edge_count (ADR D3 — no {redacted:true} placeholder, count is the only signal; service-role skips redaction so count stays 0). 60s per-instance memory cache per ADR D4 keyed by ${serial}::${cache_suffix} where suffix is sorted workspace-id join (service-role uses literal "service_role") so workspace-scoped and service-role responses never collide; emits X-Resolver-Cache: HIT|MISS and Cache-Control: (public|private), max-age=60. HTTP semantics per ADR D5: malformed → 400, missing auth → 401, OPTIONS → 204, non-GET → 405, RPC error → 500, all other outcomes (including found:false with reason: unknown_prefix|row_not_found) → 200. CORS allows * + GET, OPTIONS. supabase/functions/resolve/deno.json mirrors graph-readiness-report. supabase/config.toml registers [functions.resolve] verify_jwt = false — we do our own JWT validation in resolveAuthCtx() so that the malformed-serial and service-role-key paths can return clean errors without Supabase's auto-401 intercepting (consistent with kyle-api, graph-heal-scan precedent; graph-readiness-report's verify_jwt=true is the exception, not the rule for service-role-friendly endpoints). Rollback: DROP FUNCTION IF EXISTS public.graph_serials_by_ids(text, uuid[]); and supabase functions delete resolve (no prior version at baseline). RESOLVER-001's NULL to_serial/from_serial baseline now hydrated at the edge layer; redacted_edge_count finally populated per ADR D3.

  • RESOLVER-001: Ships public.resolve(p_serial text) → jsonb — the constitutional primitive for Council Protocol Law 3 (PROMPT-RESOLVER-20260611-0001, baseline 83c7027). Composes graph_node_by_serial() with a 1-hop edge expansion over graph_edge_status × graph_manifest (filtered by edge_direction IN ('forward','both') outbound / ('reverse','both') inbound; status <> 'dangling'; node_type end-match) and returns the locked Council Protocol Part VI ResolvedSerial shape: identity (node_type, table_name rendered as public.<t>, id, row_type_label, is_terminal, has_workspace_id), outbound_edges / inbound_edges (1-hop only; to_serial/from_serial NULL in v1 per Note A — hydration deferred to RESOLVER-002), redacted_edge_count (ADR D3 RPC-zero; filled at edge-function layer in RESOLVER-003), and named placeholders recent_state_changes (SOT-003) / open_deliberations (COUNCIL-003), plus resolved_at and resolver_version='1.0.0'. Reason derivation for found=false: malformed_serial (null/empty), unknown_prefix (no serial_registry row with introduced_in <> 'reserved'), else row_not_found. LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path=public. Permissions: REVOKE … FROM PUBLIC, anon; GRANT EXECUTE TO authenticated, service_role (verified via pg_proc.proacl: {authenticated=X/postgres, service_role=X/postgres}, no anon, no PUBLIC). SR-003 satisfied — every column/table/function reference cited to its canonical migration source in the prompt header verification table. Inline DO-block smoke test ran during the migration transaction (PL/pgSQL loop variant per prompt escape hatch, with undefined_column/undefined_table guard), enforcing found=true, resolver_version='1.0.0', and presence of all locked fields — migration committed cleanly so smoke passed. POST-001 signature confirmed (args='p_serial text', returns='jsonb', proconfig=[search_path=public]); POST-003 comment present (starts with RESOLVER-001:); POST-004 search_path locked. POST-008/008b functional verification deferred to authenticated-context check (the introspection role intentionally lacks EXECUTE per POST-002). No edge function in this prompt (RESOLVER-002 wraps). Rollback: DROP FUNCTION IF EXISTS public.resolve(text);.

  • RESOLVER-000 (canonical alignment): Aligned the three documentation artifacts to the canonical RESOLVER-000 prompt text verbatim. docs/RESOLVER_ADR.md rewritten to canonical §0–§6 structure (Purpose, Context, Decisions D1–D7 with Alternatives/Rationale/Consequence, Verification Flags Closed — dangling status confirmed in migration 20260610190214 — Open Items including SOT-003 / COUNCIL-003 named pack dependencies, References, Change Log). D3 codifies the redacted_edge_count field. SR-003 wording in docs/FOUNDATION_ROADMAP.md §5.5 replaced with locked "Dynamic SQL Column Reference Discipline" 4-step procedure ("Violation = automatic POST fail. Source: Kyle ratification 2026-06-10. Established after ghost-column bug caught by POST-008."). SR-003 candidate row in §5 sweep table marked ✅ RESOLVED — promoted to ACTIVE Standing Rule SR-003 in RESOLVER-000 (commit pending). PRE-006 row in docs/COUNCIL_PR_REVIEW_CHECKLIST.md rewritten to canonical wording, verification statement template updated, and a per-pack gate-map row added: RESOLVER (all prompts with SQL or edge functions) | PRE-001..PRE-006 (as applicable) | POST-001..POST-008. No SQL, no edge functions, no src/ changes.

  • RESOLVER-000: Constitution check ratification + ADR + SR-003. All 7 questions ratified with Option A: (D1) one endpoint /resolve/v1/:serial, (D2) RPC core + edge function wrapper, (D3) three-tier auth with workspace-scoped redaction, (D4) 60s edge-function memory cache, (D5) 200+found:false for unknown serials, (D6) SR-003 ships with RESOLVER-000, (D7) both-halves rollback. New document: docs/RESOLVER_ADR.md (serial ADR-RESOLVER-20260610-0001). Standing Rule SR-003 promoted from candidate to ACTIVE in FOUNDATION_ROADMAP.md §5.5. PRE-006 added to COUNCIL_PR_REVIEW_CHECKLIST.md for dynamic SQL schema verification. docs/RESOLVER_PACK_ROADMAP.md status: ROADMAP_DRAFT → ACTIVE. TRAVERSABILITY pack status updated to ✅ COMPLETE in foundation tracker. RESOLVER pack EXECUTING.

  • TRAVERSABILITY-007b: G8 (manifest_coverage_gaps) refined to FK-aware counting per SWEEP-012. Previous version used pg_class.reltuples as a row-count estimate, which false-positively flagged 13 soft edges where every source row had NULL on the FK column. Refined predicate: per-edge dynamic COUNT(*) WHERE fk_col IS NOT NULL. Now flags only true coverage gaps (source rows exist with non-NULL FK AND ledger has zero matching entries). is_compliant=true returned cleanly. TRAVERSABILITY pack TRULY COMPLETE.

  • TRAVERSABILITY-007: Pack-closing migration. Extends serial_compliance_check() with G8 (manifest_coverage_gaps — flags manifest edges with non-empty source tables and zero ledger evidence) and G9 (dangling_edge_summary — informational summary of dangling ledger rows for STATE_OF_TRUTH consumption). Updates G7 SECDEF allow-list to permit graph_node_by_serial and graph_walk (the TRAVERSABILITY-006 read-time primitives are intentionally callable by authenticated; manifest is platform metadata). G8 contributes to is_compliant; G9 is informational and does not. Commits docs/TRAVERSABILITY_PACK_RETROSPECTIVE.md (serial RETRO-TRAVERSABILITY-20260610-0001) as the pre-RESOLVER constitution check. TRAVERSABILITY pack COMPLETE.

  • TRAVERSABILITY-006: SQL-native graph_node manifest table (TRVN prefix) seeding 51 GraphNodeType rows mirroring worldportGraph.ts NODE_TABLE. 4 terminal nodes flagged (site, agent, block, daily_letter); 2 platform-global tables flagged (agent_dispatch_log, codex_jobs). Drops graph_target_table() heuristic. Rewrites run_graph_scanner() and run_graph_scanner_backfill_step() to use graph_node as the single source of truth for node_type → table_name resolution. SWEEP-008 resolved: backfill now correctly handles has_workspace_id=false source tables (passes NULL::uuid to ges_upsert_edge). Ships two read-time traversal primitives: graph_node_by_serial(text) → JSONB descriptor for any serial, and graph_walk(serial, max_hops, node_limit) → JSONB BFS traversal with ADR D3 bounds (default 3/500, hard ceiling 5/5000). Trigger surface (47 wirings, 4 dispatchers) and pg_cron schedules unchanged. SR-002 sweep CLEAN at baseline cd7a70a (75 TS = 75 DB; 0 nulls). Note: pre-flight unique_node_types came back 50 (not 51) — daily_letter is terminal AND has no inbound manifest edges yet, so it doesn't appear in graph_manifest; the 51-row seed still includes it for completeness.

  • TRAVERSABILITY-005c: Drop public._diag_005b table — debug artifact from the 005b discovery chain that surfaced the suggested_target_type ghost-column bug. The fix shipped in 20260610205553 + 20260610205709; this cleans up the diagnostic table that should not have persisted. Added COMMENT ON SCHEMA public documenting the _diag_* no-persist convention. SWEEP-006 (filed in FOUNDATION_ROADMAP.md §5) tracks the broader convention. Smoke run post_005c_smoke (GSCAN-GLOBAL-20260610-006): status=success, duration_ms=81, rows_scanned=1008, error_detail=NULL. Manifest=75 rows preserved.

  • TRAVERSABILITY-005b: Manifest-driven backfill catch-up. New run_graph_scanner_backfill_step(text,text,text,text,text) SECURITY DEFINER helper (service_role only) walks one manifest edge: finds source rows where the FK is populated but no graph_edge_status ledger row exists, and calls ges_upsert_edge() to insert one. Terminal target nodes (site, agent, block) resolved via a small inline fallback since they never appear as a from_node (slated for replacement in TRAVERSABILITY-006). run_graph_scanner(text) rewritten with (a) a corrected dangling sweep that derives target node type from split_part(edge,'→',2) — the prior 005a body referenced a non-existent suggested_target_type column and silently rolled back every run via its outer EXCEPTION WHEN OTHERS handler; (b) a manifest-driven backfill loop over every graph_manifest row with edge_direction IN ('forward','both'); (c) per-iteration BEGIN/EXCEPTION blocks so one bad edge surfaces in error_detail instead of killing the whole run; (d) a 25 000 ms soft time budget that preserves the 30 000 ms hard ceiling. Bootstrap bootstrap_005b (GSCAN-GLOBAL-20260610-003): duration_ms=278, rows_scanned=1008, rows_marked_dangling=0, rows_backfilled=247, error_detail=NULL. Spot check: prompts.site_id IS NOT NULL = 85 = prompt→site ledger rows. 47-trigger surface, helpers, manifest, and both pg_cron jobs unchanged.

  • TRAVERSABILITY-005a: graph_manifest table + AUTO_INFER bidirectional adjacency. New public.graph_manifest table (uniqueness on (from_node,to_node,via_col,table_name)) carrying serial prefix TRVS (TRVS-GLOBAL-YYYYMMDD-NNN, set by tg_graph_manifest_set_serial BEFORE INSERT trigger). New graph_edge_direction enum (forward/reverse/both). Public read RLS (platform-global metadata; matches pg_catalog posture); service_role-only writes via migrations. Seeded all 75 manifest edges verbatim from src/lib/worldportGraph.ts at baseline 21ef21b, every row carrying edge_direction='both' per Kyle directive 2026-06-10 (Q2 Interpretation 3: manifest IS the bidirectional adjacency map shared by scanner and the future graph_walk()). New graph_target_table(text) SECURITY DEFINER helper resolves a node_type → its canonical table name from the manifest (heuristic; replaced by dedicated column in TRAVERSABILITY-006). run_graph_scanner(text) rewritten to a manifest-driven sweep — replaces the hardcoded 9-target CASE from 004 with a dynamic loop over distinct suggested_target_type values in graph_edge_status that issues UPDATE...WHERE NOT EXISTS against the resolved target table. Same signature, return shape, RLS, and service_role-only privileges. Bootstrap run bootstrap_005a executed successfully. Backfill catch-up deferred to TRAVERSABILITY-005b; graph_node_table dedicated column + graph_walk() to TRAVERSABILITY-006. 47-trigger surface (8+25+4+10) preserved; both pg_cron jobs (graph-scanner, freshness-mv-refresh) still alive.

  • TRAVERSABILITY-004: pg_cron graph scanner. New graph_scanner_runs table (mirrors mv_refresh_log shape; serial prefix GSCAN, format GSCAN-GLOBAL-YYYYMMDD-NNN) with workspace-member read RLS and platform-only writes. New run_graph_scanner(text) function — service_role only, SECURITY DEFINER — marks ledger rows as dangling when target row has been hard-deleted from one of 9 high-volume target node types (site, agent, tenant, task, incident, loop, prompt, document, prompt_pack). New pg_cron job graph-scanner registered with */5 * * * * cadence (matches freshness-mv-refresh precedent). Bootstrap run executed on migration apply. Backfill catch-up deferred to TRAVERSABILITY-005; less-trafficked target dangling deferred to TRAVERSABILITY-007 G9. The trigger surface (47 wirings, 4 dispatchers) is unchanged.

  • TRAVERSABILITY-003d: Documents domain trigger migration — closes the 4-domain split. Built tg_ensure_graph_edge_status_row_documents() reusing the ges_upsert_edge(...) helper from 003b. Wired AFTER INSERT/UPDATE triggers on 10 Documents source tables (blueprints, build_chains, context_pointers, design_bridge, documents, github_pull_requests, prompt_pack_documents, prompt_pack_items, prompts, research_vault) maintaining 18 graph edges — including 3 multi-edge dispatchers (documents=4, prompts=4, context_pointers=2). Backfilled via no-op UPDATE on all 10 tables (updated_at where present, else created_at). Pre-flight SR-001 manifest-vs-FK sweep CLEAN (0 gaps, 0 phantoms) at baseline 112f7b2. Identity (003a, 8 triggers), Operations (003b, 25 triggers), Communications (003c, 4 triggers) unchanged. Total: 47 trigger wirings across 4 dispatchers — all 75 manifest edges now actively maintained on write. Unblocks TRAVERSABILITY-004 (pg_cron scanner), -005 (AUTO_INFER), -006 (graph_walk primitives), -007 (Guards G8+G9).

  • TRAVERSABILITY-003c: Communications domain trigger migration. Built tg_ensure_graph_edge_status_row_communications() reusing the ges_upsert_edge(...) helper from 003b. Wired AFTER INSERT/UPDATE triggers on 4 Communications source tables (agent_chat_messages, email_integration_config, email_ingest, telegram_chat_links) maintaining 5 graph edges. Backfilled ledger rows for existing data. Identity (003a) and Operations (003b) unchanged. ~41 of 75 manifest edges now actively maintained on write.

  • TRAVERSABILITY-003b: Operations domain trigger migration. Built tg_ensure_graph_edge_status_row_operations() dispatching to a new ges_upsert_edge(uuid,text,text,uuid,text,uuid,text) helper (service_role only) for clean per-edge UPSERT with ADR D6 dangling-target detection. Wired AFTER INSERT/UPDATE triggers on 25 Operations source tables (agent_action_approvals, agent_dispatch_log, agent_task_assignments, agent_tool_calls, block_installations, browser_sessions, code_health_issues, codex_jobs, deployments, execution_plans, fleet_deployments, fleet_deployment_operations, fleet_templates, fleet_template_versions, incidents, loop_state_transitions, loops, plan_audit_log, revenue_events, revenue_projections, scheduled_operation_runs, scheduled_operations, tasks, tenant_churn_signals, tenant_events) maintaining 44 graph edges. Backfilled via no-op UPDATE (updated_at where present, else created_at, else id=id for 7 timestamp-less tables); populated ledger rows for tables with existing data (agent_tool_call×2=37, block_installation×2=1, revenue_event=5, scheduled_operation=2). Identity domain (003a) function, triggers, and helper untouched. ~36 of 75 manifest edges now actively maintained on write.

  • TRAVERSABILITY-003a: Identity domain trigger migration. Phase 1 schema prep: graph_edge_status.workspace_id made nullable (ADR D2) with RLS ges_select updated to admit workspace_id IS NULL global rows; 'dangling' added to status CHECK constraint (ADR D6); reverse-traversal index idx_graph_edge_status_target_edge on (suggested_target_id, edge) added (ADR D8). Phase 2 triggers: AFTER INSERT/UPDATE on 8 Identity-domain source tables (agentos_connections, agentos_connection_health, agentos_events, agentos_observability_counters, integration_credential_links, integration_events, site_integrations, tenants) write ledger rows via new tg_ensure_graph_edge_status_row_identity() SECURITY DEFINER function. UPSERT per ADR D5; dangling FK targets surface as status='dangling'. Backfill no-op UPDATEs ran on all 8 tables (site_integration→site=20 confirmed, integration_credential_link→site_integration=27, integration_event→site_integration=29, tenant→site=4 unconfirmed). Existing tg_ensure_graph_edge_status_row() (research_vault + documents) untouched.

  • TRAVERSABILITY-002: Expanded src/lib/worldportGraph.ts manifest from 14 → 51 GraphNodeType values and 24 → 75 GRAPH_EDGES across the Identity, Operations, Communications, and Documents domains. All edges derived verbatim from §1 of docs/TRAVERSABILITY_MANIFEST_AUDIT.md at TRAVERSABILITY-001c baseline (31f8d9e) — no inference. NODE_TABLE extended with table mappings for every new node type so Record<GraphNodeType, string> stays exhaustive. Header comment updated to reference ADR D12 and the 001c baseline. AutoInfer* types, edgeKey, and AUTO_INFER_RULES untouched. No duplicate (from,to,via,table) tuples. Sets the stage for the trigger migrations in TRAVERSABILITY-003a/b/c/d.

  • TRAVERSABILITY-001c: FULL REBUILD of docs/TRAVERSABILITY_MANIFEST_AUDIT.md §1 from SQL truth (pg_catalog.pg_constraint query against active serial_registry prefixes at baseline 6d6fd6b). Replaces all three prior memory-driven revisions (001/001a/001b). Four documented overrides applied on top of raw query output: research_vault dual-prefix preservation (AUDIT+RV both A); TDEP junction-table exemption (B); BLK reference-data downgrade (B) with BPT and COMP cascade downgrades; GEST self-reference exclusion (B). Final counts: A=48, B=27 (75 active + 5 reserved). Methodology change: future classification audits are SQL-driven, not manual — Schema Auditor v3 G8 (TRAVERSABILITY-007) will enforce this automatically. ADR D12 unchanged. Only docs/TRAVERSABILITY_MANIFEST_AUDIT.md and CHANGELOG.md touched.

  • TRAVERSABILITY-001b: Reconciled off-by-3 discrepancy in docs/TRAVERSABILITY_MANIFEST_AUDIT.md. SNIP (copy_snippets) and DL (daily_letters) reclassified A→B per ADR D12 (tenancy-only and terminal-root respectively — neither had real entity FKs beyond workspace_id). RPROJ (revenue_projections) stays A but edge column corrected from → workspace to → block (block_id) — the real entity FK on this table. Final classification: A=43, B=32, total=75 active + 5 reserved. §1 and §2 now consistent.

  • TRAVERSABILITY-001a: Revised docs/TRAVERSABILITY_MANIFEST_AUDIT.md to honor the existing worldportGraph.ts convention that workspace_id is tenancy scope, NOT a graph edge. Reclassified 8 tables from Category A to Category B (workspace_members, workspace_accounts, workspace_invites, integrations_registry, agents, api_keys, sites, workspaces). Identity domain shrank from 12 to 4 tables. Added ADR D12 formalizing the convention. Canonical exemption string established for tenancy-only / terminal-root tables. New totals: Category A=42, Category B=33, reserved=5, total=80.

  • TRAVERSABILITY-001: Authored docs/TRAVERSABILITY_MANIFEST_AUDIT.md — classification of all 75 active serial_registry prefixes into Category A (50 — add as graph manifest node), Category B (25 — exempt with documented reason), Category C (0 — all edge cases resolved during classification). Domain split assigned for TRAVERSABILITY-003a/b/c/d trigger migrations: Identity (12 tables), Operations (21 tables), Communications (6 tables), Documents (13 tables). Audit is read-only post-merge.

  • TRAVERSABILITY-000: Authored docs/TRAVERSABILITY_ADR.md — Architecture Decision Record for the TRAVERSABILITY pack. Captures 11 decisions (D1–D11) including: node identifier is (table_name, id) + serial shortcut (no entity_id), workspace_id nullable + CHECK on graph_edge_status for global tables, graph_walk caps at 3 hops default / 5 ceiling / 500 nodes default, soft-deprecation on DELETE without bitemporal columns (sweep filed), UPDATE triggers in scope, trigger migrations split by domain, RPC core + REST wrapper. ADR is read-only post-merge; future changes require new ADR superseding this one.

  • AUDITOR-003: Added summary_string field to public.serial_compliance_check() — single-line human-readable result. Reads '✅ COMPLIANT · N active prefixes, M reserved, 0 drift across 7 guards.' when clean, or '❌ NON-COMPLIANT · ...' enumerating each non-empty drift category by count when not. Closes the SCHEMA_AUDITOR_v2 pack. G5 (TypeScript AST scan) deferred to a separate CI-guards sweep.

  • AUDITOR-002: Added public.serial_registry_exceptions companion table to serial_registry for documented immutable-history allowlists (PK (table_name, prefix_pattern), RLS enabled, SELECT for authenticated, INSERT/UPDATE/DELETE for service_role only). Seeded 19 baseline exceptions: 7 from SERIAL-008 trigger cleanup (DEP, EML, LOOP, PG, PR, RUN, AEV), 11 from SERIAL-008d shared-trigger cleanup (APPR, ASGN, HLTH, AOC, ESCR, LST, PLOG, RPROJ, INV, ECFG, TGLINK), and 1 for legacy RouteOS tasks (TSK). Extended serial_compliance_check() with G6 (HISTORICAL_PREFIX_MISMATCH) — flags sampled rows whose serial prefix maps to a different table in serial_registry. Refined G4 (SCOPE_SEGMENT_DRIFT) to consult the exceptions table so documented historical rows no longer surface. Refined G7 (SECDEF_AUTHENTICATED_LEAK) to skip RETURNS trigger functions structurally — removed the 5 trigger entries from the AUDITOR-001 allowlist. Function now returns 13 fields including the new historical_prefix_mismatch array; is_compliant=true requires all 7 drift arrays empty.

  • AUDITOR-001: Extended public.serial_compliance_check() with three new drift guards. G3 (PREFIXCOLLISION) — finds prefixes called by trigger functions on multiple tables. G4 (SCOPE_SEGMENT_DRIFT) — samples up to 5 recent rows per registry table and surfaces any serial not matching canonical ^[A-Z]+-([A-Z0-9]{2,8}|GLOBAL)-\d{8}-\d+$. G7 (SECDEF_AUTHENTICATED_LEAK) — finds SECURITY DEFINER functions with authenticated EXECUTE, against an allowlist. Helper audit_sample_serials_for_table(text) added (service_role only). is_compliant=true now requires all 6 drift arrays empty. G7 allowlist expanded beyond spec to include pre-existing legitimate authenticated-callable SECDEF fns surfaced by pre-flight (workspace auth helpers, compute* helpers, user-action RPCs, secret hashing, and 5 trigger functions). G4 will surface pre-2026-06-09 immutable-history rows on first call; AUDITOR-002 will introduce serial_registry_exceptions to allowlist them.

  • SERIAL-008f: Two small fixes. (1) Corrected duration_ms math in refresh_mv_platform_freshness_logged() — parenthesize-then-cast so sub-second refreshes log non-zero ms (was logging 0 for any refresh under 1 second due to premature integer cast). (2) Revoked EXECUTE from authenticated on refresh_freshness_tier1-tier4 — closes the same privilege leak the SERIAL-008e follow-up migration closed for the wrapper function. All 5 cron helpers now correctly limited to service_role and pg_cron.

  • SERIAL-008e: Wired pg_cron freshness-mv-refresh job (5-minute cadence) through a logged wrapper public.refresh_mv_platform_freshness_logged(). Each REFRESH now produces an mv_refresh_log row with status (runningsuccess or error), duration, row count, and error_detail on failure. Wrapper does NOT re-raise on failure — the cron job continues, and the error row is the audit trail. Closes the deferral noted in mv_refresh_log table comment from SERIAL-006.

  • SERIAL-008d: Closed shared-trigger format drift. (1) Rewrote mbo_set_serial_tg to pass 'GLOBAL' instead of NULL — fixes 34 SERIAL-001-installed global-scope triggers in one stroke. (2) Created dedicated triggers for the 4 site-scoped SERIAL-001 tables (design_bridge/DSB, email_integration_config/ECFG, site_integrations/SINT, telegram_chat_links/TGLINK) that look up site code via FK. Updated 11 serial_registry.notes to reflect the fix (APPR's note was rewritten in a follow-up because its existing wording didn't match the spec's REPLACE pattern). No backfill of historical scope-less serials. SERIAL pack is now structurally complete.

  • SERIAL-008c: Fixed AGT prefix collision. agentos_connections now mints serials with prefix AGTCONN instead of AGT (which still belongs to agents). New AGTCONN registry row added. Historical AGT-* serials on agentos_connections are immutable broken history and will misroute through Resolver — documented in SERIAL_DICTIONARY footnote (forthcoming).

  • SERIAL-008b: Registry reconciliation round 2. Seeded 11 missing prefixes wired by SERIAL-001's dynamic installer (APPR, ASGN, HLTH, AOC, ECFG, ESCR, LST, PLOG, RPROJ, TGLINK, INV). Closes SWEEP-004 by adding PPI prefix + trigger + backfill for prompt_pack_items. Registry: 79 rows (74 active + 5 reserved). AGT collision and mbo_set_serial_tg NULL-fallback drift remain — see SERIAL-008c and SERIAL-008d.

  • SERIAL-008: Closing pack. (1) Rewrote 7 trigger functions to emit canonical 'GLOBAL' fallback (tg_serial_dep, email_ingest_set_serial, tg_serial_loop, tg_serial_page, tg_serial_pr, tg_serial_sched_run, agentos_events_set_serial). (2) Created public.serial_compliance_check() returning JSONB compliance summary — first permanent State Verifier check. (3) Section 3 skipped: pg_cron unchanged (gate requires explicit confirmation; not present in drop). No backfill of historical serials. Compliance check surfaced 13 previously-unknown unregistered tables with serial columns — filed for new SWEEP item, not auto-handled.

  • SERIAL-007: Populated docs/SERIAL_DICTIONARY.md — Active Prefixes table (62 rows), Reserved Prefixes table (5 rows), How Serials Are Generated, Serial Format Reference, How To Add a New Prefix. Documented derived-site format pattern (FDO, FLV) and the 7 trigger functions with canonical-format drift filed for SERIAL-008.

  • SERIAL-007a: Reconciled serial_registry against live triggers. Seeded 12 pre-SERIAL orphan prefixes (AEV, BSESS, DEP, EML, FDO, FDP, FLT, FLV, LOOP, PG, PR, RUN). Registry now has 67 rows (62 active + 5 reserved). Six trigger functions still use legacy 'GEN' fallback and one uses NULL — both filed for SERIAL-008.

SERIAL-006 — Materialized View Refresh Log

  • Added mv_refresh_log table (12 columns: id, serial, view_name, workspace_id, triggered_by, started_at, completed_at, duration_ms, rows_affected, status, error_detail, created_at).

  • Serial prefix MVRL. Format: MVRL-GLOBAL-YYYYMMDD-NNN.

  • BEFORE INSERT trigger via dedicated tg_mv_refresh_log_set_serial() function (not shared mbo_set_serial_tg — shared function passes NULL site_code, which produces MVRL-YYYYMMDD-NNN; dedicated function passes 'GLOBAL' explicitly).

  • FK: workspace_id → workspaces(id) ON DELETE SET NULL.

  • RLS: authenticated SELECT (workspace_id IS NULL OR is_workspace_member); no write policies — service_role only.

  • serial_registry row for MVRL added.

  • pg_cron wiring deferred to SERIAL-008.

  • feat: SERIAL-005 — Category C cleanup. Added missing TDEP row to serial_registry (gap: SERIAL-002 seed derived from mbo_serial_counters which had no TDEP entries at seed time; column and trigger were live from SERIAL-001, registry was not). Created docs/SERIAL_DICTIONARY.md skeleton with block_dependencies exemption section and junction table convention. All three Category C serialized tables (WS, WMEM, TDEP) now have complete registry coverage. Zero orphan prefixes in mbo_serial_counters.

SERIAL-004 — Schema Auditor Helper Function

Files added:

  • supabase/functions/_shared/serialAuditor.ts — Schema Auditor Council seat helper. Exports schemaAuditor_checkSerialPresence and schemaAuditor_listPrefixesForTable. Read-only queries against information_schema.columns and serial_registry.
  • supabase/functions/_shared/README.md — Establishes the _shared/ shared-utility convention for edge function imports.

Convention established: supabase/functions/_shared/ is the canonical location for TypeScript modules shared across multiple edge functions. Future packs must use this directory for shared utilities (URL imports, Deno-compatible, no side effects).

No schema changes. No application code changes.

Depends on: SERIAL-002 (serial_registry seeded). Used by: COUNCIL pack (Schema Auditor edge function, seat 4).

  • feat: SERIAL-003 — Writer-discipline audit + research_vault set_serial BEFORE INSERT trigger (AUDIT prefix, site-code aware). Refactored siteAudit.core.ts Phase G to drop app-layer mbo_generate_serial RPC and serial: field; trigger mints the value, .select("serial") reads it back. Defensive IF NEW.serial IS NULL OR NEW.serial = '' guard preserves the lingering scheduled-site-audit edge fn assignment until SWEEP-005 (filed in docs/FOUNDATION_ROADMAP.md §5, tangled with audit-core dedup). Full audit table in docs/SERIAL_WRITER_AUDIT.md — all server fns clean, all edge fns clean except the one deferred.

    • follow-up: dispatched trigger by doc_type (AUDIT for audit_report, RV otherwise), aligned fallback to 'GLOBAL', updated serial_registry (new RV row + clarified AUDIT notes).
  • feat: Codex Console at /codex — dispatch form (with smoke-test preset, advanced fields, confirm modal) and live Recent Codex Jobs table with realtime updates, detail viewer, and operator cancel.

  • feat: Enforce graph membership via DB trigger + healing page item viewer & manual site picker.

  • fix: Atlas recompute crashed with column i.title does not exist — incidents table uses serial/summary, not title.

  • feat: Graph-healing scan button now surfaces success/error toasts instead of failing silently.

Changelog

feat: Graph healing follow-ups — unhealed badge, confirmed % in report, heal-forward hook

  • Shell.tsx: "Graph Healing" sidebar item now renders the unhealed-edge count as a warning badge (same pattern as the dashboard orphan badge). Hidden when count is zero; updates on window focus.
  • scripts/run-graph-audit.ts + supabase/functions/graph-readiness-report/index.ts: Added confirmed_fill per edge (sourced from graph_edge_status) alongside raw soft_link_fill. Markdown report shows both columns; verdict line now also surfaces overall confirmed-link trust %. Scoring formula unchanged (additive reporting only).
  • graph-heal-scan: Now accepts { workspace_id, source_table, source_id } for single-node heal-forward in addition to the existing batch mode. Reuses the canonical AUTO_INFER rules — no duplication.
  • New src/lib/healForward.ts: Fire-and-forget helper that invokes graph-heal-scan for a single node; swallows errors so creation flows never block.
  • src/lib/uploadDocument.ts + src/components/research-vault/VaultEntryModal.tsx: Call heal-forward after successful document / vault inserts. New rows immediately land in the ledger as confirmed (deterministic) or unconfirmed+suggestion — never silently empty. Kyle system writers (kyle-session-distill, worldport-brief-generate) intentionally skip heal-forward.

feat: PROMPT-19 Graph healing ledger + research/doc→site healing (flag-unhealed, heal-forward)

  • Migration 20260531134523: New graph_edge_status table — workspace-scoped ledger tracking every soft-edge instance as confirmed, unconfirmed, or not_applicable, with suggested target, evidence, confidence, decider. RLS via is_workspace_member / can_write_workspace; inserts service-role only; unique on (workspace_id, edge, source_id); updated_at trigger.
  • src/lib/worldportGraph.ts: Added AUTO_INFER_RULES registry + AutoInferRule/AutoInferOutcome/AutoInferContext types and edgeKey() helper. Conservative pure-function rules for research_vault→site (site_id set → confirmed; app_tag match → suggested) and document→site (site_id set → confirmed; linked_record_type='site' → confirmed + write; capture_metadata or tags single match → suggested). Ambiguous evidence → unconfirmed, never a silent guess.
  • New edge fn graph-heal-scan (verify_jwt=false, service-role): scans soft-edge source rows in a workspace, applies the rule, upserts ledger rows, and performs deterministic domain writes only when confidence='auto' AND applyDomainWrite=true. Never auto-applies suggested. Returns { totals, per_edge }.
  • New src/lib/graphHealing.functions.ts: Server functions listGraphHealing, confirmGraphEdge (writes domain FK + ledger), markEdgeNotApplicable, rejectEdgeSuggestion, runGraphHealScan, unhealedCount. All gated by requireSupabaseAuth; RLS applies.
  • New /graph-healing route: three buckets (Needs review / Unconfirmed / Confirmed) per edge, with Confirm / Reject / N/A actions and a "Run heal scan" button. Header shows confirmed %.
  • Shell.tsx: New "Graph Healing" nav item under Build.
  • supabase/config.toml: Registered [functions.graph-heal-scan] verify_jwt = false.
  • Partial / follow-up: Shell unhealed-count badge, audit-report confirmed % integration, and heal-forward hook on document/research creation are scaffolded server-side (unhealedCount server fn ready) but not yet wired into the sidebar render and graph-readiness-report — to land in a follow-up.

chore: Run graph readiness audit — populate WORLDPORT_GRAPH_READINESS.md with live numbers

  • New scripts/run-graph-audit.ts (Prompt 18.1): Service-role script that iterates every workspace, reads the graph_audit_* views, and overwrites WORLDPORT_GRAPH_READINESS.md + WORLDPORT_GRAPH_READINESS.json with live numbers. Aggregation + scoring logic copied verbatim from supabase/functions/graph-readiness-report/index.ts so the script and edge function stay identical.
  • package.json: Added graph:audit script (bun run scripts/run-graph-audit.ts).
  • First run summary: WorldPort — needs_attention (64) — avg soft fill 19% — orphans 0 — dangling 0. Soft-link fragmentation is the next phase to address (most soft edges <25% populated); no orphans or dangling FKs.

feat: WorldPort graph readiness audit + canonical graph manifest (foundation only)

  • New src/lib/worldportGraph.ts: Canonical graph manifest — 15 node types and 24 edges (6 hard, 18 soft) derived directly from src/integrations/supabase/types.ts Relationships. Single source of truth for future graph-traversal tools. No invented edges.
  • New migration 20260531124717_graph_readiness.sql: Adds 5 read-only graph_audit_* VIEWS (security_invoker = true, granted to authenticated + service_role): graph_audit_node_counts, graph_audit_soft_link_fill, graph_audit_dangling, graph_audit_orphans, graph_audit_isolated. No domain tables changed. No FK constraints altered. No data mutated.
  • New supabase/functions/graph-readiness-report/index.ts: Member-authed (verify_jwt = true). For a given workspace_id, queries the audit views and returns { node_counts, soft_link_fill, orphans, dangling, isolated_nodes, unenforced_edges, readiness_score, verdict, recommendations }. Read-only.
  • New WORLDPORT_GRAPH_READINESS.md: Human-readable report at repo root; documents the canonical manifest and is overwritten by each audit run.
  • Modified supabase/config.toml: Registered [functions.graph-readiness-report] verify_jwt = true.
  • No Kyle-facing graph-read tools built in this prompt. Foundation + report only — graph-traversal tools come in a later prompt, after the operator certifies the report.

feat: Resumable agent sessions + team author attribution

  • Modified src/routes/agents.$agentId.tsx: Added ?session= search param (zod-validated), History sheet listing the 10 most-recent sessions for this agent+workspace, and a "New" button. Default session id resolves to the latest existing session for the agent so chat history no longer "vanishes" on refresh. <AgentChatInterface> keyed by session id so switching forces a fresh mount.
  • Modified src/lib/agentChat.functions.ts: Resolves author_name from workspace_members.display_name (fallback to JWT name/email local-part), stores it on the user message's metadata.author_name, and prefixes user turns with [Name]: when building the composio history payload so the model can attribute decisions to specific operators.
  • Modified src/components/agents/AgentChatInterface.tsx: Selects metadata and renders the small author label above each user bubble.
  • Modified supabase/functions/kyle-session-distill/index.ts: Transcript now tags operator turns as OPERATOR (Name): and the distill prompt instructs Kyle to attribute decisions/action items to the named operator when clear.
  • Dependency: Added @tanstack/zod-adapter for the search-param validator.

feat: Proactive daily Kyle briefing (scheduled, read-only)

  • Migration: Added kyle_briefing to scheduled_operations.operation_type CHECK and seeded one daily op per workspace (0 12 * * * UTC ≈ 7am CT). Idempotent.
  • New supabase/functions/kyle-briefing-generate/index.ts: Service-role edge function. Gathers open incidents, ready prompts, recent agent_tool_calls, orphan counts (documents/context_pointers/tasks/incidents) and stale vault count for a workspace; calls Anthropic via AI SDK to produce a markdown briefing (Top 3 / Status / What Kyle noticed / Suggested next actions). Upserts a research_vault row (doc_type='briefing', app_tag='@WPT', source_url='mbo://briefing/<date>') — one row per day, never duplicates. Returns { ok, vault_id, highlights_count }. Read-only; never executes any action.
  • Modified src/routes/api/public/scheduler-tick.ts: Added kyle_briefing dispatch branch (same pattern as worldport_brief), invoking the edge function with service-role bearer.
  • Modified supabase/config.toml: Registered [functions.kyle-briefing-generate] verify_jwt = false.

feat: MBO-native action tools for Kyle (via kyle-api, approval-gated)

  • Modified supabase/functions/kyle-composio/index.ts: Registered 8 MBO-native AI SDK tools. Reads (mbo_list, mbo_get) execute immediately by calling the kyle-api edge function with Authorization: Bearer ${KYLE_MBO_API_KEY}. Writes (mbo_create_task, mbo_file_incident, mbo_add_prompt, mbo_link, mbo_vault_append, mbo_update_blueprint) DO NOT execute — they insert a pending row into agent_action_approvals via a shared recordProposal helper, with the full kyle-api call shape stored in proposed_args.mbo_call. entity is constrained to the kyle-api ALLOWED_ENTITIES enum at the tool layer. System prompt updated so Kyle knows it can operate MBO directly but writes still require operator approval.
  • Modified supabase/functions/agent-action-execute/index.ts: On approve, if tool_slug starts with mbo_ and proposed_args.mbo_call is present, the function forwards the call to kyle-api with KYLE_MBO_API_KEY instead of invoking Composio. Result is persisted to agent_action_approvals.result and audited in agent_tool_calls as before. Composio path unchanged for non-MBO tool slugs.
  • Secret: KYLE_MBO_API_KEY (operator generates a key in /integrations/api-keys named kyle-agent, then pastes plaintext into Lovable Cloud secrets — hash stays in api_keys; plaintext never committed or logged).

feat: Write-scope approval gate — human-in-the-loop for Kyle

  • Migration agent_action_approvals: New table with workspace_id/agent_id/session_id, tool_slug/toolkit, risk (write|destructive CHECK), proposed_args (jsonb, redacted), summary, status (pending|approved|rejected|executed|failed CHECK), decided_by/decided_at, result/error. RLS on, is_workspace_member() for SELECT, can_write_workspace() for UPDATE. INSERT is service-role only (no member INSERT policy). Indexed on (workspace_id,status) and (session_id). GRANTs to authenticated + service_role.
  • Modified supabase/functions/kyle-composio/index.ts: Added explicit risk classifier (READONLY_PATTERNS / DESTRUCTIVE_PATTERNS regex). Composio tools are filtered to read-only before passing to generateText. A local propose_action AI SDK tool (zod-validated args) is added — it inserts an agent_action_approvals row (status pending, args redacted via redactArgs) and returns "Proposed — awaiting operator approval." System prompt rewritten so Kyle MUST route any write/destructive action through propose_action. Audit logging untouched.
  • New supabase/functions/agent-action-execute/index.ts (verify_jwt = true): Body { approval_id }. Loads the row via the caller's JWT (RLS enforces workspace), requires status='approved', opens a Composio session, executes the named tool with proposed_args, updates the row to executed + result or failed + error, and writes an agent_tool_calls audit row. Never logs secrets.
  • Modified supabase/config.toml: Added [functions.agent-action-execute] verify_jwt = true.
  • New src/lib/agentApprovals.functions.ts: Three RLS-scoped server fns under requireSupabaseAuthlistPendingApprovals({ workspace_id, session_id? }), approveAction({ approval_id }) (sets approved + decided_by/decided_at, then forwards the caller's bearer to agent-action-execute), rejectAction({ approval_id, reason? }) (sets rejected; only flips rows still in pending).
  • New src/components/agents/PendingApprovals.tsx: For the active workspace + session, polls pending approvals every 5 s. Each card shows the summary, tool slug, a risk chip (warning|danger), and Approve / Reject buttons (worldport primitives). On Approve → calls approveAction → toast result + invalidate. Returns null when empty.
  • Modified src/components/agents/AgentChatInterface.tsx: Renders <PendingApprovals workspaceId={workspaceId} sessionId={sessionId} /> between ToolCallBadges and the input bar — visible inline in the chat for the active session.
  • Modified src/components/worldport/Shell.tsx: Added a small PendingApprovalsBadge next to the bell in the TopBar — workspace-scoped count badge that polls every 10 s and renders nothing when zero. Added useQuery import. Per LOVABLE_RULES.md: "Kyle write/destructive tools route through agent_action_approvals; never auto-execute."

feat: Mobile MBO partner chat at /kyle (installable shortcut)

  • New src/routes/kyle.tsx: createFileRoute("/kyle") — full-screen Kyle chat. Resolves the provider='composio' agent named "Kyle" for the active useWorkspace() (same lookup as KyleLauncher). Layout adapts to useIsMobile(): on mobile, fixed inset wrapper from top to bottom-[64px] with pb-[env(safe-area-inset-bottom)] so the chat input sits above the bottom nav and respects safe-area insets; on desktop, centered max-w-3xl at h-[calc(100dvh-64px)]. Header has a "New" button that generates a fresh session_id and a "History" Sheet that loads up to 10 distinct recent session_ids from agent_chat_messages (most-recent first) with a preview line; tapping one loads that session's messages. Renders the existing <AgentChatInterface> (no fork) keyed by sessionId. Falls back to the same "Kyle isn't set up for this workspace yet" message as KyleLauncher.
  • Modified src/components/agents/AgentChatInterface.tsx: Added optional sessionId and className props. When sessionId is provided, it's used verbatim (the key on the parent re-mounts on change); otherwise the existing crypto.randomUUID() behavior is preserved. className overrides the default h-[60vh] border rounded-lg wrapper so callers can opt into full-flex / full-height layouts. All existing callers (KyleLauncher, etc.) continue to work unchanged.
  • Modified src/components/mobile/MobileBottomNav.tsx: Replaced the "Notes" slot with a "Kyle" tab (lucide Zap) linking to /kyle. Existing tabs (Home, Queue, Tasks, Menu) and styling preserved.
  • Modified public/manifest.json: Added a shortcuts array with { name: "Ask Kyle", short_name: "Kyle", url: "/kyle", description: "Open your WorldPort partner" } so long-pressing the installed MBO app icon offers "Ask Kyle" as a deep link. All existing manifest fields unchanged. No new packages, no service worker, no fork of chat logic — desktop KyleLauncher and mobile /kyle route coexist on the same AgentChatInterface.

feat: Auto-distill Kyle think-sessions into the research vault

  • New supabase/functions/kyle-session-distill/index.ts: Service-role edge function (POST { workspace_id, agent_id, session_id }) that loads all agent_chat_messages for a session (ordered), skips with { skipped: "too_short" } if fewer than 2 messages, builds a compact OPERATOR/KYLE transcript (capped ~24k chars), and produces a structured distillation via @ai-sdk/anthropic + generateText using AGENT_MODEL (no tools, plain text). The model output follows a fixed shape — # Title, ## TL;DR, ## Key decisions, ## Action items, ## Open questions, ## Sources / links mentioned. Upserts exactly ONE research_vault row per session keyed on (workspace_id, doc_type='session_note', source_url='mbo://session/<session_id>'): updates title, overview (= parsed TL;DR), content (full markdown), app_tag='@WPT', status='active', freshness_score=0.9, last_reviewed_at, updated_at if it exists; otherwise inserts and lets the serial trigger assign serial. Returns { ok, vault_id }. Never logs conversation contents.
  • Modified src/lib/agentChat.functions.ts: After the assistant reply is inserted (composio branch only), fire-and-forget a POST to ${SUPABASE_URL}/functions/v1/kyle-session-distill with the service-role bearer. Debounced: count(*) of session messages must be ≥ 4 AND either even, OR the previous message's created_at is > 90 s ago. Wrapped in try/catch with .catch(() => {}) on the fetch — never awaited, never throws, never blocks the user's reply. Read tools, openai/aria/kyle_base44 branches untouched.
  • Modified supabase/config.toml: Added [functions.kyle-session-distill] verify_jwt = false so it can be invoked server-to-server. No new tables — distillation is additive on top of agent_chat_messages; research_vault retains exactly one canonical session_note row per session_id.

feat: Kyle loads living WorldPort brief + semantic session memory

  • Modified supabase/functions/kyle-composio/index.ts: Before each generateText turn, the function now loads (a) the canonical living WorldPort brief from research_vault (workspace_id, doc_type='context_brief', app_tag='@WPT') and (b) semantically-recalled prior research/sessions — embeds the latest user message with the same text-embedding-3-small model used by vault-search/embed-vault-entry and calls the existing match_research_vault RPC with the service-role admin client (p_workspace_id, p_query_embedding, p_limit=5). The top matches are formatted as - {title}: {overview} lines (capped ~6k chars). Brief + memory are appended to the base persona under labeled sections (## WorldPort Context … and ## Relevant prior sessions / research) and passed as system to generateText. Brief truncated to ~12k chars to stay within model context. Both loads are wrapped in try/catch with console.error logging only the failure message — Kyle still answers from the base persona if the brief row is missing or recall fails. No new embedding pipeline, no schema changes, audit logging and tool loop untouched. Never logs brief or memory contents.

feat: Living WorldPort context brief — auto-refreshed every 6h

  • New supabase/functions/worldport-brief-generate/index.ts: Service-role edge function (POST { workspace_id }) that assembles a single Markdown brief from live MBO data — pinned context_pointers with kind='fundamental' (top 30, pinned first), sites (code/name/status, cap 60), open incidents count + top 5 by severity, 10 most-recent blueprints, 10 most-recent research_vault rows with freshness_score >= 0.5 (excluding doc_type='context_brief' so the brief never references itself), and the latest daily_letters row (first ~400 chars). Output capped to ~24k chars. Upserts ONE canonical research_vault row per workspace keyed on (workspace_id, app_tag='@WPT', doc_type='context_brief') — updates title, content, overview, freshness_score=1.0, last_reviewed_at, updated_at if it exists, otherwise inserts with status='active' and lets the serial trigger assign serial. Returns { ok, vault_id, chars, generated_at }. Never logs content.
  • Modified supabase/config.toml: Added [functions.worldport-brief-generate] with verify_jwt = false so scheduler-tick can invoke it server-to-server with the service-role bearer.
  • Modified src/routes/api/public/scheduler-tick.ts: New worldport_brief branch in dispatch(op) — POSTs to ${SUPABASE_URL}/functions/v1/worldport-brief-generate with Authorization: Bearer ${SUPABASE_SERVICE_ROLE_KEY} and the op's workspace_id. Result ok requires both res.ok and out.ok === true; failure surfaces as brief HTTP <status>. nextRunAt and all other branches unchanged.
  • Migration: Extended the scheduled_operations_operation_type_check CHECK constraint to allow 'worldport_brief'. Seeded one scheduled_operations row per existing workspace (name='WorldPort Context Brief', operation_type='worldport_brief', schedule_type='cron', cron_expression='0 */6 * * *', status='active', next_run_at=now(), alert_on_failure=false) using WHERE NOT EXISTS so re-running is a no-op. No new tables — the brief lives in research_vault, the schedule lives in scheduled_operations.

feat: Dashboard 3-zone layout + Kyle activity strip

  • New src/components/dashboard/KyleActivityStrip.tsx: Resolves the workspace's Kyle agent (agents.provider='composio' + name='Kyle') then queries the last 5 agent_tool_calls rows for that agent. Renders a horizontal strip of small chips inside a worldport Card — each chip shows a Zap icon, toolkit/tool_slug label (truncated to 20 chars), and a status dot (bg-success for status='ok', otherwise bg-danger). Relative time shows on hover via title. Returns null when no rows exist — no empty-state card. Uses Tailwind v4 design tokens only.
  • Modified src/routes/index.tsx: Restructured the sequential widget stack into three labeled zones — Now (4 StatCards + TodayFocusCard 2/3 + InboxWidget 1/3), Today (Daily Letter 2/3 + VelocitySparklineStrip 1/3), and Context (KyleActivityStrip, then 2-col grids for AgentStatusWidget+AriaStatusCard, FreshnessWidget+FinancialStrip, ActivityFeed+SiteHealthStrip). Zone headers use text-[11px] font-semibold uppercase tracking-[0.05em] text-text-tertiary. Removed TopPromptsCard and its topPromptsRes query (superseded by TodayFocusCard); dropped topPrompts from DashboardData. All other queries, the realtime channel subscription, and the refresh selector are unchanged. TopPromptsCard.tsx itself was not deleted.

feat: Cross-entity link chips on Incidents + Tasks

  • New src/components/incidents/IncidentLinkChips.tsx: Renders Chip link chips for linked_task_id (→ /tasks?selected=…), linked_prompt_id (→ /prompt-queue?selected=…), and linked_pr_serial (→ /prompt-queue?selected=…). Null-safe — returns nothing when no links are present. Chips use the worldport Chip primitive with tone="info" and wrap TanStack Link with stopPropagation so row/card clicks don't fire.
  • New src/components/tasks/TaskLinkChips.tsx: Renders Chip link chips for source_email_id (→ /email) and parent_task_id (→ /tasks?selected=…). Shows "↗ Subtask of {parent_serial?.slice(0,16)}" when a parent serial is supplied. Same null-safe, stopPropagation, Chip primitive approach.
  • Modified src/components/incidents/IncidentTable.tsx: Added "Links" column between Status and Opened. Each row renders <IncidentLinkChips> via the existing TD primitive. Updated empty-state colSpan from 7 → 8.
  • Modified src/routes/incidents.tsx: Added IncidentLinkChips import. In the mobile card view, chips render below the existing status/site/serial chip row. select("*") already covers the linked fields — no query change needed.
  • Modified src/components/tasks/TaskCard.tsx: Imported TaskLinkChips. Added a new flex row below the priority/site/due/assignee chip row (outside the card's inner <button> to keep valid HTML). Chips render for source_email_id and parent_task_id.
  • Modified src/components/tasks/TaskList.tsx: Imported TaskLinkChips. In the mobile list item layout, chips render below the existing priority/site/due row inside the flex-1 content area.
  • Modified src/routes/tasks.tsx: Added selected?: string to the Search type so /tasks?selected=… is valid for incoming cross-entity links. select("*") already covers source_email_id and parent_task_id.
  • No new deps, no schema changes, no Kanban column changes.

feat: ⌘K command palette — navigate, quick actions, recent

  • New src/components/worldport/CommandPalette.tsx: shadcn Command wrapped in Dialog. Three groups: Navigate (flat list derived from exported NAV + BUSINESS_ITEMS in Shell.tsx, with section hint chips), Actions (New Prompt → /prompt-queue?new=1, New Task → /tasks?new=1, New Incident → /incidents?new=1, New Capture → /capture/note, Ask Kyle), and Recent (last 5 mbo_audit_log rows for the current workspace via TanStack Query, only fetched while open). Entity type → route map covers prompt / incident / task / agent / site / document; relative timestamp on each row.
  • Modified src/components/worldport/Shell.tsx: Exported NAV and BUSINESS_ITEMS so the palette can build its flat route list without duplication. Mounted <CommandPalette> once inside Shell alongside KyleLauncher. Added a document keydown listener that toggles the palette on ⌘K / Ctrl+K and prevents the default browser shortcut. Replaced the static TopBar <input> with a visually identical button that opens the palette; onOpenPalette prop threaded through TopBar. Escape closes via shadcn Dialog default behavior.
  • Ask Kyle wiring: Palette dispatches a window CustomEvent('kyle:open'); KyleLauncher listens for it via useEffect and opens its existing Sheet. No new component coupling.
  • No new deps; no route file changes (the ?new=1 auto-open in destination routes is out of scope for this prompt per spec).

feat: Nav IA redesign — 4 workflow sections, Business fold

  • Modified src/components/worldport/Shell.tsx: Replaced the 6-section entity-grouped NAV with 4 workflow-verb sections: Now / Build / Operate / Settings. Removed "Tools" (Workspace moved to Settings), "Intelligence" (Atlas + Daily Letter → Operate; Revenue, Tenants, Valuation, Marketplace, Analytics → new "Business" sub-group), and "Platform" (Incidents → Operate; Sites & Pages + Research Vault → Build; Blueprints/Documents dropped from sidebar).
  • Business sub-group: New BUSINESS_ITEMS constant rendered as a collapsible group at the bottom of the sidebar, collapsed by default. Toggle uses TrendingUp icon + ChevronDown caret; expanded items indent ml-4. In icon-only mode the toggle still renders as a single TrendingUp button.
  • Icon fixes: Daily Letter now uses Newspaper (was a duplicate Mail with Email). Removed unused FileText import.
  • No new deps, no route changes, no schema changes. Orphan badge on Dashboard preserved.

feat: AOS-MBO-CMP-004 Tool-call activity UI + floating "Ask Kyle" launcher

  • New src/components/agents/ToolCallBadges.tsx: Presentational chip strip. TanStack Query against agent_tool_calls for the current agent_id + session_id, newest first, limit 20. Each chip shows a Wrench icon, the toolkit (or tool_slug fallback), and a state dot (bg-emerald-500 for ok / bg-destructive otherwise). Empty result renders nothing. Theme tokens only — no hex.
  • Modified src/components/agents/AgentChatInterface.tsx: Renders <ToolCallBadges /> between the message list and the composer. handleSend now invalidates both ["agent-chat", agentId, sessionId] and ["agent-tool-calls", agentId, sessionId] so badges refresh after each turn. Chat logic unchanged.
  • New src/components/agents/KyleLauncher.tsx: Fixed bottom-right floating "Ask Kyle" button (Zap icon, bg-primary text-primary-foreground, z-50). Opens a shadcn Sheet (right side, sm:max-w-lg) that resolves the workspace via useWorkspace(), looks up the provider='composio' agent named "Kyle" for that workspace, and mounts the existing <AgentChatInterface />. Missing-Kyle path shows a friendly "Kyle isn't set up for this workspace yet" message instead of crashing.
  • Modified src/components/worldport/Shell.tsx: Imports KyleLauncher and renders <KyleLauncher /> once as a sibling of the main content area, so it appears on every authenticated page. /login, /invite/$token, /onboarding do not mount the Shell and are unaffected.
  • No new deps: reused shadcn Sheet (already used by MobileMenuSheet/workspace) and worldport primitives. No new colors — bg-background, bg-muted, text-muted-foreground, text-primary-foreground, bg-emerald-500, bg-destructive only.

feat: AOS-MBO-CMP-003 Wire composio provider into sendAgentMessage

  • Modified src/lib/agentChat.functions.ts: Added composio branch inside sendAgentMessage server function, before the existing openai | aria_perplexity | kyle_base44 branch. When agent.provider === "composio", fetches full chat history from agent_chat_messages, builds a message array, and calls the kyle-composio edge function server-to-server via service-role bearer at ${process.env.SUPABASE_URL}/functions/v1/kyle-composio. Response content becomes the agent reply; errors surface as [Kyle] tool error: …. Falls through to the existing agent_chat_messages insert and last_active_at update — no duplicate tool logging (edge function handles agent_tool_calls audit rows).
  • No signature changes: requireSupabaseAuth, ChatInput schema, and the agent reply insert path remain untouched.

feat: AOS-MBO-CMP-002 kyle-composio edge function

  • New supabase/functions/kyle-composio/index.ts: Deno edge function (verify_jwt=false; server-to-server with service-role bearer). Initializes Composio with VercelProvider, creates a session for fixed COMPOSIO_USER_ID = "user_0qev3c", pulls tools, and runs generateText (AI SDK) against anthropic(MODEL) with stopWhen: stepCountIs(10). System prompt is operator-focused / role-greeting (no "Brian" hardcode). Model read from AGENT_MODEL with default claude-sonnet-4-6 — never hardcoded.
  • Audit trail: Walks result.steps[].toolCalls + toolResults, derives { tool_slug, toolkit (prefix-before-underscore lowercased), status, args_summary, error, latency_ms }. args_summary redacts any key matching /key|secret|token|password/i and truncates long values. One row per tool call inserted into agent_tool_calls via the service-role client, tagged with workspace_id, agent_id, session_id, message_id.
  • Response: { ok: true, content, tools_used: [{ tool_slug, toolkit, status }] }. Errors return { ok: false, error } (500). finally logs only { ts, status, latency_ms, tool_count } — never message contents, args, or secrets.
  • New supabase/functions/kyle-composio/deno.json: import map for @composio/core, @composio/vercel, @ai-sdk/anthropic, ai via npm: specifiers (Deno requirement). Packages live ONLY in this function — root package.json/bun.lock untouched.
  • Modified supabase/config.toml: added [functions.kyle-composio] verify_jwt = false.
  • Env: ANTHROPIC_API_KEY added to the project secrets (COMPOSIO_API_KEY already present). AGENT_MODEL optional.

feat: AOS-MBO-CMP-001 Composio integration — migration + Kyle agent seed

  • New migration supabase/migrations/20260531000023_composio_kyle.sql: Creates public.agent_tool_calls audit table (workspace_id → workspaces, agent_id → agents, session_id, message_id → agent_chat_messages SET NULL, tool_slug, toolkit, status default 'ok', args_summary, error, latency_ms, created_at) with indexes on workspace_id / agent_id / session_id. RLS enabled; SELECT via is_workspace_member(workspace_id), INSERT via can_write_workspace(workspace_id). Grants: SELECT to authenticated (members read-only), ALL to service_role (backend-only writes per spec — INSERT intentionally NOT granted to authenticated).
  • Kyle agent seed: Verified agents.provider is free-text (no enum ALTER needed). Inserted single row name='Kyle', provider='composio', api_endpoint=NULL, role carries the Composio operator description (agents has no description column; serial minted by existing tg_serial_agent trigger → AGT-NWL-20260530-001). Guarded with WHERE NOT EXISTS against re-seed.
  • Outcome: Migration applied cleanly. Seed row confirmed (1 row, provider='composio', name='Kyle'). No FK to auth.users, no USING (true), no secrets stored in args_summary.

feat: BP-MBO-20260527-AGT-008 Smoke + CHANGELOG + LOVABLE_RULES addendum

  • New scripts/smoke-agentos-integration.ts: offline integration smoke (bun run scripts/smoke-agentos-integration.ts). Builds a fake agentos_connections row, mints a 60s HS256 JWT, hits an in-memory mock AgentOS health endpoint, runs diffPlan against matched spec/live to assert a no-op (operations.length === 0), and round-trips a nexusos payload through sanitizeOutbound asserting it becomes [redacted:forbidden-token] with redacted === ["nexusos"]. Prints PASS/FAIL and exits non-zero on failure. Not wired into CI per LOVABLE_RULES Forbidden Actions — runs locally before pack approval.
  • Modified LOVABLE_RULES.md: appended the permanent ### AgentOS Integration (added by BP-MBO-20260527-AGT-008) section verbatim from the pack prompt. Pinned: AgentOS repo/branch/manifest, server-only call path, seat-secret location, sanitizer enforcement, plan/role/trigger enums, inbound-event HMAC contract, locked deployment-op ordering, and the retire_agent no-auto-rollback rule.
  • New docs/AGENTOS-INTEGRATION-CONTRACT.md: operator-facing contract — how to add a connection, the 32 AgentOS routes MBO uses, the 7 inbound event types and their counter side effects, the sanitizer rules, and the smoke command.
  • Pack summary (BP-MBO-20260527 AgentOS spoke, AGT-001 → AGT-008): AGT-001 manifest pin + agentos_connections/agentos_event_subscriptions schema; AGT-002 server-side HTTP client + JWT minting + sanitizer + Vitest coverage; AGT-003 /tenants connection management; AGT-004 fleet templates list + form-driven editor with version locking; AGT-005 deployment engine (dry-run, ordered apply with retries, reversible-only rollback); AGT-006 inbound agentos-events edge function + observability counters + drift flag; AGT-007 read-only observability widgets on tenant + agent detail views; AGT-008 smoke + docs + rules addendum.

feat: BP-MBO-20260527-AGT-007 AgentOS observability widgets

  • New hooks src/lib/agentos-observability-queries.ts: useDispatchVolume, useQuotaBurnDown, useInboundEvents, useDriftStatus — read-only TanStack Query hooks over AGT-006's counters/events, workspace-scoped via RLS, zero-filled day buckets so charts render without layout shift on empty windows.
  • New server fn src/lib/agentosQuota.functions.ts: fetchAgentosQuotaState proxies live client.quotaState() so the seat secret never reaches the browser; returns null when the connection is unverified or AgentOS is unreachable. Polled every 60s via refetchInterval.
  • New widgets under src/components/agents/: DispatchVolumeChart (recharts line, completed vs failed, last 30d), QuotaBurnDownChart (live limits/usage chips + warnings area), DriftBanner (session-dismissible amber banner driven by fleet_deployments.drift_suspected_at), InboundEventsFeed (last 25 events with redacted/handler-error chips).
  • Modified src/routes/tenants.$tenantId.tsx: verified connections now render an "Observability" section below the fleet deployment history (drift banner + two-up charts + events feed).
  • Modified src/routes/agents.$agentId.tsx: added an "AgentOS" tab showing the same observability stack scoped to the workspace's most recently verified tenant connection.

feat: BP-MBO-20260527-AGT-006 Inbound AgentOS events + observability counters

  • New migration: agentos_events (serial prefix AEV, unique (connection_id, event_id) for dedupe, workspace-scoped SELECT RLS) and agentos_observability_counters (per-day per-metric, unique (connection_id, bucket, metric)). Added fleet_deployments.drift_suspected_at column. New SECURITY DEFINER helper agentos_bump_counter(workspace_id, connection_id, metric, delta) restricted to service_role for atomic counter upserts.
  • New edge function supabase/functions/agentos-events/: HMAC-SHA256 verification against AGENTOS_INBOUND_HMAC (sentry-webhook shape), x-agentos-connection-id header lookup, inline payload sanitizer (forbidden tokens nexusos/preserver[redacted:forbidden-token], captured in redacted[]). Dispatches to inline handlers for dispatch.completed, dispatch.failed, memory.updated, quota.warning (also writes last_error on connection), seat.invited/seat.accepted (no-op v1), fleet.config.changed (flips drift_suspected_at on most recent applied deployment). Handler errors stored on agentos_events.handler_error but never reject; idempotent on dedupe.
  • New frontend registry under src/integrations/agentos/events/: router.ts exports AGENTOS_EVENT_HANDLERS + AGENTOS_HANDLER_BY_TYPE (event-type → metric descriptor) consumed by AGT-007 widgets. Per-event descriptor files under handlers/. sanitize-inbound.ts re-exports the AGT-002 sanitizer for parity with the edge function.
  • Modified supabase/config.toml: registered [functions.agentos-events] with verify_jwt = false (HMAC, not JWT).
  • Secret required: AGENTOS_INBOUND_HMAC (shared with NetworkOS — never logged).

feat: BP-MBO-20260527-AGT-005 Deployment engine (dry-run, apply, rollback)

  • New migration: fleet_deployments + fleet_deployment_operations (workspace-scoped RLS via is_workspace_member/can_write_workspace, serial prefixes FDP/FDO, indexes on (connection_id, created_at DESC) and (deployment_id, ordinal)). Names deviate from the prompt's deployments to avoid colliding with the pre-existing site deployments table.
  • New server fns under src/lib/deployments/ (kept out of src/server/ because TanStack Start import-protection blocks src/server/** from client bundles):
    • plan.ts — pure diffPlan(spec, live) returning ordered PlannedOperation[] in the locked priority: upgrade_planenable_channelinstall_toolregister/update/retire_agentset_memory_policy. Exports REVERSIBILITY map (retire_agent is the only irreversible op).
    • apply.functions.tsplanDeployment (dry-run, persists nothing) and applyDeployment (idempotent: reuses existing applied row, rejects applying). Executes ops in order with 3-retry exponential backoff (200/600/1800ms), on failure auto-reverts succeeded reversible ops.
    • rollback.functions.tspreviewRollback lists succeeded ops in reverse with reversibility flag; applyRollback creates a new deployment row with rollback_of pointing to the original and reverts each reversible op via the AGT-002 client.
  • New components under src/components/deployments/:
    • DeployModal.tsx — three phases (select template+version → dry-run preview → confirm/apply). Lists only published templates and published versions. Preview shows op count, warnings, and per-op reversible/irreversible chip.
    • DeploymentHistoryTable.tsx — last 25 deployments for a connection with state chip and Rollback button on applied rows.
    • RollbackModal.tsx — irreversible ops rendered with red border + tooltip explaining AgentOS retention may have wiped state.
  • Modified: src/routes/tenants.$tenantId.tsx — verified connections now render a "Fleet deployments" card with Deploy CTA, history table, and rollback flow.

feat: BP-MBO-20260527-AGT-003 Connect-tenant flow on /tenants/:tenantId

  • New migration: agentos_connection_health (append-only) — columns connection_id, workspace_id, checked_at, ok, latency_ms, raw. RLS: workspace members read, workspace writers insert; no update/delete policies. Index idx_agentos_health_conn(connection_id, checked_at DESC). Grants to authenticated and service_role.
  • New server fns (src/lib/agentosConnection.functions.ts):
    • createAgentosConnection — inserts pending row, derives seat_secret_ref = AGENTOS_SEAT_<id>. Secret value is NEVER persisted to Supabase; operator adds it to Lovable Cloud secrets under that name.
    • verifyAgentosConnection — mints JWT, hits GET /api/blocks/agentos/health, updates status + last_verified_at/last_error, appends row to agentos_connection_health. Never logs secret or JWT.
    • revokeAgentosConnection — flips status to revoked.
    • (Located in src/lib/ rather than src/server/ because TanStack Start import-protection blocks client imports from src/server/**.)
  • New components:
    • src/components/tenants/AgentosConnectionPanel.tsx — three-field form (tenant UUID defaulted from URL, API origin defaulting to https://api.worldport.dev, automation seat secret). On submit the secret is cleared from memory and the operator is shown the exact Cloudflare secret NAME to add, then can click "Verify connection".
    • src/components/tenants/AgentosHealthCard.tsx — status pill, last verified, last error tag, last-10 ping sparkbar, Re-verify and Revoke buttons.
  • Modified: src/routes/tenants.$tenantId.tsx — appends an "AgentOS Fleet" section below the existing tabs that swaps panel ↔ health card based on (workspace_id, target_tenant_id) connection presence.

feat: BP-MBO-20260527-AGT-002 AgentOS client + sanitizer + JWT minting

  • src/integrations/agentos/types.ts: contract types mirroring AgentOS manifest fb095c5bAgentOsPlan (observer/companion/pro/business/command), AgentOsRole (owner/admin/member/automation), AgentSpec, SeatInvite, ToolInstall, QuotaState, DashboardStats, MemoryPolicy, AgentosConnectionRow, CloudflareEnv, and the strict AgentOsClient interface with the 18 verbatim method signatures.
  • src/integrations/agentos/errors.ts: AgentOsAuthError (401/403), AgentOsQuotaError (429 w/ retryAfter ms), AgentOsTransientError (5xx/network).
  • src/integrations/agentos/sanitize.ts: recursive walker, FORBIDDEN_TOKENS = ["nexusos", "preserver"], case-insensitive replace with [redacted:forbidden-token]. Returns { clean, redacted } for both directions — sanitize and continue, never fail.
  • src/integrations/agentos/jwt.ts: mintAutomationJwt({ connection, env }) — HS256 via Web Crypto, 60s TTL, reads secret from env[connection.seat_secret_ref] at call time. Never logs secret or JWT body. Runs only in the Cloudflare Worker.
  • src/integrations/agentos/client.ts: createAgentOsClient(args): AgentOsClient (strict) plus createAgentOsClientWithMeta(args) (returns { data, redactedOutbound, redactedInbound } so callers can log to agentos_events in AGT-006). Maps HTTP status → typed errors; outbound bodies sanitized before serialize, inbound bodies sanitized before return.
  • src/integrations/agentos/sanitize.test.ts: vitest spec covering case-insensitive redaction, nested walking, clean payloads, and primitive handling. (@ts-nocheck since vitest types aren't installed; the lovable vite plugin runs the file.)

feat: BP-MBO-20260527-AGT-001 AgentOS connection schema + registry seed

  • integrations_registry: seeded agentos row (name AgentOS, category agent-fleet, default health unknown) — idempotent via ON CONFLICT (key) DO NOTHING.
  • New table public.agentos_connections: workspace-scoped record of per-(workspace × target tenant) AgentOS connection state — columns include target_tenant_id/target_tenant_label, api_origin, automation_seat_id, seat_secret_ref (Cloudflare Worker secret NAME only — no plaintext), signing_key_id, status (pending|verified|failed|revoked), last_verified_at, last_error, app_tag default '@MBO'. Unique on (workspace_id, target_tenant_id). No FK on target_tenant_id (cross-DB).
  • Indexes: idx_agentos_conn_ws (workspace_id, status), idx_agentos_conn_site (site_id).
  • Serial trigger: tg_serial_agtconn mints AGT prefix via mbo_generate_serial (new prefix, no collision with SITE/PROMPT/BP/BC/INC/TSK/CTX/DOC/DL/PACK/PAGE).
  • RLS: enabled with four policies (select/insert/update/delete) using public.is_workspace_member / public.can_write_workspace. Workspace B cannot see Workspace A's rows.
  • GRANTs: SELECT,INSERT,UPDATE,DELETE to authenticated, ALL to service_role.

feat: NXS-CLEAN-003 Doc/asset sweep + annotate historical pack

  • docs/prompt-packs/BP-MBO-PACK-001-LOVABLE.md: added editor's note immediately under the H1 flagging the NexusOS → NetworkOS decommission and pointing future work to NetworkOS / packages/network-os. Body left verbatim as historical record per the read-only-historical-pack convention.
  • public/LovableBuildSequence + public/WORLDPORT_DESIGN_TOKENS.css: no changes needed — both files were already swept clean in NXS-CLEAN-001 (the seed-data line and the design-system header comment both already read NetworkOS).
  • Validation grep (nexusos|NexusOS excluding LOVABLE_RULES.md, historical pack, 2026-05-2x migrations) returns empty.

feat: NXS-CLEAN-002 Forward-migrate NexusOS seed rows to NetworkOS

  • DB migration (forward only): updates public.sites rows where code='NXS' AND name='NexusOS' — sets name='NetworkOS', description='Hub-and-spoke event fabric (in worldport-platform/packages/network-os)', repo_url='https://github.com/teamsilverine/worldport-platform/tree/main/packages/network-os'. Idempotent; re-running is a no-op.
  • Code column untouched: @NXS retained as airport-tag per locked decision.
  • Scope: historical seed migrations 20260521000817/012847/021203/021941/022414 left intact. All literal 'NexusOS' matches in those files were sites-table rows, so a single UPDATE covers every workspace's seed row — no additional table updates needed.
  • Verified: SELECT code, name, repo_url FROM public.sites WHERE code='NXS' returns NXS | NetworkOS | https://github.com/teamsilverine/worldport-platform/tree/main/packages/network-os.

feat: NXS-CLEAN-001 Relabel NexusOS → NetworkOS in rules + onboarding

  • LOVABLE_RULES.md: replaced the NexusOS row in the three-apps table with the corrected NetworkOS framing (lives in teamsilverine/worldport-platform at packages/network-os, accessed via AgentOS spoke). Added new "Decommissioned terms" section codifying that @NXS stays as a tag, NetworkOS is the display name, and the word "NexusOS" must not be reintroduced. Airport-code line on line 75 left intact per locked decision.
  • Files modified: src/routes/onboarding.tsx (seed-sites copy now lists NetworkOS), public/WORLDPORT_DESIGN_TOKENS.css header comment, public/LovableBuildSequence seed-data line — all stale "NexusOS" display-name references replaced.
  • Out of scope: the nxs_sites seed row rename (DB migration in NXS-CLEAN-002) and historical migration files (left untouched).

feat: PROMPT-MBO-WORKSPACE-002 — Add password_ref integer field to workspace_accounts

  • DB migration: added optional password_ref integer column with comment clarifying no passwords are stored — just a numeric cross-reference to an offline list.
  • Files modified: src/routes/workspace.tsx — added Password Ref # numeric input next to Username in the edit slide-over (integer-only, optional, with tooltip), and account card now shows a small lock icon + ref number under the login email when set.

feat: PROMPT-MBO-WORKSPACE-001 — Workspace Accounts Registry

  • DB migration: new workspace_accounts table (workspace_id scoped, RLS via is_workspace_member/can_write_workspace, ACC- serial trigger). Seeded ~28 accounts (GitHub, Supabase, Lovable, Vercel, Cloudflare, Stripe, LawPay, DocuSign, Twilio, Resend, Google Workspace, Notion, etc.) for every existing workspace.
  • Files created: src/routes/workspace.tsx/workspace route with realtime search, category filter chips, favorites strip, recently-visited horizontal strip, 3/2/1-col responsive grid, Google favicon + first-letter fallback, integration status badge, site code @pills, slide-over Add/Edit sheet with full field set, archive + favorite toggles, visit tracking on Open.
  • Files modified: src/components/worldport/Shell.tsx and src/components/mobile/MobileMenuSheet.tsx — new "Tools" nav section with Workspace entry (LayoutGrid icon).
  • Note: Implemented with workspace-scoped RLS instead of the prompt's auth.role() = 'authenticated' policy, per LOVABLE_RULES (every new table must be workspace-scoped). MeterOS emit skipped — no networkos module exists in this repo.

feat: Combine voice notes into a single AI-synthesized thought

  • DB migration: added archived_at (timestamptz) and merged_into (uuid self-FK ON DELETE SET NULL) to context_pointers. New indexes for merged_into and active-by-workspace queries. Context Map query now filters archived_at IS NULL.
  • Files created: src/lib/voiceNotes.functions.ts — two server fns: synthesizeVoiceNotes (Lovable AI Gateway, google/gemini-2.5-flash, strict-JSON system prompt that dedupes + orders without inventing facts; fallback to timestamped concat) and saveCombinedVoiceNote (inserts new voice_note, archives sources with merged_into link).
  • Files modified: src/routes/context-map.tsx — "Combine voice notes" toggle in header, checkbox overlay on cards (only voice_note kind selectable), floating action bar showing selection count, modal that previews AI synthesis with editable title/body and source list before commit.

feat: VAULT Overview field + drag-and-drop document upload on New vault entry

  • DB migration: added overview (text) and file_name (text) columns to research_vault. Existing file_uri column now wired through the modal.
  • Files modified: src/components/research-vault/VaultEntryModal.tsx — added Overview textarea below Title and an Attachment dropzone that accepts PDF/MD/TXT/DOCX/CSV/JSON up to 25MB. Uploads go to the mbo-documents bucket under {user_id}/vault/{timestamp}-{name}. Drag-and-drop + click-to-browse, with upload progress, remove action, and existing-file display when editing.

BP-MBO-20260525-B10-001 — Full Idea-to-Production Loop: 8-state machine, DB-enforced transitions, audit trail

  • Context: Item 28/28 (B10). Final item in the prompt pack. Ships the cross-cutting loops state machine that ties capture/research/plan/build/review/deploy/verify/lock into a single object.
  • DB migration: new workspace-scoped loops (serial LOOP, state CHECK across 8 values, FKs → sites/prompts/github_pull_requests/deployments, locked_at/locked_by, loop_duration_minutes). loop_state_transitions audit table (from_state, to_state, actor_kind, notes). Nullable loop_id columns added to prompts, github_pull_requests, deployments. SECURITY DEFINER loop_transition(_loop_id, _to_state, _notes, _actor_kind) RPC validates the legal-transition graph (capture→{research,plan}, research→plan, plan→build, build→review, review→{deploy,build}, deploy→verify, verify→{lock,build}, lock→∅), refuses writes on locked loops, writes the audit row, computes loop_duration_minutes on lock. AFTER-INSERT trigger seeds the initial transition row. RLS via is_workspace_member / can_write_workspace with locked_at IS NULL guard on update/delete.
  • Files created: src/lib/loopStates.ts (canonical LOOP_STATES + LEGAL_NEXT + STATE_TONE — single source of truth). src/components/loops/StateMachineVisualizer.tsx (8-dot ProgressStepper + horizontal step-node visualizer with past=success, current=primary ring, future=muted). src/routes/loops.tsx (layout Outlet). src/routes/loops.index.tsx (filterable list with state-count chips, inline +New Loop creator, LoopCard with ProgressStepper + site chip + duration). src/routes/loops.$loopId.tsx (detail view with StateMachineVisualizer, history timeline of transitions with from→to + notes + actor_kind, Advance panel showing only legal next-states with optional notes textarea, errorComponent + notFoundComponent per TanStack discipline).
  • Files modified: src/components/worldport/Shell.tsx (added Workflow icon + Loops nav item under Operations).
  • Progress: 28 of 28 shipped. 🎉 PROMPT PACK COMPLETE.

BP-MBO-20260525-B4-001 — Atlas Auto-Surface: prioritized workspace signal feed

  • Context: Item 27/28 (B4). Introduces /atlas as the operator's "what needs my attention right now" view, aggregating signals from across MBO into one ranked feed.
  • DB migration: new workspace-scoped atlas_signals (serial ATL, kind/severity/title/summary/route/source_table/source_id/score/metadata jsonb, dismissed_at). RLS via is_workspace_member / can_write_workspace. New compute_atlas_signals(_workspace_id) SECURITY DEFINER RPC wipes undismissed rows and re-derives: overdue tasks (score 80+), open incidents (score 95), high/critical-churn tenants (score 70 + risk gap), blocks shipped in last 7d (40), research vault entries in last 3d (30).
  • Route /atlas: severity rollup (critical/warning/info), grouped sections by kind with icons (Clock/AlertTriangle/TrendingDown/Package/Sparkles), per-signal Card with score chip and one-click dismiss, deep-link to source route. Recompute button calls the RPC and invalidates the query.
  • Nav: Atlas pinned to top of Intelligence section in Shell.tsx.
  • Files: new src/routes/atlas.tsx, new migration, edited Shell.tsx, CHANGELOG.md, regenerated types.ts.
  • Progress: 27 of 28 items complete.

BP-MBO-20260524-E9-001 — Revenue Intelligence consolidation: per-block detail + projections + formatCents

  • Context: Item 26/28 (E9). Most deliverables already shipped in Items 22–25 (/revenue, /tenants, /marketplace, /valuation, blocks_catalog, block_installations, comp_multiples, valuation_snapshots, revenue_snapshots, compute_valuation RPC replacing the spec's valuation-snapshot edge function per the stack's TanStack-server-fn policy). This patch adds the remaining gaps: forward projections table, per-block revenue route, and the canonical cents formatter.
  • DB migration: new workspace-scoped revenue_projections (block_id FK→blocks_catalog CASCADE, projection_date, projected_mrr_cents, projected_tenants, assumptions jsonb, UNIQUE(workspace_id, block_id, projection_date)). RLS via is_workspace_member / can_write_workspace + service_role bypass; idx_revproj_ws_block_date for range queries. Seed: 12 months of forward projections per active block per workspace using 5% MoM compound growth from current contributed MRR.
  • Files created: src/lib/formatCents.ts (canonical integer-cents → USD utility with compact option for K/M abbreviation; division by 100 happens only at display). src/routes/revenue.$blockId.tsx (block detail page: header with category/layer/wave/status/quality chips and back link to /revenue; 4-stat row MRR/ARR/Tenants/Installations; 12-month trailing MRR area chart; full installations table with tier, status chip, MRR, tenant link to /tenants/$tenantId, health score, churn-risk chip color-toned by threshold; 12-month projection LineChart from revenue_projections). errorComponent + notFoundComponent on the route per TanStack discipline.
  • Files modified: none beyond CHANGELOG (route is auto-registered by TanStack file-based routing; types regen via Supabase integration).
  • Progress: 26 of 28 shipped.

BP-MBO-20260525-D4-001 — Valuation Model: live ARR × multiple, what-if levers, daily snapshots, investor view

  • DB migration: new workspace-scoped sensitivity_parameters (paramname/value/notes UNIQUE per workspace, seeded 8 rows: platform_premium_conservative|base|aggressive, adoption_dampener*, multiple_band 0.30, default_multiple 5.0). valuation_snapshots (snapshot_date UNIQUE per workspace, total_arr_cents, conservative|base|aggressive cents, assumptions jsonb). valuation_levers (lever_type CHECK add_block|add_feature|add_tenant, block_slug, feature_name, lever_label, three delta_cents columns). RLS via is_workspace_member / can_write_workspace on all three. SECURITY DEFINER compute_valuation(workspace_id) walks block_installations, joins comp_multiples (avg p50, falls back to default_multiple), applies adoption_factor = tenants/(tenants+dampener) per scenario, multiplies by platform premium, upserts today's snapshot, then refreshes +block levers (up to 10 uninstalled blocks at 10 initial tenants × $200/mo) and +tenant lever (avg ARPU × default_multiple × premium). Seed: 30 days of synthetic trend snapshots per workspace with gentle upward jitter.
  • Files created: src/routes/valuation.tsx (three RangeCards Conservative|Base|Aggressive with assumption sub-text; meta strip with ARR + 30-day Δ% + sensitivity band; Recharts AreaChart showing Conservative dashed line + Base solid filled + Aggressive shaded band; +Block top-5 leverboard sorted by base delta with per-scenario range; +Tenant single card; Recompute button calls compute_valuation RPC; link to Investor View). src/routes/investor.tsx (full-width no-sidebar layout, hero with Platform ARR + 30-day sparkline, 3 KPIs Live Blocks/Active Tenants/Avg ARPU, 3-up valuation range with base accented, comp benchmarks table joining comp_multiples+blocks_catalog, highest-impact next move card, Print/Export PDF button via window.print()).
  • Files modified: src/components/worldport/Shell.tsx (added TrendingUp icon import + Valuation nav item under Intelligence).
  • Progress: 25 of 28 shipped.

BP-MBO-20260525-D3-001 — Tenant Management: per-tenant view, churn signals, upgrade history

  • DB migration: new workspace-scoped tenants (serial TEN, status trial|active|past_due|paused|churned, MRR/ARR cents, health_score, churn_risk 0–100, tenure_months, contact info, stripe_customer_id, primary_site_id FK→sites). tenant_events (serial TEVT, event_type install|upgrade|downgrade|churn|recovery|payment.*|note|contact|churn_risk.change, mrr_delta_cents, metadata jsonb). tenant_churn_signals (8 weighted signals per tenant, UNIQUE(tenant_id,signal_key)). Added nullable tenant_id FK on block_installations. Triggers tg_serial_tenant / tg_serial_tenant_event via mbo_generate_serial. RLS via is_workspace_member / can_write_workspace on all three tables. SECURITY DEFINER function compute_tenant_churn(_tenant_id) recomputes 8 signals (declining_usage, open_tickets, payment_retries, block_downgrade, low_login, feature_stall, sentiment, renewal_close) using existing data (incidents, tenant_events, last_activity_at) and updates churn_risk + health_score. Seed: 4 sample tenants per workspace (Northwind, Atlas, Meridian, Summit) with onboarding + payment events and computed health.
  • Files created: src/routes/tenants.tsx (layout Outlet). src/routes/tenants.index.tsx (4-stat header for count/MRR/ARR/at-risk, search + status filter + sort by MRR|risk|name, table with health & churn-risk chips color-coded). src/routes/tenants.$tenantId.tsx (header with status chip, MRR/ARR/Health/Churn metrics color-accented, click-to-email/tel/Stripe links; 4 tabs Overview/Blocks/Health/History; Overview shows top active risk signals + recent activity; Blocks lists installed blocks via FK join; Health shows all 8 signals with raw_metric JSON, Recompute button calls compute_tenant_churn RPC; History is reverse-chrono tenant_events with Δ MRR).
  • Files modified: src/components/worldport/Shell.tsx (added Tenants nav item under Intelligence using Users icon).
  • Progress: 24 of 28 shipped.

BP-MBO-20260525-D2-001 — Block Marketplace: catalog, dependency graph, one-click install

  • DB migration: new global tables block_categories (slug PK, name, icon, sort_order), blocks_catalog (slug UNIQUE, name, category_slug, layer, wave, status CHECK live|beta|building|planned|deprecated, quality_tier CHECK experimental|standard|enterprise, short/long_description, icon, is_marketplace_listed default true), block_pricing_tiers (block_id FK CASCADE, tier_name CHECK Free|Starter|Growth|Pro|Enterprise, monthly_cents, features jsonb, sort_order, UNIQUE(block_id,tier_name)), block_dependencies (block_id+depends_on_block_id FKs CASCADE, kind CHECK required|recommended, UNIQUE), comp_multiples (comp_name, comp_arr_cents, arr_multiple_p50). Workspace-scoped block_installations (workspace_id FK→workspaces CASCADE, block_id FK CASCADE, serial, tier_name, status CHECK active|paused|uninstalled|pending_payment, monthly_cents, installed_at/by, UNIQUE(workspace_id,block_id)) with tg_serial_block_installation minting BI-YYYYMMDD-NNN. RLS: global tables readable by authenticated; installations via is_workspace_member (select), can_write_workspace (write), service_role bypass. Seed: 12 categories + 8 blocks (RouteOS, GeocodeOS, ImportOS, DocketForge, DocsOS, EsignOS, LedgerOS, PayOS) with 3 pricing tiers each and required/recommended dependencies + comp rows.
  • Files created: src/routes/marketplace.tsx (layout Outlet). src/routes/marketplace.index.tsx (sidebar with search input + Category/Status filter lists; sort dropdown name/price/status; responsive grid of BlockCards showing icon, name, category·layer·wave, status/quality/tier chips, Install button using cheapest tier or Installed state, Details button). src/routes/marketplace.$blockSlug.tsx (hero with status/quality/category/layer/wave chips + installed indicator, missing-required-deps warning card with co-install list, 3-column pricing tier table with feature checklists, dependencies list with installed indicators, optional comparable-companies table). Install mutation co-installs missing required deps at their Starter tier in a single insert.
  • Files modified: src/components/worldport/Shell.tsx (added Package icon + Marketplace nav item under Intelligence).
  • Progress: 23 of 28 shipped.

BP-MBO-20260525-D1-001 — Revenue Intelligence Dashboard: MRR/ARR trends, tenant deltas, revenue event log

  • DB migration: extended revenue_snapshots with new_tenants, churned_tenants, per_site jsonb (default {}). New revenue_events table (workspace_id FK→workspaces ON DELETE CASCADE, site_id FK→sites ON DELETE SET NULL, serial, event_type CHECK in new|expansion|contraction|churn|refund, tenant_name, amount_cents bigint, occurred_at, notes). Index idx_rev_events_ws_time on (workspace_id, occurred_at DESC). RLS enabled with re_select/re_write/re_svc_all via is_workspace_member/can_write_workspace. BEFORE INSERT trigger tg_serial_revenue_event mints REV-YYYYMMDD-NNN via mbo_generate_serial. Seeded 30 days of synthetic snapshots and 5 sample events per workspace that had none.
  • Files created: src/routes/revenue.tsx (90-day query of revenue_snapshots + 20 latest revenue_events, KPI strip via FinancialStrip, 4-up delta cards for MRR Δ 7d / new 30d / churn 30d / open claims, three area charts for MRR/Tenants/ARR, event list). src/components/revenue/RevenueTrendChart.tsx (recharts AreaChart with gradient fill, token-driven colors via readChartColors). src/components/revenue/RevenueEventList.tsx (icon+tone per event type, +/- amount color, relative time).
  • Files modified: src/components/worldport/Shell.tsx (added DollarSign Revenue nav item under Intelligence).
  • Progress: 22 of 28 shipped.

BP-MBO-20260524-B2-001 — In-Workspace Browser: embedded live preview of each managed app inside Site Detail

  • DB migration: extended sites with preview_url, embed_blocked (bool default false), embed_last_tested, embed_last_screenshot_at, embed_notes. New browser_sessions (workspace_id, site_id, serial BSESS-prefixed UNIQUE, url_used, started_at, ended_at, embed_method iframe|proxy|screenshot|blocked, notes) with BEFORE INSERT serial trigger tg_serial_browser_session calling mbo_generate_serial('BSESS', site.code). RLS: bs_select via is_workspace_member, bs_insert via can_write_workspace, bs_svc_all for service_role; standard grants.
  • Files created: src/lib/embedProbe.functions.ts (TanStack serverFn probeEmbed with requireSupabaseAuth + zod input {site_id, url, workspace_id}; 5s-timeout HEAD fetch via AbortController, parses X-Frame-Options DENY|SAMEORIGIN and CSP frame-ancestors 'none'|'self' to decide embeddability; updates sites.embed_blocked|embed_last_tested|embed_notes scoped by workspace_id; returns {embeddable, reason}). src/components/sites/SitePreviewPanel.tsx (desktop-only via hidden lg:block; toolbar with Preview/Live URL toggle gated on which URLs exist, RefreshCcw to re-key the iframe, Re-probe button, ShieldAlert status chip, "Open externally" anchor, current-URL display, last-probed timestamp + reason; auto-probes when embed_last_tested is null or >24h old; on first render of a non-blocked URL inserts a browser_sessions row with embed_method='iframe'; iframe uses sandbox="allow-scripts allow-same-origin allow-forms allow-popups", referrerPolicy="no-referrer", full-height; blocked-state card with reason + new-tab CTA + re-probe; empty-state when no URL).
  • Files modified: src/components/sites/SiteTabs.tsx (added preview to SiteTabKey union and inserted "Preview" tab between Pipeline and Design Bridge). src/routes/sites.$siteId.tsx (imported SitePreviewPanel, renders when tab === 'preview'). src/components/sites/SiteHeader.tsx — already exposes a live_url external-link icon, spec satisfied without change.
  • Deviation from spec: supabase/functions/embed-probe/index.ts Deno edge function reimplemented as a TanStack server function per project rule "Do NOT use Supabase Edge Functions for app-internal logic". HEAD-fetch + header parsing semantics, 5s timeout, and update fields all preserved. No new npm packages installed. No allow-top-navigation in iframe sandbox. Mobile (< lg) hides the panel entirely.
  • Progress: 20 of 28 shipped.

BP-MBO-20260524-B6-001 — Complete Dashboard (PandaOS parity): Today's Focus + 14-day Velocity strip

  • Files created: src/components/dashboard/TodayFocusCard.tsx (critical-path roll-up — fetches open SEV1/SEV2 incidents, ready P0/P1 prompts, and overdue tasks (due_at < now, status not in done/archived/cancelled) in one parallel batch; merges & ranks SEV1→P0→P1→overdue; renders tone-coded chip + serial + title rows with deep-link to /incidents|/prompt-queue|/tasks; "Clear runway" empty state when all green). src/components/dashboard/VelocitySparklineStrip.tsx (14-day per-day series for prompts shipped [status='shipped', completed_at], incidents opened [created_at], tasks completed [completed_at], captures [context_pointers.created_at]; inline 120×32 SVG sparkline component, totals + today count per metric, 2×4 grid, 60s stale).
  • Files modified: src/routes/index.tsx (mounted <TodayFocusCard /> and <VelocitySparklineStrip /> above the existing FreshnessWidget; both reuse the dashboard workspaceId and refresh on the existing realtime channel).
  • Progress: 19 of 28 shipped.

BP-MBO-20260524-B7-001 — Scheduled Autonomous Operations: cron registry, approval flow, failure alerts

  • DB migration: new scheduled_operations (workspace_id, site_id, serial OPS-prefixed, name, description, operation_type kyle_session|aria_research|prompt_fire|webhook_call|edge_fn|sql_query, operation_config jsonb, schedule_type cron|interval, cron_expression, interval_minutes ≥5, status draft|review|active|paused|archived, next_run_at, last_run_at, run/success/failure counters, alert_on_failure, created_by/submitted_by/_at/approved_by/_at) with BEFORE INSERT serial trigger tg_serial_sched_ops + updated_at trigger + partial index on (status,next_run_at) WHERE status='active'; new scheduled_operation_runs (operation_id FK CASCADE, serial RUN-prefixed, status running|success|failed, started/completed/duration_ms, error_message, response jsonb, optional incident_id FK) with serial trigger; standard ws_read/ws_write/svc_all RLS via is_workspace_member + can_write_workspace (run rows are svc-write only — only the tick writes).
  • Files created: src/routes/api/public/scheduler-tick.ts (TSS public route invoked by pg_cron via pg_net every minute; selects up to 20 due active ops next_run_at <= now(), inserts run row status=running, dispatches per type — prompt_fire updates prompts.status='ready'; webhook_call fetches with configured method/headers/body; kyle_session/aria_research/edge_fn currently stubbed responses — and on completion updates run with status/duration/error/response, opens SEV2 incident if alert_on_failure and failed, then updates op last_run_at/next_run_at/run-counters; cron parser handles */N * * * *, 0 */N * * *, M H * * *, falls back to +60min). src/lib/scheduler.functions.ts (TanStack serverFns createScheduledOperation — rejects sql_query, inserts status=draft, returns id+serial; schedulerDecide — submit (draft→review by any member), activate (review|paused→active, gated owner/admin, computes next_run_at), pause (gated, active→paused, clears next_run_at), archive (gated); writes mbo_audit_log scheduler.{action}). src/components/scheduler/SchedulerPage.tsx (filter chips All/Draft/Review/Active/Paused/Archived, op cards with status chip, serial, type, human schedule label, relative next/last run, counters, action buttons Submit/Activate/Pause/Archive role-gated). src/components/scheduler/OperationCreateModal.tsx (single-screen wizard: name/description, operation type select, per-type config — prompt picker for prompt_fire, URL/method/body for webhook_call, fn name/payload for edge_fn, prompt/query for kyle/aria; schedule = cron preset list (every 5/15/60m, daily 08:00/17:00 UTC) + custom cron + live describeCron label, or fixed interval ≥5m; alert_on_failure toggle; inserts as draft via serverFn). src/routes/scheduler.tsx (route shell w/ meta).
  • Files modified: src/components/worldport/Shell.tsx (added Scheduler nav item under Operations with Clock icon).
  • Deviation from spec: edge functions scheduler-tick / scheduler-approve reimplemented as TSS public route + TanStack serverFn per project rule "Do NOT use Supabase Edge Functions". sql_query operation type is registered in the enum but blocked at creation. To activate scheduling, run in SQL: CREATE EXTENSION IF NOT EXISTS pg_cron; CREATE EXTENSION IF NOT EXISTS pg_net; SELECT cron.schedule('scheduler-tick','* * * * *',$$SELECT net.http_post(url:='https://worldport-mbo.lovable.app/api/public/scheduler-tick', headers:=jsonb_build_object('apikey','<anon>','content-type','application/json'), body:='{}'::jsonb);$$);
  • Progress: 18 of 28 shipped.

BP-MBO-20260524-B8-001 — PR/Deployment Pipeline: GitHub webhook, commit↔serial linking, deploy health check

  • DB migration: new github_pull_requests (workspace_id, site_id, serial PR-prefixed, github_repo, github_pr_number UNIQUE(repo, number), title, body, state open|closed|merged, author, head_branch, base_branch, checks_status, linked_mbo_serial, linked_entity_type prompt|task|incident|plan|deployment, opened_at/merged_at/closed_at, mbo_approved_by/_at) with BEFORE INSERT serial trigger tg_serial_pr using mbo_generate_serial('PR', site.code), updated_at trigger, standard ws_read/ws_write/svc_all RLS via is_workspace_member + can_write_workspace. Extended deployments with pr_id (FK), pr_number, branch, deploy_url, environment (default 'production'), health_check_status healthy|degraded|down|unknown, health_check_score 0-100, health_check_at, started_at, duration_ms, incident_id (FK incidents), external_id with UNIQUE partial index.
  • Files created: src/routes/api/public/github-webhook.ts (TSS public route; HMAC-SHA256 verification against GITHUB_WEBHOOK_SECRET via x-hub-signature-256 with timingSafeEqual; routes by x-github-event header — pull_request → upserts row matched to site by repo_url ILIKE, regex-extracts MBO serial /(?:PROMPT|TASK|TSK|INC|PLAN|DEPLOY|DEP|PR|BP|SITE)-[A-Z0-9]{2,8}-\d{8}-\d{3,4}/g from title+body, maps prefix → linked_entity_type, marks linked prompts shipped on merge; deployment_status → upserts deployment, on failure inserts SEV2 incident + links via incident_id, on success runs inline health check (10s timeout fetch, scores 100 if <3s / 70 if <5s / 50 if slow / 0 if non-200|timeout), updates sites.health_score/health_status/last_deploy/last_health_check_at, opens SEV2 incident if score<50; always returns 200 so GitHub stops retrying). src/lib/pipeline.functions.ts (TanStack serverFns: deployHealthCheck re-runs check against deploy_url||site.live_url; mergePullRequest verifies workspace_role IN owner|admin via workspace_members, requires GITHUB_TOKEN, calls PUT /repos/{repo}/pulls/{n}/merge with merge_method squash|merge|rebase, updates PR state→merged + mbo_approved_by/_at, logs mbo_audit_log action pr.merged). src/components/sites/DeploymentPipelineTab.tsx (Open PRs cards with title/#/state chip/checks chip/linked-serial chip/author/branches/opened-relative + GitHub external link + Merge button gated on isAdmin; Recent deployments rows with serial/status chip/health-score chip (green ≥80, amber ≥50, red else)/commit-sha7/branch/duration/incident pill + Check button to re-run health; Recently-closed compact list).
  • Files modified: src/components/sites/SiteTabs.tsx (added pipeline to SiteTabKey union + SITE_TABS between Deployments and Design Bridge). src/routes/sites.$siteId.tsx (imported DeploymentPipelineTab, rendered when tab==='pipeline').
  • Configuration required: set workspace secrets GITHUB_WEBHOOK_SECRET (shared with the GitHub repo webhook) and GITHUB_TOKEN (a fine-grained PAT with pull_requests:write on the target repos) before merge actions work. Configure each repo webhook to POST to https://<host>/api/public/github-webhook with events pull_request + deployment_status. Sites are matched to repos by sites.repo_url containing the repo full_name.
  • Deviation from spec: edge functions github-webhook / deploy-health-check / github-merge-rpc reimplemented as TSS public route + TanStack serverFns per project rule "Do NOT use Supabase Edge Functions"; HMAC verification, role gating, and 200-always semantics preserved.
  • Progress: 17 of 28 shipped.

BP-MBO-20260524-B3-001 — Email → Workflow Pipeline: Gmail/Outlook → classified → Task|Incident|Prompt|Note

  • DB migration: new email_ingest (workspace_id, site_id, serial EML-prefixed, source_provider gmail|outlook|manual|other, message_id, thread_id, from_address, from_name, to_addresses[], subject, body_text, body_html, received_at, status pending|approved|rejected|ignored, classified_as task|incident|prompt|note|unknown, classification_confidence, urgency_score 0-100, extracted_fields jsonb, classified_at, spawned_type/_id/_serial, reviewed_by/_at) with UNIQUE(workspace_id, message_id), per-ws status & received_at indexes, BEFORE INSERT serial trigger using mbo_generate_serial('EML', site.code), updated_at trigger, standard ws_select / ws_write / svc_all RLS via is_workspace_member + can_write_workspace. New email_integration_config (workspace_id, site_id, provider gmail|outlook, vault_secret_id, token_expiry, from_whitelist[], subject_keywords[], is_active, last_sync_at) with UNIQUE(workspace_id, provider) + same RLS pattern. Added tasks.source_email_id + incidents.source_email_id FKs (ON DELETE SET NULL) with partial indexes.
  • Files created: src/lib/emailWorkflow.functions.ts (TanStack serverFns classifyEmail + decideEmail with requireSupabaseAuth + zod; classify calls Lovable AI Gateway google/gemini-2.5-flash JSON-mode returning {classified_as, confidence, urgency_score, extracted_fields}, falls back to keyword heuristic when key missing; idempotent — skips if classified_at set. decideEmail handles approve/reject/ignore — approve spawns tasks/incidents/prompts/context_pointers per override_classified_as with severity derived from urgency [≥80 SEV1 / ≥60 SEV2 / ≥40 SEV3 / else SEV4], writes spawned_type/_id/_serial back to email row, logs mbo_audit_log). src/routes/email.tsx (Outlet), src/routes/email.index.tsx (filter chips All/Pending/Approved/Rejected/Ignored, two-column inbox + sticky detail panel: from/subject/classification card w/ confidence + urgency + suggested priority, body preview, classify-now button, Create-as / Site / Priority overrides, Approve|Reject|Ignore action bar; toast confirms spawned serial), src/routes/email.setup.tsx (3-step wizard: provider cards Gmail/Outlook → connect placeholder → from-whitelist + subject-keywords filters; upserts to email_integration_config on conflict workspace_id+provider).
  • Files modified: src/components/worldport/Shell.tsx (Operations nav adds { label: "Email", to: "/email", icon: Mail }), src/components/inbox/InboxWidget.tsx (added pending-emails query — shows Mail · N new emails chip linking to /email when count > 0).
  • Skipped per stack rules: edge functions supabase/functions/email-classify and supabase/functions/email-approve — replaced with TanStack serverFns per project "Do NOT use Supabase Edge Functions" rule. OAuth flow + DOMPurify HTML rendering deferred until backend OAuth is wired — body_text is used and HTML is never rendered raw.

BP-MBO-20260524-B1-001 — Execution Plan Preview: structured plan + human approval gate

  • DB migration: extended execution_plans with objective, files_touched jsonb, serials_to_mint jsonb, expected_outcome, risk_notes, prompt_body_hash, generated_by (kyle|aria|manual), generation_model, generation_ms, plan_version, reviewed_by, reviewed_at, reject_reason; relaxed plan_body NOT NULL; expanded status check to include pending|rejected|failed|outdated; added idx_exec_plans_ws + idx_exec_plans_status. New plan_audit_log (workspace_id, plan_id, planned_files jsonb, actual_files jsonb, drift_detected, drift_summary) with standard ws_select / ws_insert + svc_all RLS.
  • Files created: src/lib/executionPlan.functions.ts (TanStack serverFns generateExecutionPlan + decideExecutionPlan with requireSupabaseAuth + zod; 10 plans/hour/workspace rate limit; calls Lovable AI Gateway google/gemini-2.5-flash JSON-mode for structured plan, falls back to regex-based stub when key missing or call fails; marks prior draft/pending plans as outdated on regen; decide writes prompts.status=ready|blocked + inserts mbo_audit_log entry), src/components/prompts/PlanGenerateButton.tsx (Generate / View Plan + status chip, calls serverFn via useServerFn), src/components/prompts/ExecutionPlanModal.tsx (header serial+status chip, objective card, files-touched table with create/modify/delete chips + reasons, serials-to-mint list, expected outcome, collapsible risk notes, rejection reason; Approve / Reject (inline textarea) actions wired to decideExecutionPlan; pending → shows action bar, approved → review timestamp, rejected → reason).
  • Files modified: src/components/prompts/PromptSidePanel.tsx (replaced legacy draft/approve insert mutations with PlanGenerateButton + ExecutionPlanModal; shows AlertTriangle warning row when plan is pending/rejected/outdated reminding user to approve before marking Ready), src/routes/sites.$siteId.tsx (PromptsTab gained Plan column with status dot — gray no plan / yellow pending / green approved|done / red rejected|failed; fetched via grouped execution_plans query that takes latest per prompt_id).
  • Skipped per stack rules: edge functions supabase/functions/plan-generate and supabase/functions/plan-approve — replaced with TanStack serverFns per project "Do NOT use Supabase Edge Functions" rule. OpenAI swap: uses Lovable AI Gateway with no user-supplied key required.
  • Commit: (Lovable fills this in after push)

BP-MBO-20260524-B5-001 — Multi-Agent Team Configuration: agents registry, chat, assignments

  • DB migration: extended agents with slug, provider (default 'custom'), api_endpoint, can_execute_prompts, tasks_completed, error_rate, skills[], avatar_url, paused; unique idx on (workspace_id, slug). New agent_chat_messages (workspace_id, agent_id, session_id, role check user|agent|system, content, metadata jsonb) + idx_acm_session. New agent_task_assignments (workspace_id, agent_id, entity_type check prompt|task|build_chain|incident, entity_id, status check assigned|active|done|failed, assigned_by, assigned_at, completed_at; UNIQUE agent_id+entity_type+entity_id). Standard ws_read / ws_write / svc_all RLS on both new tables.
  • Files created: src/routes/agents.tsx (Outlet), src/routes/agents.index.tsx (grid of AgentCards w/ avatar, status chip, tasks/errors/queue stats, [+ New Agent] modal with name/slug/role/provider/api_endpoint/skills checkboxes/can_execute toggle, EmptyState "Seed Kyle + Aria" button inserts both default agents), src/routes/agents.$agentId.tsx (Assignments | Performance | Skills | Chat tabs), src/components/agents/AgentChatInterface.tsx (per-session UUID, user/agent bubbles, "Thinking…" placeholder, Enter to send), src/lib/agentChat.functions.ts (TanStack serverFn sendAgentMessage with requireSupabaseAuth middleware, zod-validated input, fetches agent → inserts user msg → routes by provider [openai/aria_perplexity/kyle_base44 → POST api_endpoint, custom → echo handler] → inserts agent reply → updates agents.last_active_at), src/components/dashboard/AgentStatusWidget.tsx (2-col grid of agent chips: name + status dot + last_active_at relative).
  • Files modified: src/components/worldport/Shell.tsx (Operations nav + Bot icon → /agents), src/routes/prompt-queue.tsx (new assignAgent upserts to agent_task_assignments on conflict agent_id+entity_type+entity_id alongside prompts.agent_id update; wired into both desktop + mobile PromptSidePanel onUpdateAgent), src/routes/index.tsx (mounted AgentStatusWidget after ActivityFeed).
  • Skipped per stack rules: edge function supabase/functions/agent-chat — replaced with TanStack server fn instead (per project "Do NOT use Supabase Edge Functions" rule).
  • Pending: API key masking (api_key_id column not added — out of scope until secrets vault lands), group chat UI (schema supports session_id grouping; UI is post-B5 per spec).

BP-MBO-20260524-B9-001 — Native Integrations Hub v2: event→health trigger, public webhook, link/unlink sites

  • Files created: src/routes/api/public/integration-event.ts (HMAC-SHA256 verified POST; body validated with zod {site_integration_id uuid, event_type, severity info|warning|error, message, payload?}; inserts into integration_events via service role; 401 on bad sig, 500 if INTEGRATION_WEBHOOK_SECRET not set), src/components/integrations/LinkIntegrationModal.tsx (workspace-scoped site picker filtered to unlinked sites, inserts site_integrations row w/ status=active, health=unknown), supabase/migration (site_integrations.last_event_severity check info|warning|error, idx_integration_events_si_created DESC, idx_site_integrations_site, tg_si_health_from_event SECURITY DEFINER trigger on integration_events INSERT — updates parent last_event_at + last_event_severity + degrades health_status error→down / warning→degraded / info=unchanged + score delta -15/-5 clamped 0-100)
  • Files modified: src/routes/integrations.$integrationKey.tsx (added "Link site" button in SectionTitle right slot opening LinkIntegrationModal, Unlink action column with confirm prompt, unlinkMut deletes site_integrations row — cascades events+credentials)
  • Secrets required (placeholder — add when ready): INTEGRATION_WEBHOOK_SECRET (any 32+ char random string; share with whatever provider will POST). Webhook returns 500 cleanly until set.
  • Webhook URL: https://worldport-mbo.lovable.app/api/public/integration-event — sender computes sha256=<hex> HMAC of the raw request body and sends as X-Signature header.
  • Commit: (Lovable fills this in after push)

BP-MBO-20260524-E8-001 — Site Management v2: deployments tab, pages CRUD modal, health timestamp, counter sync triggers

  • Files created: src/components/sites/DeploymentHistory.tsx (read-only deployments table, top 20 ordered desc, status chip + truncated commit/message, queryKey ["site-tab", siteId, "deployments"]), src/components/sites/PageModal.tsx (create+edit modal for pages with / path validation, replaces inline AddPageSheet), supabase/migration (deployments table workspace+site scoped w/ DEP serial + tg_touch_updated_at, sites.last_health_check_at, idx_deployments_site, tg_sync_site_prompt_count + tg_sync_site_incident_count triggers handling INSERT/UPDATE/DELETE incl. site reassignment)
  • Files modified: src/components/sites/SiteTabs.tsx (added deployments to SiteTabKey + SITE_TABS), src/routes/sites.$siteId.tsx (Deployments tab render, last_health_check_at displayed as "Last checked: …" / "Never checked" w/ Activity icon, PagesTab rewritten to use PageModal w/ row-click edit + queryKey ["site-tab", siteId, "pages"], removed inline AddPageSheet and unused Page type)
  • Lazy tabs: Each tab is already mounted only when active (single conditional render per tab), so per-tab queries fire on demand. Standardised query key to ["site-tab", siteId, "pages"|"deployments"].
  • Commit: (Lovable fills this in after push)

BP-MBO-20260524-E7-001 — Mobile Capture v2: PWA manifest, offline queue, iOS fix, quick classify, file guard

  • Files created: public/manifest.json (MBO standalone PWA, theme #6366F1, 192/512 maskable icons referenced; PNG icons NOT created per spec), src/lib/offlineQueue.ts (native IndexedDB queue — no idb dep; enqueueCapture / flushQueue / getPendingCount; inserts into context_pointers and deletes row on success, leaves on failure), supabase/migration (documents.capture_metadata jsonb for lat/lng/captured_at/device_type/file_size_bytes, idx_documents_workspace)
  • Files modified: src/routes/__root.tsx (theme-color #6366F1, apple-mobile-web-app-capable + status-bar-style meta, manifest link), src/components/mobile/MobileChrome.tsx (dedupe <style> via #mobile-chrome-style, online/offline listeners + top banner "You're offline — captures will be queued (N pending)", auto-flush + back-online toast 2s), src/routes/capture.voice.tsx (MediaRecorder undefined guard with browser hint, streamRef instead of state, audio/mp4 first then opus webm, 10-min hard auto-stop with 9:50 warning, 24MB size guard, 4-button quick classify step with 3s auto-advance to voice_note, editable transcript review screen before save), src/routes/capture.note.tsx (tags parsing .filter(t => t.trim().length>0).map(t=>t.trim()), MAX_CHARS=50000 input cap + visible counter, navigator.vibrate(100) on save, offline detection → enqueueCapture + "Saved offline" message), CHANGELOG.md
  • Commit: (Lovable fills this in after push)

BP-MBO-20260524-E6-001 — Notes / Global Inbox: Telegram intake webhook

  • Files created: src/routes/api/public/telegram-webhook.ts (public POST; verifies X-Telegram-Bot-Api-Secret-Token against TELEGRAM_WEBHOOK_SECRET; parses !decision/!blocker/!reminder/!arch/!link/!warn prefixes → kind; extracts #tag hashtags; resolves workspace via telegram_chat_links.chat_id lookup else TELEGRAM_DEFAULT_WORKSPACE_ID; voice/audio → getFile → audio_url; inserts into context_pointers with source='telegram'; replies with serial via sendMessage; always returns 200), supabase/migration (telegram_chat_links: chat_id UNIQUE, workspace_id FK→workspaces ON DELETE CASCADE, default_site_id, default_kind, label; workspace RLS via is_workspace_member / can_write_workspace; service_role bypass)
  • Files modified: CHANGELOG.md
  • Secrets required (placeholders — add when ready): TELEGRAM_BOT_TOKEN, TELEGRAM_WEBHOOK_SECRET, TELEGRAM_DEFAULT_WORKSPACE_ID (optional fallback)
  • Setup: curl "https://api.telegram.org/bot<TOKEN>/setWebhook?url=https://worldport-mbo.lovable.app/api/public/telegram-webhook&secret_token=<TELEGRAM_WEBHOOK_SECRET>"
  • Commit: (Lovable fills this in after push)

BP-MBO-20260524-E5-001 — Research Vault / Atlas: pgvector, semantic search, freshness, create/edit UI

  • Files created: supabase/functions/embed-vault-entry/index.ts (DB-webhook handler; OpenAI text-embedding-3-small → research_vault.embedding via service role; skips unchanged text; never re-throws), supabase/functions/vault-search/index.ts (JWT-auth; embeds query then RPC match_research_vault), src/components/research-vault/VaultEntryModal.tsx (markdown editor, doc_type/app_tag/source_url/site_id fields, fire-and-forget embed invoke), supabase/migration (pgvector extension, research_vault.embedding vector(1536) + last_reviewed_at + freshness_score, HNSW cosine index, freshness index, match_research_vault RPC, workspace_id backfill)
  • Files modified: src/routes/research-vault.tsx (workspace-scoped queries via useWorkspace, 50-row pagination with Load more, 400ms debounced semantic search via vault-search edge fn, freshness chip color green<30d / amber<90d / red, Mark reviewed + Edit actions, New entry button), supabase/config.toml (vault-search verify_jwt=true, embed-vault-entry verify_jwt=false), CHANGELOG.md
  • Secrets required: OPENAI_API_KEY (already configured)
  • Commit: (Lovable fills this in after push)

BP-MBO-20260524-E4-001 — Prompt Queue v2: execution plan, result webhook, agent assign, A/B compare, vault insert

  • Files created: src/components/prompts/PromptCompareModal.tsx (two-column A/B reader, B search by serial/title, read-only), src/components/prompts/VaultPickerModal.tsx (research_vault title+content ilike search, click-to-insert), supabase/functions/prompt-result-webhook/index.ts (Bearer-auth callback, finds prompt by serial, sets status+result_summary+completed_at, audit-log inserts), supabase/migration (agents skeleton table + RLS + AGT serial trigger, execution_plans table + RLS + EXP serial trigger + touch trigger, prompts.agent_id FK→agents, prompts.result_summary)
  • Files modified: src/components/prompts/PromptSidePanel.tsx (Execution Plan section with Generate/Approve, Agent dropdown, Compare button, Result success card), src/components/prompts/PromptModal.tsx (agent_id dropdown, Insert-from-vault button, site validation helper), src/routes/prompt-queue.tsx (explicit workspace_id filter on sites + prompts queries, agent_id in create), supabase/config.toml (prompt-result-webhook verify_jwt=false), CHANGELOG.md
  • Secrets required: PROMPT_WEBHOOK_SECRET (edge function env)
  • Commit: (Lovable fills this in after push)

BP-MBO-20260524-E3-001 — Incidents v2: Sentry intake, escalation rules, post-mortem, causal links, mitigated status

  • Files created: supabase/functions/sentry-webhook/index.ts (HMAC-SHA256 verified Sentry intake → incidents insert, idempotent on external_id), src/components/incidents/PostMortemEditor.tsx (@uiw/react-md-editor lazy + template), supabase/migration (incidents.external_id/linked_task_id/linked_prompt_id/linked_pr_serial/postmortem_content, status check +mitigated, incident_escalation_rules table w/ RLS, idx_incidents_open_age, uq_incidents_external_id)
  • Files modified: src/components/incidents/IncidentDetail.tsx (Mitigate pipeline step, Details/Post-mortem/Activity tabs, causal-link chips to /tasks + /prompt-queue, "Open prompt to fix" mutation), src/routes/incidents.tsx (workspace_id filter on incidents+sites, "mitigated" in STATUS_OPTIONS), supabase/config.toml (sentry-webhook verify_jwt=false), CHANGELOG.md
  • Secrets required: SENTRY_WEBHOOK_SECRET, WORKSPACE_ID (edge function env)
  • Commit: (Lovable fills this in after push)

BP-MBO-20260524-E2-001 — Tasks v2: blocked status, subtasks, dependencies, activity, attachments, bulk

  • Files created: src/components/tasks/TaskDependencyBadge.tsx, supabase/migration (tasks.parent_task_id + blocked_reason, documents.linked_task_id, task_dependencies table, tasks_status_check w/ 'blocked', indexes)
  • Files modified: src/components/tasks/KanbanBoard.tsx (added Blocked column, multi-select, subtask + dep badges plumb-through), src/components/tasks/TaskCard.tsx (checkbox, subtask chip, blocked reason banner, dep badge), src/components/tasks/TaskModal.tsx (tabs: Details/Subtasks/Activity/Attachments, blocked_reason field, mbo_audit_log feed, document attach via uploadDocument), src/routes/tasks.tsx (workspace_id filters, subtask+dep count queries, BulkActionsBar wiring), src/lib/uploadDocument.ts (linkedTaskId option), CHANGELOG.md
  • Schema: tasks.parent_task_id (self-FK), tasks.blocked_reason, documents.linked_task_id, task_dependencies (workspace-scoped, RLS via is_workspace_member/can_write_workspace + svc_all)
  • Behavior: subtasks hidden from board (shown in modal); status='blocked' surfaces reason field + warning border; bulk: mark done / assign site / change priority / delete
  • Commit: (Lovable fills this in after push)

BP-MBO-20260524-F0-004-FRESHNESS-FOUNDATION — freshness thresholds, records, cron, MV, dashboard widget

  • Files created: supabase/migrations/20260524215405**.sql (thresholds + records + RLS + compute/upsert functions), supabase/migrations/20260524215437**.sql (tier refresh fns, mv_platform_freshness, v_platform_freshness, pg_cron jobs, GRANT/REVOKE hardening), src/styles/freshness.css, src/hooks/useFreshnessClass.ts, src/components/dashboard/FreshnessBadge.tsx, src/components/dashboard/FreshnessWidget.tsx
  • Files modified: src/routes/index.tsx (wired FreshnessWidget), CHANGELOG.md
  • Schema: freshness_thresholds (seeded 26 rows, 4 tiers), freshness_records (per-entity scores), compute_freshness() (linear/exponential/cliff decay), upsert_freshness_record(), refresh_freshness_tier1..4(), mv_platform_freshness (workspace rollup), v_platform_freshness (security-invoker view); pg_cron jobs scheduled per tier
  • Security: RLS via is_workspace_member/can_write_workspace on both tables; svc_all for service_role; REVOKEd anon/authenticated from internal fns + MV; frontend reads via v_platform_freshness only
  • Verification: widget renders on / showing platform arc, tier bars, top-5 stale entities; realtime subscription on freshness_records; auto-refresh 60s
  • Commit: (Lovable fills this in after push)

BP-MBO-20260524-F0-003-WORKSPACE-MIGRATION — finalize workspace multi-tenancy on 12 core tables

  • Files modified: LOVABLE_RULES.md, CHANGELOG.md
  • Schema migrations: added UNIQUE (workspace_id, code) on sites; added UNIQUE (workspace_id, letter_date) on daily_letters
  • Verification: prior state already satisfied 11/12 blueprint requirements (no owner_id columns, all 10 leaf tables workspace_id NOT NULL, ws_* policies in place, child tables parent-scoped). Only the two missing per-workspace unique constraints needed to be added.
  • Rules updated: LOVABLE_RULES.md "Workspace multi-tenancy" section now reflects that all 20 tables are workspace_id NOT NULL.
  • Commit: (Lovable fills this in after push)

BP-MBO-20260524-F0-002B-SVC-ROLE-SYMMETRY — add svc_all service_role policy to 12 core tables

  • Files created: supabase/migrations/20260524000002_svc_role_symmetry.sql (auto-named by Lovable Supabase integration)
  • Files modified: CHANGELOG.md
  • Schema migrations: svc_all policy added to blueprints, build_chains, context_pointers, daily_letters, documents, incidents, prompts, sites, tasks, site_integrations, integration_credential_links, integration_events
  • Verification: ✓ migration applied · pre-existing linter warnings unchanged (not introduced by this migration)
  • Commit: (Lovable fills this in after push)

BP-MBO-20260524-E1-001 — Dashboard v2

  • Added revenue_snapshots table (workspace_id, snapshot_date, MRR/ARR/tenants/claims) with workspace-member read RLS.
  • New components: AriaStatusCard, FinancialStrip, DesktopCaptureFab.
  • Dashboard: realtime channel (prompts/incidents/tasks/agents) invalidates the dashboard query; refresh interval selector (30s/1m/5m/Off, default 1m); mobile stat grid 2×2.
  • ActivityFeed + recentActivity extended with agent entity (sourced from mbo_audit_log entity_type='agent').