diff --git a/docs/adr/0000-record-decisions-with-madr.md b/docs/adr/0000-record-decisions-with-madr.md new file mode 100644 index 0000000..2c89451 --- /dev/null +++ b/docs/adr/0000-record-decisions-with-madr.md @@ -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. diff --git a/docs/adr/0001-idiomatic-go-single-module-layout.md b/docs/adr/0001-idiomatic-go-single-module-layout.md new file mode 100644 index 0000000..18e4ab1 --- /dev/null +++ b/docs/adr/0001-idiomatic-go-single-module-layout.md @@ -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). diff --git a/docs/adr/0002-language-plurality-at-the-wire.md b/docs/adr/0002-language-plurality-at-the-wire.md new file mode 100644 index 0000000..63b76b5 --- /dev/null +++ b/docs/adr/0002-language-plurality-at-the-wire.md @@ -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). diff --git a/docs/adr/0003-mcp-integration-and-testing-surface.md b/docs/adr/0003-mcp-integration-and-testing-surface.md new file mode 100644 index 0000000..7e228d3 --- /dev/null +++ b/docs/adr/0003-mcp-integration-and-testing-surface.md @@ -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`. diff --git a/docs/adr/0004-defer-http-api.md b/docs/adr/0004-defer-http-api.md new file mode 100644 index 0000000..59d6d9a --- /dev/null +++ b/docs/adr/0004-defer-http-api.md @@ -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. diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..e8d1494 --- /dev/null +++ b/docs/adr/README.md @@ -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 | diff --git a/docs/adr/template.md b/docs/adr/template.md new file mode 100644 index 0000000..678a88f --- /dev/null +++ b/docs/adr/template.md @@ -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.} diff --git a/docs/architecture.md b/docs/architecture.md index fb05443..6b4139b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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.