Skip to content
Ocean-Atlasv0.1.0Canonical Knowledge

Knowledge API Service

This decision is Accepted. services/knowledge-api is implemented, depends only on builders/core, and runs as a second process alongside the existing static site, reachable exclusively over Fly’s private 6PN network.

The “Expose Ocean-Atlas Knowledge APIs” epic requires programmatic access to Ocean-Atlas knowledge for Ocean Studio, Ocean CLI, Ocean Assistant, generators, and compiler diagnostics — consumers that should not need to depend on the website. adr-atlas-builder-boundary already anticipated this work and deliberately scoped “a runtime Atlas API” out of Core V1, calling it a “future story.” adr-search-today-structured-retrieval-tomorrow separately requires that whenever that future retrieval work happens, it must consume Core’s normalized AtlasModel — never scrape rendered dist/ HTML, never re-parse canonical Markdown directly.

Between those two ADRs and this one, rfc.versioned-atlas-model (Draft, 2026-08-14) proposed a third path: publish a versioned, static JSON projection of AtlasModel at build time (/api/atlas-model/v1.json), served by the existing static site with no new runtime component. That RFC explicitly considered and rejected a runtime query API (§7.1, “Build a runtime query API instead of a static artifact”), reasoning that “a live API is a larger infrastructure commitment… than is justified before any concrete consumer exists.” At the time it was written, that was correct — no consumer had been named.

This epic now supplies exactly the concrete consumer set the RFC’s rejection was conditioned on: Ocean Studio, Ocean CLI, Ocean Assistant, generators, and compiler diagnostics, each wanting live, queryable access (including real server-side search with query parameters, not a client-downloaded static blob) rather than a file they would each have to fetch and index themselves. The RFC’s static-artifact proposal remains a reasonable design for a different problem — offline or build-time consumers with no need for live queries — but it does not serve this epic’s actual consumers, and continuing to leave it as an open, undecided Draft while implementing a different approach in parallel would leave the repository’s own decision record silently contradicting itself. This ADR resolves that directly: it supersedes rfc.versioned-atlas-model rather than leaving it unaddressed.

services/knowledge-api is a new runtime HTTP service that depends only on builders/core’s buildAtlasModel — never canonical Markdown directly, never generated presentation output (dist/, .generated/). This extends adr.atlas-builder-boundary’s one-directional dependency rule to a second, runtime downstream consumer: the build-time presentation adapter (builders/astro-starlight) and this runtime service are siblings downstream of Core, and neither depends on the other.

  • Snapshot-per-process. buildAtlasModel({ repositoryRoot }) runs once at process startup; there is no live filesystem watching in production. A content change requires a redeploy — consistent with the deterministic, reproducible-build requirement adr.atlas-builder-boundary §17 already establishes, and with fly.toml’s existing strategy = "immediate" redeploy model.
  • Versioned /v1 route prefix. Breaking changes require a new /v2 prefix; additive changes (new fields, new routes) stay within v1. Every response echoes AtlasModel.schemaVersion and apiVersion.
  • Network-private by construction, not by application-level authentication. The service is reachable only via Fly’s private 6PN network, at http://ocean-atlas.internal:8080/v1 — the same convention already used by other Ocean services (ocean-audit.internal:8080/v1, ocean-bundle.internal:8080/v1). It is deliberately never declared as a public [http_service]/[[services]] block in fly.toml; that omission, not an authentication check, is what keeps it off the public internet. There is no application-level authentication for v1 — every route is GET-only and read-only, and the trust boundary is “any Machine on the Ocean Fly organization’s private network,” matching how sibling Ocean services already operate.
  • Deployment topology. Same Docker image and Fly Machine as the public website. The node process binds 0.0.0.0:8080 (6PN traffic arrives at the Machine’s private address, not loopback), but that port is simply never declared public — nginx and its public port-80 site are otherwise untouched.
  • No HTTP framework. A ~15-route, GET-only surface is served by a small hand-rolled node:http router, matching builders/core’s zero-runtime-dependency precedent.

4.1 A static AtlasModel projection (rfc.versioned-atlas-model)

Section titled “4.1 A static AtlasModel projection (rfc.versioned-atlas-model)”

This is the alternative this ADR formally supersedes. A build-time static JSON artifact remains a smaller, purely additive change with no new runtime process, and is worth reconsidering later for consumers that genuinely want an offline or build-time snapshot rather than a live query interface. It does not, however, serve this epic’s named consumers, who need live search and filtering rather than a downloadable blob they would each have to index themselves. Superseded here, not rejected outright — the RFC’s own document is kept as historical proposal context per decisions.rfcs.index’s lifecycle rules, marked Superseded with a superseded-by relationship to this ADR.

Rejected. ocean-atlas.internal already gives the right service identity under the existing ocean-<name>.internal convention; a second app would double the deployment surface for no isolation benefit at this scale.

4.3 Public exposure via an nginx reverse proxy on the same public port

Section titled “4.3 Public exposure via an nginx reverse proxy on the same public port”

