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
58 changes: 58 additions & 0 deletions docs/adr/0000-record-decisions-with-madr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
status: accepted
date: 2026-07-24
deciders: Brian Jin
---

# Record runtime decisions with MADR-format ADRs

## Context and problem statement

The runtime makes cross-cutting implementation decisions — repository layout, adapter strategy,
what to defer — that no single commit or pull request represents, because they are stances rather
than diffs. The commit and PR bodies here already carry excellent rationale, but a stance like "no
`packages/` umbrella" is owned by no one change and cannot be found by reading `git log`.

The specification project already has a process for design decisions: RFCs, in
`judgment-pack-spec` under `rfcs/` (`rfcs/0000-rfc-process`). But an RFC is a public,
comment-before-commit proposal aimed at reviewers who need to weigh in — appropriate for a
normative standard, and disproportionate for the implementation choices of one nonnormative
consumer that are, in practice, already made.

## Decision drivers

- Cross-cutting stances need a greppable, durable home separate from `git log`.
- The runtime has no external review body; comment-before-commit ceremony would be theatre.
- The record should stay lightweight enough that writing one is not a deterrent.

## Considered options

- Markdown ADRs (MADR) under `docs/adr/`.
- Adopt the specification's RFC process inside the runtime as well.
- Keep relying on commit and pull-request bodies plus [architecture.md](../architecture.md).

## Decision outcome

Chosen option: MADR-format ADRs under `docs/adr/`, because they fit decisions already made and keep
the record cheap. The repository boundary decides the mechanism:

- **Runtime → ADRs.** Implementation decisions, recorded after the fact.
- **Specification → RFCs.** Normative, public, cross-implementation proposals open for comment.

A runtime decision graduates into a spec RFC only if it ever needs agreement across independent
implementations.

[architecture.md](../architecture.md) remains the description of the system as it is; ADRs hold the
history of why and when. architecture.md links to an ADR rather than restating it.

### Consequences

- Good, because a future contributor finds "why is there no `packages/`?" in one file, not a commit.
- Good, because it matches a repository that already values written rationale (CHANGELOG, VERSIONING, NOTICE, releasing).
- Bad, because it is a second place to keep honest; an accepted ADR that no longer holds must be superseded, not silently ignored.
- Revisit when the runtime grows a contributor community that needs comment-before-commit, at which point some decisions may warrant a runtime RFC process.

## More information

Status lifecycle: `proposed` → `accepted` → (`deprecated` | `superseded by NNNN`). Accepted ADRs are
immutable; a change is a new ADR. See [README.md](README.md) for how to add one.
58 changes: 58 additions & 0 deletions docs/adr/0001-idiomatic-go-single-module-layout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
status: accepted
date: 2026-07-24
deciders: Brian Jin
---

# Idiomatic Go single-module layout; no `packages/` umbrella

## Context and problem statement

An early structure sketch drew a polyglot-monorepo layout: a `packages/` parent holding
`core/`, `conformance/`, `cli/`, `api/`, `mcp/`, `sdk/`, and a `pyproject.toml`-or-equivalent at the
root. The runtime is in fact a single Go module. The question was whether to reshape the repository
toward that sketch or to follow Go's own conventions.

## Decision drivers

- In Go a directory is a package and its path **is** the import path; there are no neutral grouping
folders.
- Go already provides entrypoint/private/public grouping with compiler-enforced meaning.
- The runtime ships as a single static binary.
- A layout Go developers recognize at a glance lowers the cost of every future contribution.

## Considered options

- **A. Idiomatic Go.** `cmd/` for entrypoints, `internal/` for private implementation, top-level
directories (e.g. `sdk/`) for the public surface.
- **B. `packages/` umbrella** wrapping the same packages.
- **C. Multi-module workspace** with `go.work` and per-module `go.mod`.

## Decision outcome

Chosen option: **A**, because the grouping a `packages/` folder would provide already exists here
with stronger semantics, and adding it only takes something away:

