From 1c280a497636bef47d26c8ddee691673b92b5054 Mon Sep 17 00:00:00 2001 From: ProfessorPolymorphic <116023536+ProfessorPolymorphic@users.noreply.github.com> Date: Mon, 27 Jul 2026 07:18:46 -0700 Subject: [PATCH 1/2] Refresh the technical docs against the current codebase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the /docs drift tracked since May as #94, #96, and #97. API Reference (#94) documented the submissions and registry endpoints and missed three others entirely: POST /api/ask (the site assistant), POST /api/similarity/preview, and POST /internal/sync. All three are now covered with their real contracts — /api/ask's rate limits and headers, its 200-with-fallback behaviour on a MindRouter outage (it deliberately does not 500, so the chat widget degrades), the 0.2-vs-0.3 threshold that distinguishes the preview endpoint from the persisting one, and the sync trigger's 409-when-in-flight. Verified against the route handlers, and all twelve routes under app/api and app/internal now appear. Architecture (#96) described the migration-003 schema: five tables, no lifecycle columns, no friction ledger, no ClickUp projections, no request registry, no agent log. It now covers all seventeen tables, the ~40 columns added to applications since, and why the enums carry no CHECK constraints (the typed modules and verify:portfolio are the enforcement, so adding a lifecycle state stays a governance change rather than a migration). The component diagram showed a browser-calls-API architecture that was never true of most of this site — it now says plainly that pages are server components reading lib/* directly, and that the route handlers exist for mutations, the agent, and the sync trigger. Admin Guide (#97) listed a six-state lifecycle superseded by ADR 0001. It now carries the real twelve-state ladder and states where the admin surfaces actually sit: registry edits are overwritten by the next seed:portfolio TRUNCATE, status narrative comes from ClickUp, requests live in the tech_requests registry. Submissions review is the part that is still the live workflow. Also documents a defect found while checking that page rather than papering over it: STATUS_OPTIONS in app/admin/registry/[id]/page.tsx still offers the legacy submission states and the pre-ADR-0001 capitalised union. applications.status is plain TEXT with no CHECK, so those values write successfully, and publicStageFromStatus() buckets anything unrecognised as Exploring — an admin can silently misfile a project on /portfolio, and verify:portfolio won't catch it because the verifier reads lib/portfolio.ts, not the database. The page now warns against using that control until the list is fixed. Co-Authored-By: Claude Opus 5 --- app/docs/admin-guide/page.tsx | 90 ++++++++++- app/docs/api-reference/page.tsx | 73 ++++++++- app/docs/architecture/page.tsx | 259 +++++++++++++++++++++++++++----- 3 files changed, 374 insertions(+), 48 deletions(-) diff --git a/app/docs/admin-guide/page.tsx b/app/docs/admin-guide/page.tsx index 6e66777..8938f07 100644 --- a/app/docs/admin-guide/page.tsx +++ b/app/docs/admin-guide/page.tsx @@ -4,7 +4,7 @@ export default function AdminGuideDocsPage() { return (

Application Lifecycle

-

Applications progress through these states:

+

+ The canonical lifecycle is the two-layer taxonomy in{" "} + + ADR 0001 + + : an operational ladder of eleven states plus the tracked{" "} + meta-state, rolling up into six public stages. Each state has a + measurable verification rule enforced by{" "} + npm run verify:portfolio in CI. +

+ +

+ STATUS_OPTIONS in{" "} + app/admin/registry/[id]/page.tsx still offers the legacy + submission states (in-development, staging,{" "} + retired) and the pre-ADR-0001 capitalised union + (Planned, Prototype, Piloting,{" "} + Production, Tracked, Archived). + None of those are members of the current taxonomy. +

+

