openapi: "3.1.0"
info:
  title: Mzizi API
  version: "1.0.0"
  description: |
    The Mzizi API — components, brand, architecture, Ubuntu doctrine, the open 3D frontend
    model, and design system. Mzizi is an open-architecture project of the Bundu Foundation,
    operated and developed by Nyuchi.

    The Mzizi API follows resource-oriented design principles. All resources
    are served as JSON with schema.org JSON-LD metadata, CORS headers, and CDN-friendly caching.

    All data routes read from Supabase. If the database is not configured, endpoints return
    503 with setup instructions. There are no hardcoded fallbacks.

    **Base URL:** `https://mzizi.dev/api/v1`

    **Registry:** the stable registry across 10 architecture layers — served from the Supabase `components` table (live count via `GET /api/v1/stats` → `stable`; counts are never hardcoded in this spec so it cannot drift from the database)

    **MCP Server:** `https://mzizi.dev/mcp` (Streamable HTTP transport)
  contact:
    name: Bundu Foundation
  license:
    name: MIT
    identifier: MIT

servers:
  - url: https://mzizi.dev/api/v1
    description: Production
  - url: http://localhost:3000/api/v1
    description: Development

tags:
  - name: Discovery
    description: API metadata, health, search, stats
  - name: Brand
    description: Five African Minerals design system
  - name: Registry
    description: Component registry (shadcn-compatible)
  - name: Architecture
    description: Ecosystem architecture, data layer, pipeline, sovereignty, 3D frontend axes/layers
  - name: Ubuntu
    description: Ubuntu doctrine — Five Pillars and Five Principles
  - name: Content
    description: Documentation pages, changelog, AI instructions
  - name: MCP
    description: Model Context Protocol server for AI assistants

