diff --git a/AGENTS.md b/AGENTS.md
index 94a06a4..036cd73 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1079,7 +1079,11 @@ The nested build-only Action (`packages/ci/portal`, `dist/portal-action.js`)
is described below.
**Rung 1**: unit, contract, purity, and mutation tests, a Node run of the
bundled generator, and a maintainer browser check on this repository's corpus.
-Load-bearing:
+Since #310, `site.yml` also builds this repository's corpus with the shipped
+portal Action and publishes it at . That is
+supplemental dogfood, not rung 2: ADR-0014's rung 2 needs a run in a separate,
+isolated reference repository, and an in-repository consumer stays at rung 1
+(ADR-0053). Load-bearing:
- **It lives outside the CLI.** ADR-0033 decision 11 keeps HTML out of
`@adrkit/cli`; do not add an `adr site` or `--format html`.
@@ -1128,7 +1132,8 @@ documented workflow is in `site/src/content/docs/ci.mdx`. **Rung 1**: unit,
contract, and mutation tests, and a Node run of the committed bundle against a
git fixture. `site.yml` builds this repository's corpus with it into
`/decisions/` on adrkit.dev and checks the bundle (`scripts/check-portal-bundle.ts`);
-that is rung 2 only once a deploy has been observed serving it. Load-bearing:
+that deploy is supplemental dogfood; rung 2 needs a run in an isolated reference
+repository (ADR-0014). Load-bearing:
- **Logic is `src/portal-action.ts`, wiring is the entrypoint.** Only
`portal-action-entrypoint.ts` imports `@actions/*`, as for the queue Action,
@@ -1196,11 +1201,13 @@ external/community validation has been recorded.
## The read-only REST server (`packages/server`)
`@adrkit/server` (private) is the image's `server` selector, under
-[ADR-0055](./docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-th.md)
+[ADR-0055](./docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-t.md)
(**accepted**). **It authenticates no one.** `node:http` over `@adrkit/sdk`
and `@adrkit/portal`'s pure builder, and nothing else (`check-deps` admits
-exactly those two). **Rung 1**: unit, contract, and side-effect-denial tests
-plus a local Podman smoke; the APIM path is unmeasured. Load-bearing:
+exactly those two). **Implemented, rung 1** (#314): unit, contract, and
+side-effect-denial tests, a local Podman smoke, and a CI container smoke of the
+`server` selector under `--read-only` with a `:ro` corpus mount; the APIM path
+is unmeasured. Load-bearing:
- **The route table is the server.** `src/routes.ts` is data; the dispatcher
matches it and `/openapi.json` is generated from it. Every row is `GET`;
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 26aa1ac..a07a40f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -69,7 +69,7 @@ Until `1.0.0`, minor releases may include breaking changes
`/api/v1/corpora/{corpus}`: `records`, `records/{id}`, `graph?kinds=`,
`queue?asOf=`, `issues`, and `governing?path=`, plus `/api/v1/corpora`,
`/openapi.json`, `/healthz`, and the decision portal at `/`
- ([ADR-0055](docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-th.md)).
+ ([ADR-0055](docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-t.md)).
Every answer is `{ corpus, commit, data }`, with the commit read from the
`.adrkit-corpus.json` file the populator writes next to the records; a corpus
without one is not served. Configure it with `ADRKIT_SERVER_CORPORA`
diff --git a/README.md b/README.md
index 3c9aa00..973d356 100644
--- a/README.md
+++ b/README.md
@@ -166,7 +166,7 @@ corpus it serves, private repositories included.
The `server` selector serves a read-only REST API and the decision portal over
one or more corpora
-([ADR-0055](docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-th.md)).
+([ADR-0055](docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-t.md)).
It answers `GET` and `HEAD` only, and every other method gets `405`. Every data
response is `{ corpus, commit, data }`. It runs no `git`, spawns no process, and
opens no outbound connection. It binds `127.0.0.1` unless `ADRKIT_SERVER_HOST`
diff --git a/docs/adr/0054-publish-a-static-decision-portal-for-github-pages-built-outside-the-cli-from-the.md b/docs/adr/0054-publish-a-static-decision-portal-for-github-pages-built-outside-the-cli-from-the.md
index 86f323d..4844380 100644
--- a/docs/adr/0054-publish-a-static-decision-portal-for-github-pages-built-outside-the-cli-from-the.md
+++ b/docs/adr/0054-publish-a-static-decision-portal-for-github-pages-built-outside-the-cli-from-the.md
@@ -333,11 +333,21 @@ workflow deploys to GitHub Pages:
## Evidence rung
-Nothing here exists yet. The surface enters **rung 1** of ADR-0014 at best
-when action items 1 to 7 land: unit, contract, and purity tests, each
-observed failing first (ADR-0016), and a byte-identity check on the copies.
-A dogfood build of this repository's own corpus from `site.yml` (item 8)
-would be rung 2. No external party has run it, so rung 3 is absent.
+**Rung 1** of ADR-0014. Rung 1 landed with action items 1 to 7
+([#292](https://github.com/mbeacom/adrkit/pull/292) and
+[#302](https://github.com/mbeacom/adrkit/pull/302)): unit, contract, and purity tests, each observed failing
+first (ADR-0016), and a byte-identity check on the copies. Item 8 is done as
+written: since [#310](https://github.com/mbeacom/adrkit/pull/310) (2026-10-10),
+`site.yml` builds this repository's own corpus with the shipped portal Action
+and publishes it at . That is supplemental,
+maintainer-owned dogfood evidence, **not rung 2**: ADR-0014 defines rung 2 as a
+run in a separate, isolated reference repository with reproducible,
+self-verifying, fail-closed evidence, and ADR-0053 records that an
+in-repository consumer stays at rung 1. Item 8's own wording ("the rung-2
+dogfood") predates that reading and overstates it. Rung 2 needs the portal
+Action run in an isolated reference repository such as
+[`adrkit-t018-dogfood`](https://github.com/mbeacom/adrkit-t018-dogfood); rung 3
+(an external party) is not claimed.
## Action items
@@ -358,6 +368,12 @@ would be rung 2. No external party has run it, so rung 3 is absent.
nested bundle.
7. [x] Add a test that the portal's shipped strings contain no ratifying or
review command and no readiness word.
-8. [ ] Build this repository's corpus from `site.yml` and publish it under
- adrkit.dev as the rung-2 dogfood.
+8. [x] Build this repository's corpus from `site.yml` and publish it under
+ adrkit.dev as the rung-2 dogfood. **Done 2026-10-10 in
+ [#310](https://github.com/mbeacom/adrkit/pull/310): `site.yml` runs
+ `uses: ./packages/ci/portal`. Checked 2026-10-10: `/decisions/` and
+ `/decisions/page/app.mjs` answer 200 as `text/html` and `text/javascript`,
+ and `/decisions/portal-data.json` has `version: "1"`, 59 records (the count
+ `adr lint` checks), and commit e491a78 of `main`. Recorded in
+ [#294](https://github.com/mbeacom/adrkit/issues/294#issuecomment-6103512935).**
9. [x] Ratify or reject this record.
diff --git a/docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-t.md b/docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-t.md
index 1acf9b9..a66c410 100644
--- a/docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-t.md
+++ b/docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-t.md
@@ -352,30 +352,53 @@ non-Azure deployment needs it.
## Evidence rung
-**None yet; rung 1 at best when built** under ADR-0014. Nothing in this record
-is implemented, and it depends on ADR-0053's SDK. Rung 1 would be the unit,
+**Rung 1** under ADR-0014, since
+[#314](https://github.com/mbeacom/adrkit/pull/314) (2026-10-10). The unit,
contract, method-guard, verb-guard, error-text, side-effect-denial, and
-OpenAPI-agreement tests above, each observed failing first (ADR-0016), plus a
-local container smoke. The APIM path (clause 15) is unmeasured, and a
-reference-repository or external run does not exist.
+OpenAPI-agreement tests in `packages/server/test/` were each observed failing
+against a planted break first (ADR-0016), as #314 lists; the side-effect-denial
+test runs under Bun on the source and under Node on the bundle. A local Podman
+smoke ran the all-in-one image read-only with a `:ro` corpus, and the CI
+container smoke ("Smoke test the read-only server through the all-in-one
+image") runs the `server` selector under `--read-only` with a `:ro` mount and
+passed on `main`. The APIM path (clause 15, action item 8) is unmeasured, and
+no reference-repository or external run exists, so neither rung 2 nor rung 3
+is claimed.
## Action items
-1. [ ] Create `packages/server` (private) on `node:http` over `@adrkit/sdk`,
+1. [x] Create `packages/server` (private) on `node:http` over `@adrkit/sdk`,
with the routes, envelope, sidecar read, and swap of clauses 1 to 7, and
its `check-deps` entry (`@adrkit/sdk` and `@adrkit/portal` only) with a
- negative case.
-2. [ ] Add the method guard, the verb and transition guard, the error-text
+ negative case. **Done 2026-10-10 in [#314](https://github.com/mbeacom/adrkit/pull/314): `scripts/check-deps.ts` admits
+ exactly the two, and `scripts/check-deps.test.ts` rejects a third
+ workspace package and an HTTP framework.**
+2. [x] Add the method guard, the verb and transition guard, the error-text
sentinel test, and the side-effect-denial variant, each observed failing.
-3. [ ] Serve `/openapi.json` and test it against the route table.
-4. [ ] Serve ADR-0054's bundle and its data file from the loaded handle, with
- the headers of clause 9.
-5. [ ] Add the `server` selector and local target to the `Containerfile` and
+ **Done 2026-10-10 in [#314](https://github.com/mbeacom/adrkit/pull/314): `methods`, `writing-verbs`, `error-text`, and
+ `side-effect-denial` tests, observed failing against a planted POST route,
+ writing verb, `String(error)` in a 500, and `execSync` (under both
+ runtimes).**
+3. [x] Serve `/openapi.json` and test it against the route table. **Done
+ 2026-10-10 in [#314](https://github.com/mbeacom/adrkit/pull/314): generated from `src/routes.ts`; `openapi.test.ts` was
+ observed failing on planted drift.**
+4. [x] Serve ADR-0054's bundle and its data file from the loaded handle, with
+ the headers of clause 9. **Done 2026-10-10 in [#314](https://github.com/mbeacom/adrkit/pull/314): the data file is built
+ per request by `buildPortalData` from the loaded handle at the sidecar's
+ commit, served with the clause 9 CSP (`frame-ancestors 'none'` observed
+ failing when dropped), `nosniff`, `no-referrer`, and no CORS header.**
+5. [x] Add the `server` selector and local target to the `Containerfile` and
`scripts/container-entrypoint.sh`, and a CI smoke under
- `--read-only` with `:ro` mounts.
-6. [ ] Agree the `.adrkit-corpus.json` sidecar with ADR-0056.
-7. [ ] Document the selector in the README, leading with "this server
- authenticates no one".
+ `--read-only` with `:ro` mounts. **Done 2026-10-10 in [#314](https://github.com/mbeacom/adrkit/pull/314): the
+ `server` target and selector, and a CI step that builds the target and
+ smokes `adrkit:ci server` under `--read-only` with a `:ro` corpus mount,
+ green on `main` at a5b0646 and e491a78.**
+6. [x] Agree the `.adrkit-corpus.json` sidecar with ADR-0056. **Done
+ 2026-10-10: clause 4 here and ADR-0056 decision 4 carry the same contract
+ sentence, identical after whitespace and emphasis are normalized.**
+7. [x] Document the selector in the README, leading with "this server
+ authenticates no one". **Done 2026-10-10 in [#314](https://github.com/mbeacom/adrkit/pull/314): the README's "The
+ read-only REST server" section opens with that sentence.**
8. [ ] Measure APIM's "REST API as MCP server" over these routes with a
2025-era and a `2026-07-28` client (ADR-0056).
-9. [ ] Ratify or reject this record.
+9. [x] Ratify or reject this record. **Ratified by `@mbeacom` on 2026-10-10.**
diff --git a/docs/sdk-surface.md b/docs/sdk-surface.md
index f02e85d..b222eb5 100644
--- a/docs/sdk-surface.md
+++ b/docs/sdk-surface.md
@@ -352,15 +352,19 @@ Decisions this left open, and how they were settled:
ADR-0054) calls `openDecisions` and five of the six handle members (`records`, `issues`,
`queue`, `graph`, `governing`; not `get`), and takes its default `asOf` from `queue()`'s echo.
`bun test` runs it in CI over fixture corpora, and the portal Action's bundle is tested the
- same way. That consumer is build-time only: it opens a corpus once per build, so the handle's
- long-lived use is not exercised (ADR-0053 decision 8), and whether it satisfies ADR-0053
- action item 9 is for the publication record to decide. The consumer the surface was designed
- for, a Backstage backend, does not exist yet. ADR-0031's own trade-off — *"the risk is that the guess hardens before the
- Backstage plugin tests it"* — is not mitigated by this work, only made concrete enough to
- criticize.
-- **Implemented, not exercised.** The eight-call `explain` assembly the surface claims to absorb
- is now written and tested equal to `adr explain --json`; its fitness for a long-lived server
- consumer is still argued, not observed.
+ same way. That consumer is build-time only: it opens a corpus once per build. A second,
+ long-lived consumer now exists: the REST server (`packages/server`, ADR-0055) holds one
+ handle per corpus, serves `records`, `get`, `queue`, `graph`, `issues`, and `governing` from
+ it, and opens a fresh handle when a corpus's synced commit changes (clause 5), with its
+ tests and a CI container smoke over the built image. Together they exercise every handle
+ member, at build time and in a running process; whether that satisfies ADR-0053 action
+ item 9 is for the publication record to decide. The consumer the surface was first designed
+ for, a Backstage backend, exists only as a spike (`mbeacom/adrkit-backstage`), which used the
+ published CLI JSON, because an unpublished SDK consumed from source loads a stale prebuilt
+ core.
+- **Implemented and exercised in a server.** The eight-call `explain` assembly the surface
+ claims to absorb is written and tested equal to `adr explain --json`, and the REST server's
+ `governing` route runs it per request.
- **No release, and none prepared.** ADR-0031 clause 7 specifies `versioning: 'independent'`
*when* released; wiring that into `scripts/release-pack.ts` now would prepare an act clause 8
does not authorize, so it is deliberately absent — and asserted absent by test, so its absence
diff --git a/packages/server/src/bin.ts b/packages/server/src/bin.ts
index db9d825..e39d981 100644
--- a/packages/server/src/bin.ts
+++ b/packages/server/src/bin.ts
@@ -1,6 +1,6 @@
/**
* `adrkit-server`: the entry point of the image's `server` selector
- * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-th.md)
+ * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-t.md)
* clauses 12 to 14).
*
* **This server authenticates no one.** It binds `127.0.0.1` unless `ADRKIT_SERVER_HOST`
diff --git a/packages/server/src/config.ts b/packages/server/src/config.ts
index 7a11d5e..aa384a1 100644
--- a/packages/server/src/config.ts
+++ b/packages/server/src/config.ts
@@ -1,6 +1,6 @@
/**
* The server's configuration
- * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-th.md)
+ * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-t.md)
* clauses 6 and 14), read from the environment once at start.
*
* | Variable | Default | Meaning |
diff --git a/packages/server/src/corpus.ts b/packages/server/src/corpus.ts
index 390d772..652ea4e 100644
--- a/packages/server/src/corpus.ts
+++ b/packages/server/src/corpus.ts
@@ -1,6 +1,6 @@
/**
* One configured corpus: its sidecar, its loaded handle, and the swap between handles
- * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-th.md)
+ * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-t.md)
* clauses 4 and 5).
*
* **The sidecar contract, worded as in ADR-0055 clause 4 and ADR-0056 clause 4:**
diff --git a/packages/server/src/messages.ts b/packages/server/src/messages.ts
index 07315ea..d3602ec 100644
--- a/packages/server/src/messages.ts
+++ b/packages/server/src/messages.ts
@@ -1,6 +1,6 @@
/**
* Every error the server answers with, as fixed text looked up by code
- * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-th.md)
+ * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-t.md)
* clause 8). No stderr, exception message, stack, or file system path reaches a response:
* an error body is built from this table and nothing else.
*/
diff --git a/packages/server/src/openapi.ts b/packages/server/src/openapi.ts
index 68d764e..297b5eb 100644
--- a/packages/server/src/openapi.ts
+++ b/packages/server/src/openapi.ts
@@ -1,6 +1,6 @@
/**
* `/openapi.json`, generated from the route table
- * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-th.md)
+ * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-t.md)
* clause 11). Pure. Every operation is a `get`; there is no other method to describe.
*
* OpenAPI 3.0.3 rather than 3.1, because 3.0 is the version APIM's importer documents fully.
diff --git a/packages/server/src/routes.ts b/packages/server/src/routes.ts
index 10c03a5..e825af1 100644
--- a/packages/server/src/routes.ts
+++ b/packages/server/src/routes.ts
@@ -1,6 +1,6 @@
/**
* The route table: every path this server answers, as data
- * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-th.md)
+ * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-t.md)
* clauses 1, 2, 9, and 11).
*
* The dispatcher matches against this table and nothing else, and `/openapi.json` is generated
diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts
index 52ec678..11ea695 100644
--- a/packages/server/src/server.ts
+++ b/packages/server/src/server.ts
@@ -1,6 +1,6 @@
/**
* The read-only HTTP server
- * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-th.md)).
+ * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-t.md)).
*
* `node:http` and nothing else (clause 10). The handler:
*
diff --git a/packages/server/src/shapes.ts b/packages/server/src/shapes.ts
index 85c561f..5fc48e0 100644
--- a/packages/server/src/shapes.ts
+++ b/packages/server/src/shapes.ts
@@ -1,6 +1,6 @@
/**
* The response shapes of `/api/v1`, projected field by field from the SDK's
- * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-th.md)
+ * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-t.md)
* clauses 1, 3, and 7).
*
* Projected rather than passed through, for two reasons. The canonical serializer refuses
diff --git a/packages/server/src/validate.ts b/packages/server/src/validate.ts
index 7f10102..71d2dbb 100644
--- a/packages/server/src/validate.ts
+++ b/packages/server/src/validate.ts
@@ -1,6 +1,6 @@
/**
* Validation of everything a request carries, before any SDK call
- * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-th.md)
+ * ([ADR-0055](../../../docs/adr/0055-serve-a-read-only-rest-api-over-the-sdk-in-the-oci-image-and-reach-mcp-clients-t.md)
* clauses 7 and 8). Pure: no clock (the caller passes `now`), file, or environment.
*/