+ applications.status is plain TEXT with no + CHECK constraint, so such a value writes successfully, and{" "} + publicStageFromStatus() buckets anything unrecognised as{" "} + Exploring so the UI never crashes. The practical effect: + setting a status here can silently misfile a project on{" "} + /portfolio.{" "} + npm run verify:portfolio will not catch it — the verifier + reads lib/portfolio.ts, not the database. +

+

+ Until that list is fixed, prefer editing{" "} + lib/portfolio.ts and re-seeding over changing a + status here. Tracked as a follow-up to the July 2026 documentation + audit. +

+
+

Registry Detail Page

Each registry entry (/admin/registry/[id]) has an editable form for: @@ -128,6 +171,37 @@ export default function AdminGuideDocsPage() { This is useful for existing applications that predate the platform.

+

Where the admin surfaces sit now

+

+ /admin/* is transitional. The source-of-truth + boundary has moved since these pages were built: +

+ +

+ Submissions review — the dashboard, notes, similarity, and promote — + remains the live workflow here, and is what this page is for. ClickUp + write-side (new submissions creating ClickUp tasks) is still future + work; when it lands, /admin/submissions retires. +

+

Notes System

Notes are simple text entries attached to submissions. Each note has an author name and diff --git a/app/docs/api-reference/page.tsx b/app/docs/api-reference/page.tsx index e36ce39..d3e5870 100644 --- a/app/docs/api-reference/page.tsx +++ b/app/docs/api-reference/page.tsx @@ -59,7 +59,7 @@ export default function ApiReferenceDocsPage() { return (

Returns 503 if MINDROUTER_API_KEY is not configured.

+ + + +

+ Returns {`{ response, citations[], toolCalls[], iterations, truncated, salvagedToolCalls }`}. + Citations are assembled from the tools' own canonical URLs, never authored by the model. +

+

+ Rate limited per IP hash: 60/hour public, 600/hour internal. Over the + limit returns 429 with Retry-After, + X-RateLimit-Limit, and X-RateLimit-Remaining. + Returns 503 with unconfigured: true when + MINDROUTER_API_KEY is unset. +

+

+ A MindRouter outage returns 200, not 500 — the body + carries a friendly fallback message and fallback: true so + the chat widget degrades instead of erroring. Every call is logged to{" "} + agent_queries. +

+
+ + +

Similarity

+
+ + +

+ Distinct from /api/submissions/[id]/similarity, which + persists matches after a real submission. This runs at threshold + 0.2 rather than 0.3 — deliberately over-notifying and + letting the submitter judge. +

+
+
+ +

Operations

+
+ +

+ Lives under /internal so the Basic-auth proxy covers it. + Used by the “Sync now” button and by the host cron: +

+
{`curl -s -u "$BASIC_AUTH_USER:$BASIC_AUTH_PASS" -X POST \\
+  https://aispeg.insight.uidaho.edu/internal/sync`}
+

+ Returns the run summary on success. 503 if + CLICKUP_API_TOKEN is unset; 409 if a sync is already + in flight — a run takes 30–60s of sequential ClickUp calls and + overlapping runs would double-write. +

+
+ + + This page drifted badly once already (issue #94): it documented the + registry and submissions endpoints while missing the site assistant, the + similarity preview, and the sync trigger entirely. If you add a route + under app/api/, add it here in the same PR. +
); } diff --git a/app/docs/architecture/page.tsx b/app/docs/architecture/page.tsx index 77e1edf..4d1ee02 100644 --- a/app/docs/architecture/page.tsx +++ b/app/docs/architecture/page.tsx @@ -19,35 +19,50 @@ export default function ArchitectureDocsPage() {

Component Diagram

{`
-┌─────────────────────────────────────────────────────────────┐
-│                        Browser                              │
-│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
-│  │ Builder Guide │  │ Admin Pages  │  │ AI Chat Panel    │  │
-│  │ (Wizard)      │  │ (Dashboard)  │  │ (MindRouter)     │  │
-│  └──────┬───────┘  └──────┬───────┘  └────────┬─────────┘  │
-└─────────┼──────────────────┼──────────────────┼─────────────┘
-          │                  │                  │
-          ▼                  ▼                  ▼
-┌─────────────────────────────────────────────────────────────┐
-│                   Next.js API Routes                        │
-│  /api/submissions  /api/registry  /api/ai/analyze-idea      │
-│  /api/.../notes    /api/.../similarity  /api/ai/refine      │
-│  /api/.../promote                                           │
-└────────────┬───────────────────────────────┬────────────────┘
-             │                               │
-             ▼                               ▼
-┌────────────────────────┐    ┌──────────────────────────────┐
-│     PostgreSQL 16      │    │   MindRouter (On-Prem LLM)   │
-│  ┌──────────────────┐  │    │  OpenAI-compatible API        │
-│  │ submissions      │  │    │  qwen3.6-27b model            │
-│  │ submission_details│  │    │  https://mindrouter.uidaho.edu│
-│  │ submission_notes  │  │    └──────────────────────────────┘
-│  │ applications     │  │
-│  │ similarity_matches│  │
-│  └──────────────────┘  │
-└────────────────────────┘
+┌──────────────────────────────────────────────────────────────────┐
+│                            Browser                               │
+│ ┌───────────┐ ┌──────────┐ ┌───────────┐ ┌──────┐ ┌───────────┐ │
+│ │ Portfolio │ │ Builder  │ │ Site      │ │Admin │ │ Internal  │ │
+│ │ + Pipeline│ │ Guide    │ │ Assistant │ │Pages │ │ (ops only)│ │
+│ └─────┬─────┘ └────┬─────┘ └─────┬─────┘ └──┬───┘ └─────┬─────┘ │
+└───────┼────────────┼─────────────┼──────────┼───────────┼────────┘
+        │            │             │          │           │
+        ▼            ▼             ▼          ▼           ▼
+┌──────────────────────────────────────────────────────────────────┐
+│                       Next.js (App Router)                       │
+│  Server components read lib/* directly — most pages make no API  │
+│  call at all. Route handlers exist for mutations and the agent:  │
+│                                                                  │
+│  /api/submissions  /api/registry   /api/ai/analyze-idea          │
+│  /api/.../notes    /api/.../promote  /api/ai/refine              │
+│  /api/.../similarity  /api/similarity/preview                    │
+│  /api/ask (agent loop)         /internal/sync (ClickUp trigger)  │
+└───┬────────────────────┬───────────────────┬─────────────────────┘
+    │                    │                   │
+    ▼                    ▼                   ▼
+┌─────────────────┐ ┌──────────────────┐ ┌──────────────────────┐
+│  PostgreSQL 16  │ │ MindRouter (LLM) │ │  External / vendored │
+│ submissions     │ │ OpenAI-compatible│ │ ClickUp  (pull-only) │
+│ submission_*    │ │ qwen3.6-27b      │ │ GitHub Issues        │
+│ applications    │ │ mindrouter       │ │ vendor/data-         │
+│ blockers        │ │   .uidaho.edu    │ │   governance (submod)│
+│ similarity_*    │ └──────────────────┘ │ vendor/strategic-plan│
+│ clickup_*       │                      └──────────────────────┘
+│ tech_request*   │      Typed TS modules (lib/*) are the source
+│ roi_claims      │      of truth for taxonomies and narrative —
+│ agent_queries   │      generated catalogs come from the
+│ schema_migrations│     submodules at build time.
+└─────────────────┘
       `}
+ + Most of this site never touches its own API. Pages are server + components that import lib/* and query Postgres directly. + The route handlers above exist for mutations (the wizard, the admin + surfaces), for the agent loop, and for the sync trigger — not as a + data-access layer for the UI. + +

Database Schema

submissions

@@ -113,8 +128,52 @@ output_types TEXT[] submission_id UUID FK → submissions -- Provenance link created_at TIMESTAMPTZ updated_at TIMESTAMPTZ -- Auto-updated via trigger + +-- Portfolio identity (Migration 005). lib/portfolio.ts is the typed +-- shadow and seed source; lib/work.ts is the runtime read path. +slug TEXT -- Stable public identifier +tagline TEXT +home_units TEXT[] -- Whose work depends on this +operational_owners JSONB -- [{name, title}] — rendered publicly +build_participants TEXT[] -- Who actually built it +visibility_tier TEXT -- public | embargoed | internal +clickup_task_id TEXT -- Reverse pointer (ADR 0004) + +-- Lifecycle taxonomy (Migration 007, ADR 0001). No CHECK constraint — +-- the typed union in lib/portfolio.ts is the source of truth, and +-- npm run verify:portfolio enforces the per-status evidence rules. +status TEXT -- 12 values: idea | scoping | approved | + -- building | prototype | piloting | + -- production | maintained | paused | + -- sunsetting | archived | tracked +iids_sponsor TEXT +feature_complete BOOLEAN +live_url_is_staging BOOLEAN +pilot_cohort JSONB -- {size, scope, namedUsers[]} +production_scope TEXT -- home-unit | institution-wide | external +support_contact TEXT +sunset_date DATE +replaced_by TEXT +tracking_only BOOLEAN + +-- Strategic-plan alignment (Migration 008, ADR 0002) +strategic_plan_alignment TEXT[] -- Priority codes, e.g. {A.1, D.3} + +-- ClickUp-synced narrative (Migration 011, ADR 0004) +status_summary TEXT +status_summary_at TIMESTAMPTZ +status_summary_source TEXT `} + + status, visibility_tier, and the alignment + codes are all validated by typed TypeScript modules and by{" "} + npm run verify:portfolio in CI, not by the database. That + is deliberate — adding a lifecycle state should be a code change with a + governance record (an ADR amendment plus a vocabulary registration), + not a migration. See ADR 0001. + +

similarity_matches

Pre-computed overlap scores between submissions and registry applications.

{`
@@ -137,6 +196,93 @@ content          TEXT
 created_at       TIMESTAMPTZ
       `}
+

blockers

+

+ The friction ledger — what is stalling a project, and who is named. One + row per active blocker, categorised against the 14-category taxonomy in{" "} + lib/work.ts. +

+
{`
+id               UUID PRIMARY KEY
+application_id   UUID FK → applications ON DELETE CASCADE
+category         TEXT           -- oit-review | unit-engagement |
+                                -- legal-embargo | funding | ...14 total
+named_party      TEXT           -- e.g. 'OIT', 'Unit X'
+since            DATE           -- Drives the public day counter
+public_text      TEXT           -- Safe for /portfolio
+internal_text    TEXT           -- No surface reads this as of 2026-07-27
+severity         TEXT           -- low | medium | high
+resolved_at      DATE
+      `}
+ +

clickup_projects · clickup_status_updates · clickup_requests · clickup_sync_runs

+

+ Projection tables for the read-only ClickUp ingestion (ADR 0004, + Migrations 010–011). These carry no foreign keys to{" "} + applications — the seed script truncates that table with + CASCADE, which would silently wipe synced data. Synced rows key on + ClickUp ids and join to portfolio slugs at read time via{" "} + lib/clickup-map.ts, so seed and sync are order-independent + and each is individually idempotent. +

+

+ clickup_sync_runs records freshness so a surface can say + how stale it is rather than implying it is live. +

+ +

tech_requests + tech_request_events, tech_request_links, tech_request_project_links, roi_claims

+

+ The Unified Technology Request registry (ADR 0005 Phase 1, Migration + 018). One tech_requests row per request regardless of + origin (tdx | clickup |{" "} + site-submission | direct), with an append-only + event trail, a request↔request and request↔project link graph, and an + ROI claims ledger. Backs /portfolio/pipeline, the single + all-origin request queue. +

+

+ Project links use application_slug as a soft key rather + than a foreign key, for the same re-seed reason as the ClickUp tables. +

+ +

agent_queries

+

+ Observability for the site assistant (ADR 0007, Migration 009). Every{" "} + POST /api/ask is recorded — message, response, tools + called, citation count, iterations, truncation, latency, outcome, HTTP + status, model. Reviewable at /internal/agent-log. +

+
{`
+id               BIGSERIAL PRIMARY KEY
+created_at       TIMESTAMPTZ
+ip_hash          TEXT           -- SHA-256 of ':'.
+                                -- Never the raw IP — no PII at rest.
+audience         TEXT           -- CHECK (public | internal)
+message          TEXT
+response         TEXT           -- Null if the request errored early
+tool_calls       TEXT[]         -- Names, in call order
+citation_count   INTEGER
+iterations       INTEGER
+truncated        BOOLEAN
+latency_ms       INTEGER
+outcome          TEXT           -- ok | mindrouter_error | tool_error |
+                                -- rate_limited | bad_request |
+                                -- unconfigured | internal_error
+http_status      INTEGER
+model            TEXT
+error_message    TEXT
+      `}
+ +

schema_migrations

+

+ Applied-migration ledger written by scripts/migrate.ts, + which is the only thing that applies migrations. Piping a{" "} + .sql file straight into psql changes the + schema without recording it here, and the runner will later try to + apply it again and fail — individual migration files are not reliably + idempotent. Idempotency lives in the runner. +

+

Indexes

Array columns on the applications table use GIN indexes @@ -199,31 +345,66 @@ auth_level 5% Exact match

  • Admin redirected to the new registry entry
  • +

    Assistant Flow

    +
      +
    1. User asks a question in the floating chat widget
    2. +
    3. Client POSTs to /api/ask; rate limit checked per IP hash
    4. +
    5. + The agent loop (lib/agent/loop.ts) calls MindRouter with + the message plus 25 read-only tool definitions +
    6. +
    7. + Tools execute against site data and return a payload plus a{" "} + canonicalUrl; the loop accumulates those as citations +
    8. +
    9. + The model composes an answer, or refuses if no tool returned relevant + data — see ADR 0007's strict-citation policy +
    10. +
    11. The turn is logged to agent_queries
    12. +
    +

    Project Structure

    +

    + Abbreviated — see CLAUDE.md for the annotated tree, which + is kept current as the normative reference. +

    {`
     app/
    -  layout.tsx              # Root layout with Sidebar
    -  page.tsx                # Landing — four-card steering page
    +  layout.tsx              # Root layout: Sidebar + site-assistant widget
    +  page.tsx                # Landing — three-lane steering page
       portfolio/              # Projects inventory
    +    pipeline/             # Unified all-origin request queue (ADR 0005)
       builder-guide/          # Submit-a-Project assessment
    +  intake/[token]/         # Submitter-visible status page
    +  coordination/           # PROCESS surfaces (ADR 0006)
    +  standards/              # REFERENCE surfaces
    +    data-model/           # Data Governance Explorer
    +    strategic-plan/       # Strategic Plan Alignment Explorer + map
       reports/                # Reports surface
    -  standards/              # Standards ledger
    -  ai4ra-ecosystem/        # AI4RA partnership reference
    -  admin/
    -    submissions/          # Submission list + detail
    -    registry/             # App registry list + detail + new
    -  api/
    -    submissions/          # CRUD + notes + similarity + promote
    -    registry/             # CRUD
    -    ai/                   # analyze-idea + refine (MindRouter)
    +  about/, ai4ra-ecosystem/
    +  internal/               # Ops only — sync trigger, agent log
    +  admin/                  # Submissions + registry admin
    +  api/                    # See the API Reference page
       docs/                   # Documentation pages (you are here)
     components/
       Sidebar.tsx             # Navigation sidebar
    +  SectionSubNav.tsx       # Shared sub-nav for Standards + Coordination
       PortfolioCard.tsx       # Project card
    +  PortfolioFilters.tsx    # Two-tier stage / status filter
    +  ProjectDetail.tsx       # Project detail composition
    +  ChatWidget.tsx          # Site assistant (ADR 0007)
       IssueCard.tsx           # GitHub issue card
       DocPage.tsx             # Documentation layout components
     lib/
    -  portfolio.ts            # Projects inventory (typed)
    +  portfolio.ts            # Projects inventory + lifecycle (typed)
    +  work.ts                 # Postgres read path for /portfolio
    +  utr.ts, requests.ts     # Request registry (ADR 0005)
    +  clickup*.ts             # ClickUp ingestion (ADR 0004)
    +  agent/                  # Site assistant — tools, loop, logging
    +  governance/             # UDM catalog modules (generated + curated)
    +  strategic-plan/         # Pillars + priorities (generated + curated)
    +  surveys/                # Operational Excellence survey
       standards-watch.ts      # Standards ledger entries
       builder-guide-data.ts   # Quiz steps, scoring, tiers
       similarity.ts           # Similarity detection engine
    
    From 59cd0b4d13ae2d679b6885eeddb84b2a735d5e1e Mon Sep 17 00:00:00 2001
    From: ProfessorPolymorphic
     <116023536+ProfessorPolymorphic@users.noreply.github.com>
    Date: Mon, 27 Jul 2026 07:51:39 -0700
    Subject: [PATCH 2/2] Purge the legacy status vocabulary from docs, and delete
     a dead seed file
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    Follow-up to #321, which refreshed these pages but left two lines still
    describing the pre-ADR-0001 lifecycle — my miss.
    
    /docs/api-reference documented the registry PATCH `status` parameter as
    "idea | approved | in-development | staging | production | retired". That
    is the legacy submission lifecycle, not the operational ladder. It now
    lists the twelve real values and states the consequence of sending
    anything else: the column is plain TEXT with no CHECK, so a bad value
    writes successfully and renders as Exploring.
    
    /docs/architecture had two contradictory `status` lines in the same
    applications block — the legacy one it always had, and the correct
    twelve-value one #321 added lower down. Removed the legacy line.
    
    Deletes db/seed_applications.sql. It is an unreferenced, byte-identical
    copy of migration 004 whose header instructs the reader to run it with
    `psql "$DATABASE_URL" -f db/seed_applications.sql`. That is precisely
    what #312 established as forbidden and what /docs/deployment warns
    against: it would TRUNCATE applications and repopulate every row with
    2026-era statuses like 'in-development', which no longer exist in the
    taxonomy and which publicStageFromStatus() buckets as Exploring. A
    loaded gun with instructions attached, referenced by nothing.
    
    Verified both databases are already clean — every row in dev (29) and
    prod (15) carries a current-taxonomy value, so no data repair is needed.
    
    The applied migrations 003 and 004 still contain the legacy vocabulary in
    comments and seed data. Left alone deliberately: they are history, they
    have already run, and rewriting them would misrepresent what was applied.
    
    Co-Authored-By: Claude Opus 5 
    ---
     app/docs/api-reference/page.tsx |   2 +-
     app/docs/architecture/page.tsx  |   2 -
     db/seed_applications.sql        | 594 --------------------------------
     3 files changed, 1 insertion(+), 597 deletions(-)
     delete mode 100644 db/seed_applications.sql
    
    diff --git a/app/docs/api-reference/page.tsx b/app/docs/api-reference/page.tsx
    index d3e5870..09ede9f 100644
    --- a/app/docs/api-reference/page.tsx
    +++ b/app/docs/api-reference/page.tsx
    @@ -151,7 +151,7 @@ export default function ApiReferenceDocsPage() {