paths:
  /:
    get:
      summary: API Discovery
      description: Returns a discovery document listing all available resources.
      operationId: getApiDiscovery
      tags: [Discovery]
      responses:
        "200":
          description: Discovery document with schema.org WebAPI metadata
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiDiscovery"
        "500":
          $ref: "#/components/responses/InternalError"

  /brand:
    get:
      summary: Brand System
      description: |
        Returns the complete Nyuchi brand system — Five African Minerals palette,
        typography, spacing, ecosystem brands, accessibility standards, and voice & tone.

        Reads from Supabase. Returns 503 if database is not configured.

        Includes schema.org JSON-LD metadata (`@context`, `@type: Brand`).
      operationId: getBrandSystem
      tags: [Brand]
      responses:
        "200":
          description: Complete brand system
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BrandSystem"
        "500":
          $ref: "#/components/responses/InternalError"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /ui:
    get:
      summary: Component Registry Index
      description: |
        Returns metadata for every stable registry item across 10 architecture layers
        (live count from Supabase — query `GET /api/v1/stats` for the current total).
        Returns 503 if the database is not configured.
      operationId: getRegistryIndex
      tags: [Registry]
      responses:
        "200":
          description: Registry index
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RegistryIndex"
        "500":
          $ref: "#/components/responses/InternalError"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /ui/{name}:
    get:
      summary: Individual Component
      description: |
        Returns a single component with inline source code.
        Reads from Supabase. Returns 503 if database is not configured.
      operationId: getComponent
      tags: [Registry]
      parameters:
        - name: name
          in: path
          required: true
          description: Component name in kebab-case (e.g., "button", "use-toast")
          schema:
            type: string
            pattern: "^[a-z][a-z0-9-]*$"
          examples:
            ui:
              value: button
              summary: A UI component
            hook:
              value: use-toast
              summary: A hook
            lib:
              value: utils
              summary: A library utility
      responses:
        "200":
          description: Component with source code
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RegistryItem"
        "400":
          description: Invalid component name
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Component not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/InternalError"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /ecosystem:
    get:
      summary: Ecosystem Architecture
      description: |
        Returns architecture principles (Ubuntu, local-first, mobile-first,
        open-source/sovereign, open-data) and the framework decision (Next.js + Capacitor).

        Reads from Supabase. Returns 503 if database is not configured.

        Includes schema.org JSON-LD metadata (`@context`, `@type: TechArticle`).
      operationId: getEcosystem
      tags: [Architecture]
      responses:
        "200":
          description: Ecosystem architecture
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EcosystemArchitecture"
        "500":
          $ref: "#/components/responses/InternalError"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /data-layer:
    get:
      summary: Data Layer Architecture
      description: |
        Returns the local-first data layer (RxDB, SQLite, IndexedDB),
        cloud services (Supabase, CouchDB), and data ownership rules.

        Reads from Supabase. Returns 503 if database is not configured.

        Includes schema.org JSON-LD metadata (`@context`, `@type: TechArticle`).
      operationId: getDataLayer
      tags: [Architecture]
      responses:
        "200":
          description: Data layer specification
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DataLayerArchitecture"
        "500":
          $ref: "#/components/responses/InternalError"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /pipeline:
    get:
      summary: Open Data Pipeline
      description: |
        Returns the open data pipeline stages — Redpanda event streaming,
        Apache Flink PII stripping, and Apache Doris analytics.

        Reads from Supabase. Returns 503 if database is not configured.

        Includes schema.org JSON-LD metadata (`@context`, `@type: TechArticle`).
      operationId: getPipeline
      tags: [Architecture]
      responses:
        "200":
          description: Pipeline stages
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PipelineArchitecture"
        "500":
          $ref: "#/components/responses/InternalError"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /sovereignty:
    get:
      summary: Technology Sovereignty
      description: |
        Returns sovereignty assessments for every technology in the Nyuchi stack,
        including removed technologies and migration notes.

        Reads from Supabase. Returns 503 if database is not configured.

        Includes schema.org JSON-LD metadata (`@context`, `@type: TechArticle`).
      operationId: getSovereignty
      tags: [Architecture]
      responses:
        "200":
          description: Sovereignty assessments
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SovereigntyReport"
        "500":
          $ref: "#/components/responses/InternalError"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /health:
    get:
      summary: Health Check
      description: |
        Returns structured health status for monitoring.
        Compatible with Vercel, Datadog, and standard health check protocols.
        No caching — always returns live status.
      operationId: getHealth
      tags: [Discovery]
      responses:
        "200":
          description: All checks passing
          headers:
            Cache-Control:
              schema:
                type: string
                example: "no-cache, no-store"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthStatus"
        "503":
          description: One or more checks failing
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthStatus"

  /architecture:
    get:
      summary: 3D Architecture snapshot
      description: |
        Full snapshot of the Nyuchi 3D Architecture model — every axis with its layers,
        covenants, stakeholders, implementation rules, and live component counts. Wraps
        the `get_architecture()` SQL helper. ISR cache: 1 hour.
      operationId: getArchitectureSnapshot
      tags: [Architecture]
      responses:
        "200":
          description: "Snapshot reshaped as `{ data: axes[].layers[], meta }`."
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { type: object }
                  meta:
                    type: object
                    properties:
                      axes_count: { type: integer }
                      version: { type: string }
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /architecture/axes:
    get:
      summary: Per-axis summary with live counts
      description: |
        One row per axis with `layer_count` and `component_count` joined live from
        `architecture_frontend_layers` and `components`. Wraps `get_axes_summary()`.
      operationId: getAxesSummary
      tags: [Architecture]
      responses:
        "200":
          description: 5 axes ordered by sort_order.
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { type: object }
                  meta:
                    type: object
                    properties:
                      count: { type: integer }
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /architecture/layers/{n}:
    get:
      summary: Per-layer detail (covenant, rules, category breakdown)
      description: |
        Single-layer detail. Wraps `get_layer_detail(p_layer_number int)`.
        Returns 400 for non-integer or out-of-range `n` (must be 1-10).
      operationId: getLayerDetail
      tags: [Architecture]
      parameters:
        - name: n
          in: path
          required: true
          schema:
            type: integer
            minimum: 1
            maximum: 10
      responses:
        "200":
          description: Layer detail with covenant, rules, and category breakdown.
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: object }
        "400":
          description: Invalid layer number.
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /architecture/frontend/axes:
    get:
      summary: Frontend Axes (3D)
      description: |
        The five axes of the 3D frontend architecture: X (horizontal composition),
        Y (vertical infrastructure), Z (depth observation), Outside (actors beyond
        the build), Documentation (self-describing system). Sourced from the
        `architecture_frontend_axes` table.
      operationId: getFrontendAxes
      tags: [Architecture]
      responses:
        "200":
          description: 3D frontend axes
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FrontendAxesResponse"
        "500":
          $ref: "#/components/responses/InternalError"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /architecture/frontend/layers:
    get:
      summary: Frontend Layers (3D)
      description: |
        The ten layers of the 3D frontend architecture (L1 tokens .. L10 documentation).
        Each layer belongs to exactly one axis. Sourced from the
        `architecture_frontend_layers` table.
      operationId: getFrontendLayers
      tags: [Architecture]
      responses:
        "200":
          description: 3D frontend layers
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FrontendLayersResponse"
        "500":
          $ref: "#/components/responses/InternalError"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /ubuntu/pillars:
    get:
      summary: Ubuntu Pillars
      description: |
        The five Ubuntu Pillars — spheres in which Ubuntu is lived. Each pillar
        maps a region of life to a platform surface so the doctrine translates
        to software. Sourced from the `ubuntu_pillars` table.
      operationId: getUbuntuPillars
      tags: [Ubuntu]
      responses:
        "200":
          description: Ubuntu pillars
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UbuntuPillarsResponse"
        "500":
          $ref: "#/components/responses/InternalError"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /ubuntu/principles:
    get:
      summary: Ubuntu Principles
      description: |
        The five Ubuntu Principles — operating rules that translate Ubuntu to
        software engineering decisions. Sourced from the `ubuntu_principles` table.
      operationId: getUbuntuPrinciples
      tags: [Ubuntu]
      responses:
        "200":
          description: Ubuntu principles
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UbuntuPrinciplesResponse"
        "500":
          $ref: "#/components/responses/InternalError"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /docs:
    get:
      summary: "GONE — long-form documentation moved into the repo as MDX"
      description: |
        Always returns HTTP 410 Gone.

        Long-form documentation now lives in the repo as MDX (CLAUDE.md §15.18).
        The `documentation_pages` Supabase table is HISTORICAL — content was
        migrated in PR #57. The response body carries a `migrated_to` map that
        lists the live URL for each former slug.
      operationId: listDocs
      deprecated: true
      tags: [Content]
      responses:
        "410":
          description: Gone — see `migrated_to` for live URLs.
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                type: object
                properties:
                  error: { type: string, example: "Gone" }
                  message: { type: string }
                  migrated_to:
                    type: object
                    additionalProperties: { type: string, format: uri }

  /docs/{slug}:
    get:
      summary: "GONE — see /docs for the migration map"
      description: |
        Always returns HTTP 410 Gone. See `GET /api/v1/docs` for the
        per-slug → URL migration map.
      operationId: getDoc
      deprecated: true
      tags: [Content]
      parameters:
        - name: slug
          in: path
          required: true
          schema: { type: string }
      responses:
        "410":
          description: Gone — see /docs for the migration map.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error: { type: string, example: "Gone" }
                  message: { type: string }
                  slug: { type: string }

  /changelog:
    get:
      summary: List releases
      description: Returns all rows from `changelog`, newest first.
      operationId: listChangelog
      tags: [Content]
      responses:
        "200":
          description: Release list
          content:
            application/json:
              schema:
                type: object
                properties:
                  releases:
                    type: array
                    items:
                      $ref: "#/components/schemas/ChangelogEntry"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /changelog/{version}:
    get:
      summary: Get a release by version
      operationId: getRelease
      tags: [Content]
      parameters:
        - name: version
          in: path
          required: true
          schema: { type: string, example: "4.0.39" }
      responses:
        "200":
          description: Release
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChangelogEntry"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /skills:
    get:
      summary: List published skills
      description: |
        Returns every skill (agent-skill MDX bodies) from the Supabase
        `skills` table in the lightweight summary shape (no `body_mdx`).
        Use `GET /skills/{name}` to fetch a single skill's full body.

        Skills are reusable workflows AI assistants invoke on specific
        tasks. Distinct from `/ai/instructions`, which serve per-AI-target
        system prompts.
      operationId: listSkills
      tags: [Content]
      responses:
        "200":
          description: "Skill index in `{ data: SkillSummary[], meta: { count } }`"
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        name: { type: string }
                        description: { type: string }
                        agents:
                          type: array
                          items: { type: string }
                        applies_to:
                          type: array
                          items: { type: string }
                        requires_mcp: { type: boolean }
                        version: { type: string }
                        status: { type: string, nullable: true }
                        updated_at: { type: string, format: date-time }
                  meta:
                    type: object
                    properties:
                      count: { type: integer }
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /skills/summary:
    get:
      summary: Lightweight skill index for the CLI update path
      description: |
        Same shape as `GET /skills` but served via the
        `get_skills_summary()` SQL helper. Reserved for the CLI's
        `nyuchi-design skills update` subcommand to detect version
        drift cheaply; gives `/skills` headroom to add filtering or
        pagination later without breaking the CLI contract.
      operationId: getSkillsSummary
      tags: [Content]
      responses:
        "200":
          description: Skill summary index.
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { type: object }
                  meta:
                    type: object
                    properties:
                      count: { type: integer }
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /skills/{name}:
    get:
      summary: Get a single skill (with full body_mdx)
      description: |
        Returns the full skill row including `body_mdx`. Used by
        consumers that need to write the skill to disk (the CLI's
        `nyuchi-design skills install` subcommand) or pipelines doing
        direct `curl` against the API.

        Validates `name` against `^[a-z][a-z0-9-]*$`. Returns 400 on
        invalid pattern, 404 when name well-formed but no row, 503
        when Supabase is unconfigured.
      operationId: getSkill
      tags: [Content]
      parameters:
        - name: name
          in: path
          required: true
          description: Kebab-case skill name (e.g. `nyuchi-design-system`)
          schema:
            type: string
            pattern: "^[a-z][a-z0-9-]*$"
      responses:
        "200":
          description: Skill with full `body_mdx` MDX content.
          headers:
            Cache-Control:
              $ref: "#/components/headers/CachePublic"
            Access-Control-Allow-Origin:
              $ref: "#/components/headers/CorsWildcard"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      id: { type: integer }
                      name: { type: string }
                      description: { type: string }
                      body_mdx: { type: string }
                      agents:
                        type: array
                        items: { type: string }
                      applies_to:
                        type: array
                        items: { type: string }
                      requires_mcp: { type: boolean }
                      version: { type: string }
                      status: { type: string, nullable: true }
                      created_at: { type: string, format: date-time }
                      updated_at: { type: string, format: date-time }
        "400":
          description: Invalid skill name pattern.
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /ai/instructions:
    get:
      summary: List AI instruction sets
      description: Returns the list of AI instruction targets (e.g. claude, copilot, mcp-server).
      operationId: listAiInstructions
      tags: [Content]
      responses:
        "200":
          description: Instruction set index
          content:
            application/json:
              schema:
                type: object
                properties:
                  instructions:
                    type: array
                    items:
                      type: object
                      properties:
                        target: { type: string }
                        name: { type: string }
                        description: { type: string }
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /ai/instructions/{name}:
    get:
      summary: Get AI instruction set by target
      operationId: getAiInstruction
      tags: [Content]
      parameters:
        - name: name
          in: path
          required: true
          schema: { type: string, example: "claude" }
      responses:
        "200":
          description: Instruction set
          content:
            application/json:
              schema:
                type: object
                properties:
                  target: { type: string }
                  name: { type: string }
                  content: { type: string, description: "Full instruction text" }
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"

  /search:
    get:
      summary: Cross-resource search
      description: Full-text search across components, documentation pages, and changelog entries.
      operationId: search
      tags: [Discovery]
      parameters:
        - name: q
          in: query
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Search results
          content:
            application/json:
              schema:
                type: object
                properties:
                  query: { type: string }
                  components: { type: array, items: { type: object } }
                  docs: { type: array, items: { type: object } }
                  changelog: { type: array, items: { type: object } }

  /stats:
    get:
      summary: Live registry stats
      description: Live counts across the registry — total stable components, per-layer and per-category breakdown, token counts. Never hardcoded.
      operationId: getStats
      tags: [Discovery]
      responses:
        "200":
          description: Stats
          content:
            application/json:
              schema:
                type: object
                properties:
                  components:
                    type: object
                    properties:
                      total: { type: integer }
                      stable: { type: integer }
                      alpha: { type: integer }
                      deprecated: { type: integer }
                  layers:
                    type: array
                    items:
                      type: object
                      properties:
                        layer: { type: integer }
                        axis: { type: string }
                        count: { type: integer }
                  tokens:
                    type: object
                    additionalProperties: { type: integer }

  /ui/{name}/docs:
    get:
      summary: Get component documentation
      operationId: getComponentDocs
      tags: [Registry]
      parameters:
        - name: name
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Component documentation (use cases, variants, accessibility)
          content:
            application/json:
              schema:
                type: object
                properties:
                  useCases: { type: array, items: { type: string } }
                  variants: { type: array, items: { type: object } }
                  accessibility: { type: array, items: { type: string } }
        "404":
          $ref: "#/components/responses/NotFound"

  /ui/{name}/versions:
    get:
      summary: Get component version history
      operationId: getComponentVersions
      tags: [Registry]
      parameters:
        - name: name
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Version history
          content:
            application/json:
              schema:
                type: object
                properties:
                  versions:
                    type: array
                    items:
                      type: object
                      properties:
                        version: { type: string }
                        change_type: { type: string }
                        comment: { type: string }
                        changed_by: { type: string }
                        created_at: { type: string, format: date-time }

  /mcp:
    post:
      summary: MCP JSON-RPC
      description: |
        Model Context Protocol endpoint for AI assistants. Accepts JSON-RPC requests
        using the Streamable HTTP transport. Exposes registry components, brand system,
        design tokens, and architecture data as MCP resources and tools.
      operationId: mcpJsonRpc
      tags: [MCP]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [jsonrpc, method]
              properties:
                jsonrpc:
                  type: string
                  const: "2.0"
                method:
                  type: string
                  description: "MCP method (e.g., tools/list, tools/call, resources/read)"
                id:
                  oneOf:
                    - type: string
                    - type: integer
                params:
                  type: object
      responses:
        "200":
          description: JSON-RPC response
          content:
            application/json:
              schema:
                type: object
                required: [jsonrpc]
                properties:
                  jsonrpc:
                    type: string
                    const: "2.0"
                  id:
                    oneOf:
                      - type: string
                      - type: integer
                  result:
                    type: object
                  error:
                    type: object
                    properties:
                      code:
                        type: integer
                      message:
                        type: string
    get:
      summary: MCP SSE Stream
      description: Server-Sent Events stream for MCP notifications.
      operationId: mcpSse
      tags: [MCP]
      responses:
        "200":
          description: SSE event stream
          content:
            text/event-stream:
              schema:
                type: string
    delete:
      summary: MCP Session Cleanup
      description: Cleans up an MCP session.
      operationId: mcpCleanup
      tags: [MCP]
      responses:
        "200":
          description: Session cleaned up
    options:
      summary: MCP CORS Preflight
      description: CORS preflight for MCP endpoint.
      operationId: mcpCors
      tags: [MCP]
      responses:
        "204":
          description: CORS headers returned