Considered and rejected after explicit direction: the API must not be internet-reachable, only reachable from other Ocean services over Fly’s private network. An earlier version of this design proxied /api/* through nginx on the public site’s port; that would have made the API publicly fetchable by anyone who could reach the website, which is exactly what this decision exists to avoid.

Rejected. Entangles a presentation framework with non-web consumers (the Ocean CLI, compiler diagnostics) that have no reason to depend on it, and breaks the fully-static dist/ output model adr.atlas-builder-boundary establishes for the website.

4.5 builders/astro-starlight consuming the new API at build time

Section titled “4.5 builders/astro-starlight consuming the new API at build time”

Rejected for now, despite this epic’s own note that “the website should consume these APIs rather than directly accessing the underlying knowledge source where practical.” Making the static build depend on a running HTTP service would make it network-dependent and non-deterministic, contradicting adr.atlas-builder-boundary §17’s requirement that deleting generated content and dist/ and rebuilding must reliably recreate the site. It would also be architecturally odd now that the API is private-network-only: a public static build cannot depend on a private-network-only service anyway. Recorded here as a known, deliberately unresolved tension rather than silently promised or silently implemented.

4.6 A full external API gateway or auth provider

Section titled “4.6 A full external API gateway or auth provider”

Rejected. No such gateway exists yet for Ocean-Atlas to sit behind; the Fly private-network boundary is the gateway substitute for v1.

4.7 Live filesystem watching in production

Section titled “4.7 Live filesystem watching in production”

Rejected. Contradicts the static-build determinism this repository already relies on, and turns a bad edit into a runtime crash instead of a controlled build failure.

4.8 A heavier HTTP framework (Express, Fastify, etc.)

Section titled “4.8 A heavier HTTP framework (Express, Fastify, etc.)”

Rejected. The route count and complexity do not justify a new dependency on what is Ocean-Atlas’s first public-facing runtime process; a small hand-rolled router is easier to audit.

Rejected. atlas.knowledge-architecture §20 already treats semantic search as a distinct future layer on top of keyword search, not a v1 requirement; no proven need exists yet.

  • Ocean Studio, Ocean CLI, Ocean Assistant, generators, and compiler diagnostics gain live, queryable access to Ocean-Atlas knowledge without depending on the website or re-implementing Markdown parsing.
  • The one-directional dependency rule from adr.atlas-builder-boundary now has a second proof point: a runtime consumer can sit downstream of Core exactly like a build-time adapter does, without either depending on the other.
  • No new public attack surface: the API is unreachable from the public internet by construction (absence of a public Fly service declaration), not by a security control that could be misconfigured.
  • No new Fly app or infrastructure spend — the same Machine and Docker image serve both the public site and the private API.
  • The repository’s decision record stays internally consistent: the contradiction between this ADR and the Draft RFC is resolved explicitly rather than left standing.
  • This is Ocean-Atlas’s first long-running (non-static-build) process. If it crashes, nginx keeps serving the public site fine, but the API becomes unreachable until the container restarts — there is no supervisor restarting just the node process (see Implementation Implications).
  • Any Machine on the Ocean Fly organization’s private network can call the API unauthenticated. This mirrors how sibling Ocean services already operate, but is a real trust boundary, not a hardened one; if it ever proves insufficient, a future story would need to add a shared-secret or token check.
  • builders/astro-starlight still calls buildAtlasModel directly at build time rather than through this API (see 4.5), so this epic’s “website should consume these APIs where practical” note is only partially honored — deliberately, and recorded as such rather than silently dropped.
  • Superseding rfc.versioned-atlas-model here means its static-artifact proposal is not being built now. If a future consumer genuinely needs an offline/build-time snapshot rather than a live API, that proposal’s design remains a reasonable starting point, not wasted work.
  • decisions/rfcs/rfc-versioned-atlas-model.md’s Status is updated to Superseded with a superseded-by: adr.knowledge-api-service relationship, and decisions/rfcs/index.md’s table is updated to match, per decisions.rfcs.index’s own stated lifecycle (Draft → Review → Accepted / Rejected / Withdrawn → Superseded).
  • services/knowledge-api may depend only on @ocean-atlas/builder-core; a future change that reads canonical Markdown directly, or reads builders/astro-starlight’s dist//.generated/ output as this service’s data source, does not comply with this decision.
  • docker/nginx.conf and fly.toml’s public [http_service] block must remain untouched by this service — no /api/ or /v1/ proxy location, no second public service declaration. Adding either would silently reintroduce public exposure and must be treated as a regression against this ADR, not a routine change.
  • The node process must bind 0.0.0.0 (required for Fly 6PN reachability) but must never be declared in a public Fly service definition.
  • A route introduced in /v1 must not change or remove an existing response field without a version bump to /v2.
  • Unlike the static site, this service calls buildAtlasModel() at process startup, so the runtime image must include the canonical Markdown source itself (and the ocean-examples submodule), not just the generated site. Dockerfile’s runtime stage copies these explicitly, mirroring CANONICAL_DIRECTORIES in builders/core/src/discover.ts; a future new canonical top-level directory needs a matching line there, or the API will silently omit its content rather than fail the build.
  • A future change that makes services/knowledge-api reachable from the public internet — directly, or by proxying it through nginx’s public port — does not comply with this decision unless a new ADR explicitly revisits the private-network boundary.
  • A future change that has services/knowledge-api read canonical Markdown or generated presentation output instead of builders/core’s AtlasModel does not comply with this decision.
  • rfc.versioned-atlas-model must not be silently rewritten to describe this implementation; it remains historical proposal context, marked Superseded.
  • adr.atlas-builder-boundary — the Core/adapter one-directional dependency rule this decision extends to a second, runtime consumer, and the ADR that originally deferred “a runtime Atlas API” as a future story this ADR now delivers.
  • adr.search-today-structured-retrieval-tomorrow — requires future retrieval to consume Core’s structured model rather than scrape rendered output; this service is the first concrete implementation of that constraint.
  • adr.stable-knowledge-identity — the stable IDs this service’s by-ID lookup, relationship resolution, and search results depend on.
  • rfc.versioned-atlas-model — the static-artifact proposal this decision supersedes, kept as historical proposal context.
  • atlas.knowledge-architecture — §20–21 describe the retrieval and AI/RAG readiness goals this service is a concrete step toward.
  • architecture.knowledge-api — the reference document describing this service’s route table, envelope, and network stance.

These semantic relationships are declared in the document metadata.