Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://adrkit.dev/decisions/>. 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`.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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`;
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://adrkit.dev/decisions/>. 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

Expand All @@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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.**
22 changes: 13 additions & 9 deletions docs/sdk-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/bin.ts
Original file line number Diff line number Diff line change
@@ -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`
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/config.ts
Original file line number Diff line number Diff line change
@@ -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 |
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/corpus.ts
Original file line number Diff line number Diff line change
@@ -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:**
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/messages.ts
Original file line number Diff line number Diff line change
@@ -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.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/openapi.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/routes.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/server.ts
Original file line number Diff line number Diff line change
@@ -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:
*
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/shapes.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/validate.ts
Original file line number Diff line number Diff line change
@@ -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.
*/

Expand Down
Loading