components:
  headers:
    CachePublic:
      schema:
        type: string
        example: "public, max-age=3600, s-maxage=86400"
    CorsWildcard:
      schema:
        type: string
        example: "*"

  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string

    ServiceUnavailableError:
      type: object
      required: [error, setup]
      properties:
        error:
          type: string
          example: "Database not configured"
        setup:
          type: string
          description: Instructions for configuring Supabase
          example: "Set NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables"

    # ── Discovery ──────────────────────────────────────────────────

    ApiDiscovery:
      type: object
      required: ["@context", "@type", name, version, description, homepage, resources]
      properties:
        $schema:
          type: string
          example: "https://design.nyuchi.com/schema/api.json"
        "@context":
          type: string
          const: "https://schema.org"
        "@type":
          type: string
          const: "WebAPI"
        name:
          type: string
          example: "Nyuchi Architecture API"
        version:
          type: string
          example: "1.0.0"
        description:
          type: string
        homepage:
          type: string
          format: uri
          example: "https://design.nyuchi.com"
        resources:
          type: object
          additionalProperties:
            type: object
            required: [href, description]
            properties:
              href:
                type: string
              description:
                type: string

    # ── Brand ──────────────────────────────────────────────────────

    BrandSystem:
      type: object
      required: [$schema, version, name, minerals, ecosystem, typography, spacing]
      properties:
        $schema:
          type: string
          example: "https://design.nyuchi.com/schema/brand.json"
        "@context":
          type: string
          const: "https://schema.org"
        "@type":
          type: string
          const: "Brand"
        version:
          type: string
          example: "4.0.39"
        name:
          type: string
          example: "Nyuchi Brand System"
        lastUpdated:
          type: string
          format: date
          example: "2026-03-09"
        homepage:
          type: string
          format: uri
          example: "https://design.nyuchi.com/brand"
        minerals:
          type: array
          minItems: 5
          maxItems: 5
          items:
            $ref: "#/components/schemas/Mineral"
        ecosystem:
          type: array
          items:
            $ref: "#/components/schemas/EcosystemBrand"
        typography:
          type: object
          required: [fonts, scale]
          properties:
            fonts:
              type: object
              additionalProperties:
                $ref: "#/components/schemas/FontSpec"
            scale:
              type: array
              items:
                $ref: "#/components/schemas/TypeScaleEntry"
        spacing:
          type: array
          items:
            $ref: "#/components/schemas/SpacingToken"
        radii:
          type: object
          additionalProperties:
            type: string
        semanticColors:
          type: array
          items:
            $ref: "#/components/schemas/SemanticColor"
        backgrounds:
          type: array
          items:
            $ref: "#/components/schemas/BackgroundToken"
        componentSpecs:
          type: array
          items:
            $ref: "#/components/schemas/ComponentSpec"
        accessibility:
          $ref: "#/components/schemas/Accessibility"
        voiceAndTone:
          $ref: "#/components/schemas/VoiceAndTone"
        philosophy:
          $ref: "#/components/schemas/Philosophy"

    Mineral:
      type: object
      required: [name, hex, lightHex, darkHex, cssVar, origin, symbolism, usage]
      properties:
        name:
          type: string
          example: "cobalt"
        hex:
          type: string
          pattern: "^#[0-9A-Fa-f]{6}$"
          example: "#0047AB"
        lightHex:
          type: string
          pattern: "^#[0-9A-Fa-f]{6}$"
        darkHex:
          type: string
          pattern: "^#[0-9A-Fa-f]{6}$"
        containerLight:
          type: string
          description: Light theme container background for this mineral
        containerDark:
          type: string
          description: Dark theme container background for this mineral
        cssVar:
          type: string
          example: "--color-cobalt"
        origin:
          type: string
          description: African origin of this mineral
        symbolism:
          type: string
        usage:
          type: string

    EcosystemBrand:
      type: object
      required: [name, meaning, language, role, mineral, url]
      properties:
        name:
          type: string
          example: "mukoko"
        meaning:
          type: string
        language:
          type: string
          example: "Shona"
        role:
          type: string
        description:
          type: string
        voice:
          type: string
        mineral:
          type: string
        url:
          type: string
          format: uri

    FontSpec:
      type: object
      required: [family, usage, reason]
      properties:
        family:
          type: string
          example: "Noto Sans"
        usage:
          type: string
        reason:
          type: string

    TypeScaleEntry:
      type: object
      required: [name, sizePx, sizeRem, lineHeight, weight, font, usage]
      properties:
        name:
          type: string
        sizePx:
          type: number
        sizeRem:
          type: string
        lineHeight:
          type: string
        weight:
          type: integer
        font:
          type: string
          enum: ["sans", "serif", "mono"]
        usage:
          type: string

    SpacingToken:
      type: object
      required: [name, px, rem, usage]
      properties:
        name:
          type: string
        px:
          type: number
        rem:
          type: string
        usage:
          type: string

    SemanticColor:
      type: object
      required: [name, light, dark, usage]
      properties:
        name:
          type: string
        light:
          type: string
        dark:
          type: string
        usage:
          type: string

    BackgroundToken:
      type: object
      required: [name, light, dark, usage]
      properties:
        name:
          type: string
        light:
          type: string
        dark:
          type: string
        usage:
          type: string

    ComponentSpec:
      type: object
      required: [name, heights, padding, borderRadius, minTouchTarget, variants]
      properties:
        name:
          type: string
        heights:
          type: object
          additionalProperties:
            type: number
        padding:
          type: string
        borderRadius:
          type: number
        minTouchTarget:
          type: number
          minimum: 48
        variants:
          type: array
          items:
            type: string

    Accessibility:
      type: object
      required: [standard, minTouchTarget]
      properties:
        standard:
          type: string
          example: "APCA 3.0 AAA"
        contrastDescription:
          type: string
        minTouchTarget:
          type: integer
          minimum: 48
        focusIndicator:
          type: string
        keyboardNavigation:
          type: string
        screenReaders:
          type: string

    VoiceAndTone:
      type: object
      required: [principles, doList, dontList]
      properties:
        principles:
          type: array
          items:
            type: string
        doList:
          type: array
          items:
            type: string
        dontList:
          type: array
          items:
            type: string

    Philosophy:
      type: object
      required: [name, meaning, pillars]
      properties:
        name:
          type: string
          example: "Ubuntu"
        meaning:
          type: string
          example: "I am because we are"
        pillars:
          type: array
          items:
            type: object
            required: [name, description]
            properties:
              name:
                type: string
              description:
                type: string

    # ── Registry ───────────────────────────────────────────────────

    RegistryIndex:
      type: object
      required: [$schema, name, homepage, items]
      properties:
        $schema:
          type: string
          const: "https://ui.shadcn.com/schema/registry.json"
        name:
          type: string
          example: "mukoko"
        homepage:
          type: string
          format: uri
          example: "https://design.nyuchi.com"
        items:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/RegistryItemSummary"

    RegistryItemSummary:
      type: object
      required: [name, type, description, dependencies, registryDependencies]
      properties:
        name:
          type: string
          example: "button"
        type:
          type: string
          enum: ["registry:ui", "registry:hook", "registry:lib"]
        description:
          type: string
        dependencies:
          type: array
          items:
            type: string
        registryDependencies:
          type: array
          items:
            type: string

    RegistryItem:
      type: object
      required: [$schema, name, type, description, dependencies, registryDependencies, files]
      properties:
        $schema:
          type: string
          const: "https://ui.shadcn.com/schema/registry-item.json"
        name:
          type: string
        type:
          type: string
          enum: ["registry:ui", "registry:hook", "registry:lib"]
        description:
          type: string
        dependencies:
          type: array
          items:
            type: string
        registryDependencies:
          type: array
          items:
            type: string
        files:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/RegistryFile"

    RegistryFile:
      type: object
      required: [path, type, content]
      properties:
        path:
          type: string
          example: "components/ui/button.tsx"
        type:
          type: string
          example: "registry:ui"
        content:
          type: string
          description: Full source code of the component file

    # ── Architecture ───────────────────────────────────────────────

    EcosystemArchitecture:
      type: object
      required: ["@context", "@type", name, principles, frameworkDecision]
      properties:
        "@context":
          type: string
          const: "https://schema.org"
        "@type":
          type: string
          const: "TechArticle"
        name:
          type: string
          example: "Nyuchi Ecosystem Architecture"
        principles:
          type: array
          minItems: 5
          items:
            $ref: "#/components/schemas/ArchitecturePrinciple"
        frameworkDecision:
          $ref: "#/components/schemas/FrameworkDecision"

    ArchitecturePrinciple:
      type: object
      required: [name, title, description, rationale, implementation]
      properties:
        name:
          type: string
        title:
          type: string
        description:
          type: string
        rationale:
          type: string
        implementation:
          type: string

    FrameworkDecision:
      type: object
      required: [name, approach, framework, rationale, sovereigntyAdvantage, platforms, harmonyOs]
      properties:
        name:
          type: string
        approach:
          type: string
        framework:
          type: string
        rationale:
          type: string
        sovereigntyAdvantage:
          type: string
        platforms:
          type: array
          items:
            $ref: "#/components/schemas/PlatformTarget"
        harmonyOs:
          type: object
          required: [approach, rationale, status]
          properties:
            approach:
              type: string
            rationale:
              type: string
            status:
              type: string

    PlatformTarget:
      type: object
      required: [name, strategy, status]
      properties:
        name:
          type: string
        strategy:
          type: string
        status:
          type: string
          enum: ["production", "planned", "research"]

    DataLayerArchitecture:
      type: object
      required: ["@context", "@type", name, localDataLayer, cloudLayer, dataOwnership]
      properties:
        "@context":
          type: string
          const: "https://schema.org"
        "@type":
          type: string
          const: "TechArticle"
        name:
          type: string
          example: "Nyuchi Data Layer Architecture"
        localDataLayer:
          type: array
          items:
            $ref: "#/components/schemas/DataLayerTechnology"
        cloudLayer:
          type: array
          items:
            $ref: "#/components/schemas/CloudService"
        dataOwnership:
          type: array
          items:
            $ref: "#/components/schemas/DataOwnershipRule"

    DataLayerTechnology:
      type: object
      required: [name, role, platform, description, sovereignty]
      properties:
        name:
          type: string
        role:
          type: string
        platform:
          type: string
          enum: ["native", "browser", "both"]
        description:
          type: string
        sovereignty:
          $ref: "#/components/schemas/SovereigntyAssessment"

    CloudService:
      type: object
      required: [name, role, consistencyModel, database, dataCategories, description, sovereignty]
      properties:
        name:
          type: string
        role:
          type: string
        consistencyModel:
          type: string
          enum: ["strict", "eventual"]
        database:
          type: string
        dataCategories:
          type: array
          items:
            type: string
        description:
          type: string
        sovereignty:
          $ref: "#/components/schemas/SovereigntyAssessment"

    DataOwnershipRule:
      type: object
      required: [category, consistencyModel, database, examples, ownership, description]
      properties:
        category:
          type: string
        consistencyModel:
          type: string
          enum: ["strict", "eventual", "aggregate"]
        database:
          type: string
        examples:
          type: array
          items:
            type: string
        conflictResolution:
          type: string
        ownership:
          type: string
          enum: ["user-private", "community-shared", "public-open"]
        description:
          type: string

    PipelineArchitecture:
      type: object
      required: ["@context", "@type", name, stages]
      properties:
        "@context":
          type: string
          const: "https://schema.org"
        "@type":
          type: string
          const: "TechArticle"
        name:
          type: string
          example: "Nyuchi Open Data Pipeline"
        stages:
          type: array
          items:
            $ref: "#/components/schemas/PipelineStage"

    PipelineStage:
      type: object
      required: [name, role, description, sovereignty]
      properties:
        name:
          type: string
        role:
          type: string
        description:
          type: string
        sovereignty:
          $ref: "#/components/schemas/SovereigntyAssessment"

    SovereigntyReport:
      type: object
      required: ["@context", "@type", name, assessments, removedTechnologies]
      properties:
        "@context":
          type: string
          const: "https://schema.org"
        "@type":
          type: string
          const: "TechArticle"
        name:
          type: string
          example: "Nyuchi Technology Sovereignty"
        assessments:
          type: array
          items:
            $ref: "#/components/schemas/SovereigntyAssessment"
        removedTechnologies:
          type: array
          items:
            $ref: "#/components/schemas/RemovedTechnology"

    SovereigntyAssessment:
      type: object
      required:
        [technology, role, license, governance, sovereigntyRisk, forkable, selfHostable, rationale]
      properties:
        technology:
          type: string
        role:
          type: string
        license:
          type: string
        governance:
          type: string
        sovereigntyRisk:
          type: string
          enum: ["none", "low", "removed"]
        forkable:
          type: boolean
        selfHostable:
          type: boolean
        rationale:
          type: string

    RemovedTechnology:
      type: object
      required: [name, previousRole, reason, replacement, migrationPath]
      properties:
        name:
          type: string
        previousRole:
          type: string
        reason:
          type: string
        replacement:
          type: string
        migrationPath:
          type: string

    # ── Health ─────────────────────────────────────────────────────

    HealthStatus:
      type: object
      required: [status, timestamp, checks, version]
      properties:
        status:
          type: string
          enum: ["healthy", "degraded", "unhealthy"]
        timestamp:
          type: string
          format: date-time
        checks:
          type: object
          required: [registry, filesystem]
          properties:
            registry:
              $ref: "#/components/schemas/HealthCheck"
            filesystem:
              $ref: "#/components/schemas/HealthCheck"
        version:
          type: string

    HealthCheck:
      type: object
      required: [status, latencyMs]
      properties:
        status:
          type: string
          enum: ["pass", "fail"]
        latencyMs:
          type: integer
        message:
          type: string
        itemCount:
          type: integer

    # ── 3D Frontend Architecture ──────────────────────────────────

    FrontendAxis:
      type: object
      required: [name, title, description, geometry, metaphor, sortOrder]
      properties:
        name: { type: string, example: "x-axis" }
        title: { type: string, example: "Horizontal Composition" }
        description: { type: string }
        geometry:
          type: string
          enum: [horizontal, vertical, depth, external]
        metaphor: { type: string }
        sortOrder: { type: integer }

    FrontendAxesResponse:
      type: object
      required: ["@context", "@type", name, itemListElement]
      properties:
        "@context": { type: string, example: "https://schema.org" }
        "@type": { type: string, example: "ItemList" }
        name: { type: string }
        description: { type: string }
        itemListElement:
          type: array
          items:
            $ref: "#/components/schemas/FrontendAxis"

    FrontendLayer:
      type: object
      required: [layerNumber, subLabel, title, axisName, role, description, covenant, stakeholder]
      properties:
        layerNumber: { type: integer, minimum: 1, maximum: 10 }
        subLabel: { type: string, example: "primitive" }
        title: { type: string }
        axisName: { type: string, example: "x-axis" }
        role: { type: string }
        description: { type: string }
        covenant: { type: string }
        stakeholder: { type: string }
        implementationRules:
          type: array
          items: { type: string }
        sortOrder: { type: integer }

    FrontendLayersResponse:
      type: object
      required: ["@context", "@type", name, itemListElement]
      properties:
        "@context": { type: string }
        "@type": { type: string, example: "ItemList" }
        name: { type: string }
        description: { type: string }
        itemListElement:
          type: array
          items:
            $ref: "#/components/schemas/FrontendLayer"

    # ── Ubuntu doctrine ───────────────────────────────────────────

    UbuntuPillar:
      type: object
      required: [name, shona, title, description, sphere, platformSurface, source]
      properties:
        name: { type: string }
        shona: { type: string, description: "Shona-language name" }
        title: { type: string }
        description: { type: string }
        sphere: { type: string, description: "Region of life the pillar covers" }
        platformSurface: { type: string, description: "Software surface the pillar maps to" }
        source: { type: string, description: "Origin citation for the pillar" }
        sortOrder: { type: integer }

    UbuntuPillarsResponse:
      type: object
      required: ["@context", "@type", name, itemListElement]
      properties:
        "@context": { type: string }
        "@type": { type: string, example: "ItemList" }
        name: { type: string }
        description: { type: string }
        itemListElement:
          type: array
          items:
            $ref: "#/components/schemas/UbuntuPillar"

    UbuntuPrinciple:
      type: object
      required: [name, shona, title, description, expression, source]
      properties:
        name: { type: string }
        shona: { type: string }
        title: { type: string }
        description: { type: string }
        expression: { type: string, description: "How the principle shows up in software" }
        source: { type: string }
        sortOrder: { type: integer }

    UbuntuPrinciplesResponse:
      type: object
      required: ["@context", "@type", name, itemListElement]
      properties:
        "@context": { type: string }
        "@type": { type: string, example: "ItemList" }
        name: { type: string }
        description: { type: string }
        itemListElement:
          type: array
          items:
            $ref: "#/components/schemas/UbuntuPrinciple"

    # ── Content (docs + changelog) ────────────────────────────────

    DocPageSummary:
      type: object
      required: [slug, title]
      properties:
        slug: { type: string }
        title: { type: string }
        summary: { type: string }
        layer: { type: integer, nullable: true }
        updatedAt: { type: string, format: date-time }

    DocPage:
      allOf:
        - $ref: "#/components/schemas/DocPageSummary"
        - type: object
          properties:
            body: { type: string, description: "Long-form markdown/MDX content" }

    ChangelogEntry:
      type: object
      required: [version, title, createdAt]
      properties:
        version: { type: string, example: "4.0.39" }
        title: { type: string }
        description: { type: string }
        layersAffected:
          type: array
          items: { type: integer }
        axesAffected:
          type: array
          items: { type: string }
        componentsAdded:
          type: array
          items: { type: string }
        componentsModified:
          type: array
          items: { type: string }
        componentsDeprecated:
          type: array
          items: { type: string }
        changedBy: { type: string }
        createdAt: { type: string, format: date-time }

  responses:
    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    ServiceUnavailable:
      description: Supabase not configured — returns setup instructions
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ServiceUnavailableError"