- `cmd/` = the `main` packages; `internal/` = private, with the compiler forbidding import from
outside the module; top-level dirs = the public, importable surface.
- A `packages/` wrapper prepends a dead segment to every import path
(`.../judgment-pack-runtime/packages/internal/validation`), duplicates the role `internal/`
already plays, and would still need an `internal/` beneath it to get privacy.
- `packages/` is a JavaScript/TypeScript and Python monorepo idiom for a workspace of
independently-publishable modules. This repository is one module whose layers are not
independently published — most are `internal/` — and which compile into one binary. There is no
workspace to group.

### Consequences

- Good, because the tree matches Go tooling and reader expectations, and the single-binary story is
unaffected.
- Bad, because the repository no longer visually resembles the original cross-language sketch.
- Revisit when the repository genuinely becomes multi-module or multi-language — at which point the
idiom is sibling modules plus `go.work` (option C), still **not** a `packages/` umbrella. Other
languages are expected to live in their own repositories; see
[0002-language-plurality-at-the-wire.md](0002-language-plurality-at-the-wire.md).

## More information

Current top-level layout is described in [architecture.md](../architecture.md).
67 changes: 67 additions & 0 deletions docs/adr/0002-language-plurality-at-the-wire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
status: accepted
date: 2026-07-24
deciders: Brian Jin
---

# Language plurality lives at the wire and in thin clients, not polyglot packages

## Context and problem statement

The runtime will grow additional surfaces — an MCP adapter, perhaps an HTTP API, an in-process
`sdk` — and consumers will want to reach it from languages other than Go. The question is where the
language boundary belongs: could each surface be its own language (a polyglot monorepo), and how are
non-Go consumers served without creating a second implementation that can disagree with this one?

## Decision drivers

- This is the **reference** runtime; it participates in defining conformance, so two encodings of
the validation logic that can drift apart is the worst outcome.
- The layers share one in-process, typed data model (carrier → validation → describe → result).
- The runtime ships as a single static binary.
- The specification roadmap prizes small implementation complexity and independent implementations
in other languages — but as *whole conforming validators*, not as fragments of this one.

## Considered options

- **A. One language for the runtime; plurality at the wire and in thin clients.** Keep the runtime
in Go. Serve other languages over wire protocols (MCP, HTTP) and via thin per-language client
libraries that route back to this core; whole reimplementations live in their own repositories as
independent conforming implementations.
- **B. Polyglot packages within this repository.** Core in one language, an SDK or adapter in
another, wired together across in-repo boundaries.

## Decision outcome

Chosen option: **A**. The distinction that settles it is protocol versus library:

- **MCP and HTTP are wire protocols, not libraries.** A Go server speaking them is reachable by any
client language with zero per-language work; the server's language is invisible to the caller.
- **An SDK is a library, so it is inherently per-language.** The Go `sdk` package is an in-process
API for Go embedders. A Python or TypeScript client is a *separate distribution* in its own
repository that reaches this runtime over the wire, by invoking the binary, or through C-ABI/WASM
bindings — never by reimporting logic.
- Reimplementing validation in another language is legitimate, but it is then an **independent
implementation** that must pass the published conformance corpus as one, not an "SDK."

As long as every client routes back to this single core, the no-drift guarantee holds. `api` and
`mcp`, when built, are Go adapters wired as subcommands of the one binary; they add transport, not
judgment.

### Consequences

- Good, because there is exactly one validation implementation to trust, and all-language client
access comes free from the wire surfaces.
- Good, because the single-binary distribution and the shared in-process data model are preserved.
- Bad, because an idiomatic client in another language is real, separate work rather than a folder
in this repository.
- Revisit if the single-binary or single-implementation goals are dropped, or if an in-repo
multi-language workspace becomes necessary (which would also reopen
[0001-idiomatic-go-single-module-layout.md](0001-idiomatic-go-single-module-layout.md)).

## More information

