let d = import "adr-defaults.ncl" in

d.make_adr {
  id     = "adr-007",
  title  = "API Surface Discoverability via #[onto_api] Proc-Macro",
  status = 'Accepted,
  date   = "2026-03-23",

  context = "ontoref-daemon exposes ~28 HTTP routes across api.rs, sync.rs, and other handler modules. Before this decision, the authoritative route list existed only in the axum Router definition — undiscoverable without reading source. MCP agents, CLI users, and the web UI had no machine-readable way to enumerate routes, their auth requirements, parameter shapes, or actor restrictions. OpenAPI was considered but rejected as a runtime dependency that would require schema maintenance separate from the handler code. The `#[onto_api]` proc-macro in `ontoref-derive` addresses this by making the handler annotation the single source of truth: the macro emits `inventory::submit!(ApiRouteEntry{...})` at link time, and `api_catalog::catalog()` collects them via `inventory::collect!`. No runtime registry, no startup allocation, no separate schema file.",

  decision = "Every HTTP handler in ontoref-daemon must carry `#[onto_api(method, path, description, auth, actors, params, tags)]`. The proc-macro (in `crates/ontoref-derive`) emits `inventory::submit!(ApiRouteEntry{...})` at link time. `GET /api/catalog` calls `api_catalog::catalog()` — a pure function over `inventory::iter::<ApiRouteEntry>()` — and returns the annotated surface as JSON. The web UI at `/ui/{slug}/api` renders it with client-side filtering. `describe api [--actor] [--tag] [--auth] [--fmt]` queries this endpoint from the CLI. The MCP tool `ontoref_api_catalog` calls `catalog()` directly without HTTP. This surfaces the complete API to three actors (browser, CLI, MCP agent) from one annotation site per handler.",

  rationale = [
    {
      claim  = "Compile-time registration eliminates drift",
      detail = "inventory uses linker sections (.init_array on ELF, __mod_init_func on Mach-O) to collect ApiRouteEntry items at link time. A handler that exists in the binary but lacks #[onto_api] is detectable — cargo test or a Grep constraint catches the gap. A handler that has #[onto_api] but is removed will automatically disappear from catalog(). The annotation and the implementation are co-located and co-deleted.",
    },
    {
      claim  = "Zero runtime overhead and zero startup allocation",
      detail = "inventory::iter::<ApiRouteEntry>() walks a linked-list built by the linker — no HashMap, no Arc, no lazy_static. catalog() is a pure function that sorts and returns &'static references. This satisfies the ontoref axiom 'Protocol, Not Runtime': the catalog is available without daemon state, without DB, without cache warmup.",
    },
    {
      claim  = "Three-surface consistency without duplication",
      detail = "Browser (api_catalog.html), CLI (describe api), and MCP (ontoref_api_catalog) all read the same inventory. A manual registry or OpenAPI spec would require three update sites per route change. With #[onto_api], changing a route's auth requirement is a one-line annotation edit that propagates to all surfaces on next build.",
    },
  ],

  consequences = {
    positive = [
      "API surface is always current: catalog() reflects exactly the handlers compiled into the binary",
      "Agents (MCP) can call ontoref_api_catalog on cold start to understand the full HTTP surface without prior knowledge",
      "describe api --actor agent filters to actor-appropriate routes; agents can self-serve their available endpoints",
      "New handlers without #[onto_api] are caught by the Grep constraint before merge",
      "inventory (MIT, 0.3.x) has no transitive deps — passes deny.toml audit",
    ],
    negative = [
      "#[onto_api] parameters are stringly-typed — a misspelled auth value is not caught at compile time (only at review/Grep)",
      "inventory linker trick is platform-specific: supported on Linux (ELF), macOS (Mach-O), Windows (PE) but not on targets that lack .init_array equivalent",
      "Proc-macro adds a new crate (ontoref-derive) to the workspace; ontoref-ontology users who only need zero-dep struct loading do not need it",
    ],
  },

  alternatives_considered = [
    {
      option       = "OpenAPI / utoipa with generated JSON schema",
      why_rejected = "Requires maintaining a separate schema artifact (openapi.json) and a runtime schema struct tree. The schema can drift from actual handler signatures. utoipa adds ~15 transitive deps including serde_yaml. Violates 'Protocol, Not Runtime' — the schema becomes a runtime artifact rather than a compile-time invariant.",
    },
    {
      option       = "Manual route registry (Vec<RouteInfo> in main.rs)",
      why_rejected = "A manually maintained Vec has guaranteed drift: handlers are added, routes change, and the Vec is updated inconsistently. Proven failure mode in the previous session where insert_mcp_ctx listed 15 tools while the router had 27.",
    },
    {
      option       = "Runtime reflection via axum Router introspection",
      why_rejected = "axum does not expose a stable introspection API for registered routes. Workarounds (tower_http trace layer capture, method_router hacks) are brittle across axum versions and cannot surface handler metadata (auth, actors, params).",
    },
  ],

  constraints = [
    {
      id       = "onto-api-on-all-handlers",
      claim    = "Every public HTTP handler in ontoref-daemon must carry #[onto_api(...)]",
      scope    = "ontoref-daemon (crates/ontoref-daemon/src/api.rs, crates/ontoref-daemon/src/sync.rs)",
      severity = 'Hard,
      check    = { tag = 'Grep, pattern = "#\\[onto_api", paths = ["crates/ontoref-daemon/src/api.rs", "crates/ontoref-daemon/src/sync.rs"], must_be_empty = false },
      rationale = "catalog() is only as complete as the set of annotated handlers. Unannotated handlers are invisible to agents, CLI, and the web UI — equivalent to undocumented and unauditable routes.",
    },
    {
      id       = "inventory-feature-gate",
      claim    = "inventory must remain a workspace dependency gated behind the 'catalog' feature of ontoref-derive; ontoref-ontology must not depend on inventory",
      scope    = "ontoref-ontology (Cargo.toml), ontoref-derive (Cargo.toml)",
      severity = 'Hard,
      check    = { tag = 'Grep, pattern = "inventory", paths = ["crates/ontoref-ontology/Cargo.toml"], must_be_empty = true },
      rationale = "ontoref-ontology is the zero-dep adoption surface (ADR-001). Adding inventory — even as an optional dep — violates that contract and makes protocol adoption heavier for downstream crates that only need typed NCL loading.",
    },
  ],

  related_adrs = ["adr-001"],

  ontology_check = {
    decision_string    = "Use #[onto_api] proc-macro + inventory linker registration as the single source of truth for the HTTP API surface; surface via GET /api/catalog, describe api CLI subcommand, and ontoref_api_catalog MCP tool",
    invariants_at_risk = ["protocol-not-runtime"],
    verdict            = 'Safe,
  },
}