Governs the `internal/mcp` and `sdk` package seams scaffolded on the `feat/seam-prep` work. Related:
[0003-mcp-integration-and-testing-surface.md](0003-mcp-integration-and-testing-surface.md). The
independence of the CLI, `specVersion`, and machine-output versions is described in
[architecture.md](../architecture.md).
65 changes: 65 additions & 0 deletions docs/adr/0003-mcp-integration-and-testing-surface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
status: proposed
date: 2026-07-24
deciders: Brian Jin
---

# MCP is the runtime's integration and testing surface

> **Stub.** The direction and the constraints below are settled; the server itself is not built.
> Acceptance is gated on the Phase 0 review described under "Decision outcome".

## Context and problem statement

The specification and runtime need an interactive way to be exercised on real authoring tasks —
create a pack, revise it, remove parts, have an agent drive the loop — without building a bespoke
web application, accounts, or authentication. The runtime validates documents; it has no CRUD, no
evaluation, no network, and no API key of its own.

## Decision drivers

- Minimum configuration; no custom UI, auth, or hosting to build or secure.
- The runtime must stay keyless, offline, and deterministic.
- "Agent integration" should be first-class, since exercising the spec with an LLM author is a goal.

## Considered options

- **A. Expose the runtime over MCP** (a wire protocol) and let an existing MCP-capable agent client
be the interface.
- **B. Build a dedicated testing web app** with its own UI and key handling.
- **C. CLI-as-tool only** — an agent shells out to the existing binary, no new surface.

## Decision outcome

Chosen direction: **A**, reached in two phases so the premise is proven before code is written.

- **Phase 0 (proven first, no Go): option C as a warm-up.** An agent in the reference client shells
out to `judgment-pack spec validate` to pressure-test whether the runtime's diagnostics are good
enough to close an authoring loop. Review the result, *then* decide Phase 1.
- **Phase 1 (pending review): the MCP server.** Fill the `internal/mcp` seam as a `judgment-pack
mcp` subcommand over stdio, wrapping the existing engine (`validate`, `test_conformance`,
`get_schema`, `describe_runtime`) and evaluating nothing.

Settled constraints, regardless of phase:

- **Transport:** stdio — no ports, no auth.
- **Keys:** the API key lives in the client, never in the runtime.
- **Reference client:** Cline (open-source, in-IDE, edits files and speaks MCP).
- **Examples:** read from a `judgment-pack-spec` checkout; the runtime does not vendor example
packs, because the specification owns them.

Deferred: the MCP Go library choice (official `go-sdk` vs. `mark3labs/mcp-go`) — decided at
implementation time, not now.

### Consequences

- Good, because there is no UI, auth, or hosting to build, and BYO-key falls out of the client.
- Good, because it realizes [0002](0002-language-plurality-at-the-wire.md): all-language access
from one Go wire surface.
- Bad, because testing depends on a third-party client's behavior and setup.
- Revisit / promote to `accepted` after the Phase 0 review decides Phase 1.

## More information

Follows from [0002-language-plurality-at-the-wire.md](0002-language-plurality-at-the-wire.md).
Target package: `internal/mcp`.
39 changes: 39 additions & 0 deletions docs/adr/0004-defer-http-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
status: proposed
date: 2026-07-24
deciders: Brian Jin
---

# Defer the HTTP API until validation-as-a-service is needed

> **Stub.** Records a deferral, not a design. Reopen with a full ADR when the need below is concrete.

## Context and problem statement

The early structure sketch included an `api/` surface (routes, models, middleware, server). The
runtime is a stateless validator whose binary plus versioned JSON output already serves local use,
and whose agent-integration need is covered by MCP
([0003](0003-mcp-integration-and-testing-surface.md)).

## Decision drivers

- Every surface built now is surface to secure, document, and keep stable.
- No present use case requires a long-running server.

## Considered options

- **A. Defer** the HTTP API until a concrete validation-as-a-service need appears.
- **B. Build it now** alongside MCP for completeness.

## Decision outcome

Chosen option: **A**. Nothing today needs remote, batch, multi-tenant, or spawn-free-throughput
validation; the CLI and MCP cover the current cases. When a real need appears, the API is built as a
Go adapter (a `serve` subcommand over the same core, per
[0002](0002-language-plurality-at-the-wire.md)), and this ADR is superseded by a full one.

### Consequences

- Good, because the surface stays small and there is no server to secure yet.
- Bad, because remote/batch consumers have no first-class option until it is built.
- Revisit when a concrete validation-as-a-service requirement exists.
35 changes: 35 additions & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Architecture decision records

This directory records the cross-cutting implementation decisions behind `judgment-pack-runtime` —
the ones no single commit or pull request captures well, such as the repository layout or the
adapter strategy.

These are **ADRs**, not **RFCs**. The split follows the repository boundary:

- **This runtime → ADRs.** Implementation decisions for one nonnormative consumer of JPS, recorded
after they are effectively made. Lightweight; no comment-before-commit process.
- **The specification → RFCs.** Normative, public, cross-implementation proposals open for comment
before a decision, in `judgment-pack-spec` under `rfcs/` (see its `rfcs/0000-rfc-process`).

A runtime decision graduates into a spec RFC only if it ever needs agreement across independent
implementations (for example, a standard MCP surface every JPS validator should expose).

[architecture.md](../architecture.md) describes the system **as it is now**; ADRs record **why and
when** it became that way. architecture.md links out to the relevant ADR rather than restating it.

## Adding a record

1. Copy [template.md](template.md) to `NNNN-short-title.md` using the next free number.
2. Write it, set `status: proposed`, and open it in the pull request that makes the decision.
3. On merge, set `status: accepted`. ADRs are immutable once accepted: a later change is a **new**
ADR that supersedes the old one (which is then marked `superseded by NNNN`), never an edit.

## Index

| # | Decision | Status |
| -------------------------------------------------------- | ------------------------------------------------------------------------------- | -------- |
| [0000](0000-record-decisions-with-madr.md) | Record runtime decisions with MADR-format ADRs | accepted |
| [0001](0001-idiomatic-go-single-module-layout.md) | Idiomatic Go single-module layout; no `packages/` umbrella | accepted |
| [0002](0002-language-plurality-at-the-wire.md) | Language plurality lives at the wire and in thin clients, not polyglot packages | accepted |
| [0003](0003-mcp-integration-and-testing-surface.md) | MCP is the runtime's integration and testing surface | proposed |
| [0004](0004-defer-http-api.md) | Defer the HTTP API until validation-as-a-service is needed | proposed |
35 changes: 35 additions & 0 deletions docs/adr/template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
status: proposed # proposed | accepted | deprecated | superseded by NNNN
date: YYYY-MM-DD
deciders:
---

# {short title naming the problem and the chosen solution}

## Context and problem statement

{Two or three sentences. What forces the decision? What breaks or stays ambiguous without it?}

## Decision drivers

- {driver, e.g. a constraint or quality this must satisfy}
- {driver}

## Considered options

- {option 1}
- {option 2}

## Decision outcome

Chosen option: "{option}", because {the one or two reasons that settle it}.

### Consequences

- Good, because {benefit}.
- Bad, because {cost or new constraint accepted}.
- Revisit when {the condition that would reopen this decision}.

## More information

{Links to architecture.md sections, related ADRs, code, or the pull request. Optional.}
8 changes: 8 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,11 @@ and nearby-version substitution are forbidden.
Development builds report version `0.0.0-dev`. GoReleaser injects the exact tag version into
official binaries through a Go linker flag; source builds without that release metadata remain
development builds.

## Decisions

This document describes the runtime as it is. The cross-cutting decisions behind it -- why the layout
is idiomatic Go rather than a `packages/` monorepo, why language plurality lives at the wire, why MCP
is the integration surface -- are recorded as architecture decision records in
[`docs/adr/`](adr/README.md). Implementation decisions are ADRs; normative, cross-implementation
proposals belong to the specification's RFC process instead.