{
  "markdown": "# judgment-pack\n\n> **Status: pre-1.0**\n>\n> Tagged binaries are official only when they appear on this repository's GitHub Releases page\n> with `checksums.txt`. Development checkouts may temporarily embed unreleased JPS snapshots, but\n> the release workflow rejects them.\n\n`judgment-pack` is the vendor-neutral **reference runtime** for the Judgment Pack Specification\n(JPS). It is a reference implementation, not the only valid one: the normative specification,\nschemas, and conformance corpus are owned by the separate\n[`judgment-pack-spec`](https://github.com/Judgment-Pack/judgment-pack-spec) repository, and an\nindependent implementation is judged against the complete normative requirements of the conformance\nclass it claims — not against this runtime. Results from the corpus published for the exact version\nit names are required evidence for such a claim and are not exhaustive evidence of it (§3.4.1).\n\nThe runtime **validates documents**. It does not fetch a source, authorize an action, or establish\ntruth, organizational authority, safety, or operational fitness. It bundles two specification\nversions, `0.1.0-draft` and `0.2.0-draft`, and validates a document against the exact version that\ndocument declares; `0.1.0-draft` defines carrier, structural, and semantic document conformance\nonly, and `0.2.0-draft` changes no part of the document format.\n\nOne shared evaluator applies the specification's §§7–8 resolution model per\n[ADR-0007](docs/adr/0007-experimental-evaluator.md), and eight surfaces reach it: `jpack experimental\nevaluate` and `evaluate-corpus`, the project walk `jpack packs test`, the graph verbs\n`jpack experimental graph evaluate` and `graph test`, and the `experimental_evaluate`,\n`experimental_test_packs` and `experimental_test_graphs` MCP tools. The `experimental` namespace is a **stability**\nstatement: such a surface may change or be removed without compatibility promise. It is not a statement\nabout conformance. That evaluator implements the evaluator conformance class Core `0.2.0-draft` adds —\nthe §8.2 input preflight, the §8.3 portable disposition with its RFC 8785 byte agreement, the §8.4\nerror classes and their fixed precedence, and the §10 limits — per\n[ADR-0010](docs/adr/0010-evaluator-aligned-to-core-0.2.0-draft.md) and\n[ADR-0011](docs/adr/0011-first-evaluator-conformance-claim.md). Only a pack declaring `specVersion`\n`0.2.0-draft` is evaluated: §11 makes the declared value exact and requires an unedited `0.1.0-draft`\npack to be re-declared — one edit, the `specVersion` string, and nothing else in the document — before\nan implementation claiming this draft evaluates it, so any other version is refused as\n`pack-not-conformant` in the `preflight` phase.\n\n**This runtime's conformance claim is stated, in full and only, in\n[CONFORMANCE.md](CONFORMANCE.md).** JPS §3.4.1 fixes the entire form such a claim may take — the class,\none exact `specVersion`, the corpus version, the results obtained, and in the claim's own words that\nevery row of that corpus version passed — so this README states no part of it and neither does any\nother surface: a partial restatement would be the partial claim §3.4.1 forbids. Read that file for the\nclaim, its version scope, its evidence, and everything it does not assert, which includes anything at\nall about a pack, its facts, or the wisdom of acting on a disposition (§3.5).\n`jpack experimental evaluate-corpus` runs the bundled evaluation corpus and reports its rows —\nthe evidence §3.4.1 requires of a claim of this class, and explicitly not exhaustive evidence of one;\nsee [CONFORMANCE.md](CONFORMANCE.md).\n\nThat command carries one further opt-in, `--rfc0008-quantifiers`, which is a **draft-RFC\nprototype** per [ADR-0009](docs/adr/0009-draft-rfc-quantifier-prototype.md): it admits the\ncollection quantifiers `exists`, `every`, and `uniform` proposed by the specification's RFC 0008\n(Draft). Those operators belong to no published JPS version. A pack using one is **not valid** under\nJPS `0.1.0-draft`, `spec validate` rejects it, the evaluator without the flag refuses it, and every\nsuccessful evaluation payload produced under the flag says so in band through a `draftPrototype`\nmember — a refusal is an operational error and carries none. The flag is CLI only; the MCP tool does\nnot expose it.\n\nThe command binary is `jpack`. The project, repository, and release archives keep the\n`judgment-pack` name; the executable they carry is `jpack`.\n\n## Implemented commands\n\n```text\njpack version\njpack spec validate <pack-or->\njpack spec test-conformance [suite]\njpack spec schema <spec-version>\njpack spec examples [name]\njpack packs list        (jpack.json project convention; ADR-0012, not part of the spec)\njpack packs validate [--id X]\njpack packs test [--id X]   (EXPERIMENTAL SURFACE; claim: CONFORMANCE.md)\njpack packs suggest [--id X] [--base ROW] [--write F|-] [--max N] [--include-hugs]   (candidate row INPUTS, never rows; ADR-0024)\njpack packs lock        (declare the current documents as the project's reviewed set; ADR-0019)\njpack packs verify      (check the project against that reviewed set)\njpack packs lint [--producers M]   (every consulted pointer has a producer; ADR-0022)\njpack packs schema\njpack mcp\njpack experimental evaluate <pack-or->   (EXPERIMENTAL SURFACE; claim: CONFORMANCE.md)\njpack experimental evaluate --pack-id X   (EXPERIMENTAL SURFACE; resolves one decision id through jpack.json)\njpack experimental evaluate <pack-or-> --rfc0008-quantifiers   (DRAFT-RFC PROTOTYPE; not an input the class defines)\njpack experimental evaluate-corpus   (EXPERIMENTAL SURFACE; corpus results, the evidence §3.4.1 requires)\njpack experimental graph list   (EXPERIMENTAL: the configured graphs, resolved; ADR-0029)\njpack experimental graph validate <graph-or->   (EXPERIMENTAL composition prototype; spec RFC 0002, Draft; ADR-0015)\njpack experimental graph evaluate <graph-or-> [--inputs <file-or->]   (EXPERIMENTAL SURFACE; claim: CONFORMANCE.md)\njpack experimental graph explain <graph-or->   (the evaluation plan; nothing is evaluated)\njpack experimental graph test <graph-or-> --rows <file-or->   (EXPERIMENTAL SURFACE; claim: CONFORMANCE.md)\njpack experimental graph schema\n```\n\nThe pack argument and `--pack-id` are mutually exclusive: one pack, one source, and supplying both\nor neither is an invocation error rather than a precedence rule. `--pack-id` honors `--config` and\n`JPACK_CONFIG` like every other command that reads a configuration.\n\nThe namespace is `jpack spec`, not `jpack jps`. JPS remains the name of the\nspecification and the prefix of its provisional diagnostic codes.\n\nThe `mcp` command serves the same offline operations to a Model Context Protocol client over stdio,\nso an agent can validate documents as a tool call; see\n[docs/mcp-clients.md](docs/mcp-clients.md) for per-client setup and\n[docs/agent-testing.md](docs/agent-testing.md) for the agent-driven testing protocol.\n\n## Install a tagged release\n\nDownload the archive for your operating system and architecture from\n[GitHub Releases](https://github.com/Judgment-Pack/judgment-pack-runtime/releases). Each archive\nincludes the `jpack` binary, README, the `CONFORMANCE.md` claim the source it was\nbuilt from carries, Apache-2.0 license, attribution notice, and third-party notices.\n\nAsset names follow this pattern:\n\n```text\njudgment-pack_<version>_<os>_<arch>.tar.gz\njudgment-pack_<version>_windows_<arch>.zip\n```\n\nFor example, release `v0.1.0` uses `judgment-pack_0.1.0_linux_amd64.tar.gz`. Linux and macOS users\ncan extract an archive and install the binary into a user-owned directory already on `PATH`:\n\n```bash\ntar -xzf judgment-pack_0.1.0_linux_amd64.tar.gz\ninstall -m 0755 jpack \"$HOME/.local/bin/jpack\"\njpack version\n```\n\nOn Windows, expand the `.zip`, move `jpack.exe` into a directory on your user `PATH`, and\nrun:\n\n```powershell\njpack version\n```\n\nVerify the archive before extracting it. On Linux:\n\n```bash\ngrep ' judgment-pack_0.1.0_linux_amd64.tar.gz$' checksums.txt | sha256sum --check\n```\n\nOn macOS:\n\n```bash\ngrep ' judgment-pack_0.1.0_darwin_arm64.tar.gz$' checksums.txt | shasum -a 256 --check\n```\n\nOn Windows PowerShell:\n\n```powershell\n(Get-FileHash .\\judgment-pack_0.1.0_windows_amd64.zip -Algorithm SHA256).Hash\nSelect-String -Path .\\checksums.txt -Pattern ' judgment-pack_0.1.0_windows_amd64.zip$'\n```\n\nThe two Windows hashes must match. Release archives also carry GitHub build-provenance\nattestations, which a current GitHub CLI can verify:\n\n```bash\ngh attestation verify judgment-pack_0.1.0_linux_amd64.tar.gz --repo Judgment-Pack/judgment-pack-runtime\n```\n\nPackages for Homebrew, Scoop, `apt`, and `go install` are not published yet. In particular, source\ninstallation with `go install` does not receive the release linker metadata, so use a release\narchive when you need an accurately reported runtime version.\n\n## Build and try it locally\n\nGo 1.24 or newer is required to build from source: `internal/fssecure` binds every project file\nread to a directory handle through `os.Root`, which is a Go 1.24 standard-library type.\n\nIf an older WSL setup has persisted `GO111MODULE=off`, clear it once with\n`go env -u GO111MODULE`. The commands below explicitly enable module mode as a compatibility\nmeasure.\n\n```bash\nenv GO111MODULE=on CGO_ENABLED=0 go build -trimpath -o ./bin/jpack ./cmd/jpack\n./bin/jpack --help\n./bin/jpack version\n```\n\nFrom a sibling checkout of `judgment-pack-spec`, validate a synthetic example:\n\n```bash\n./bin/jpack spec validate ../judgment-pack-spec/examples/minimal-expense-approval.json\n./bin/jpack spec validate --format json ../judgment-pack-spec/examples/minimal-expense-approval.json\n```\n\nStandard input is accepted explicitly with `-`:\n\n```bash\n./bin/jpack spec validate --format json - < pack.json\n```\n\nRun a bundled document-conformance corpus — `0.1.0-draft` by default, or an exact bundled version:\n\n```bash\n./bin/jpack spec test-conformance\n./bin/jpack spec test-conformance --format json\n./bin/jpack spec test-conformance --spec-version 0.2.0-draft\n```\n\nRun the bundled **evaluation** corpus of JPS `0.2.0-draft` through the experimental evaluator. Every\nrow is compared by disposition equality as §8.3 defines it — both the row's expectation and the\nproduced disposition go through the same RFC 8785 canonicalizer, so a set's stored order is not a\ndifference — or by its expected §8.4 error class and phase.\nThis reports results: the required, non-exhaustive evidence for the claim in\n[CONFORMANCE.md](CONFORMANCE.md), and not that claim. A mismatching row decides nothing by itself,\nbecause §3.4 makes a divergence as likely to be a defect in the row as in this implementation, and only\na project-issued erratum can excuse a row:\n\n```bash\n./bin/jpack experimental evaluate-corpus\n./bin/jpack experimental evaluate-corpus --format json\n./bin/jpack experimental evaluate-corpus --spec-version 0.2.0-draft\n```\n\nInspect or copy the bundled schema without network access:\n\n```bash\n./bin/jpack spec schema 0.1.0-draft\n./bin/jpack spec schema 0.2.0-draft\n./bin/jpack spec schema 0.1.0-draft --write schema.json\n./bin/jpack spec schema 0.1.0-draft --write -\n```\n\nThe schema command refuses to overwrite an existing file. It is a different write from the audit\ntrail below, which appends to a file the project's own configuration named and never replaces one.\n\n## Validation behavior\n\nValidation short-circuits in this order:\n\n1. strict UTF-8 JSON carrier parsing, including duplicate-member rejection;\n2. exact `specVersion` dispatch;\n3. Draft 2020-12 structural validation with URI, date, and date-time assertions;\n4. normative semantic reference and extension-declaration checks; and\n5. required-extension capability negotiation.\n\nAn unknown but syntactically usable `specVersion` is `unsupported`, not `invalid`. The conformance\nrunner deliberately pins each case to the suite's declared specification version, so a fixture that\nviolates that pinned schema remains a structural negative case.\n\n`--through carrier` and `--through structural` produce explicitly partial results. They never print\nan unqualified “valid document” message.\n\nThe public MVP supports no JPS extensions. A structurally and semantically conforming document that\nrequires an extension is therefore reported as `unsupported`. Extension code is never discovered,\ndownloaded, installed, or executed during validation.\n\n## Experimental evaluation behavior\n\nThe evaluator implements the evaluator conformance class of JPS Core `0.2.0-draft`. The conformance\nclaim for it is stated, in full and only, in [CONFORMANCE.md](CONFORMANCE.md) — with its evidence, its\nversion scope, and everything it does not assert — and this section states no part of it: this section\nis the behavior.\n\n**Only a pack declaring `specVersion` `0.2.0-draft` is evaluated.** The §8.2 preflight, the §8.3\ndisposition shape, and the §8.4 error classes are that version's, and §11 makes a declared version\nexact: \"an unedited `0.1.0-draft` pack is not structurally conforming to `0.2.0-draft` and must be\nre-declared before an implementation claiming this draft evaluates it.\" So a pack declaring any other\nversion — `0.1.0-draft` included — is refused as `pack-not-conformant` in the `preflight` phase\n(`JPS-EVALUATION-PACK-SPEC-VERSION`), and the message cites that rule and states the remedy: **one\nedit, the `specVersion` string**, and nothing else in the document, since §11 says `0.2.0-draft` changes\nno part of the document format. There is no second, unclaimed legacy path. Document validation is\nuntouched — `spec validate` still validates a `0.1.0-draft` pack against its own published schema, and\ndocument conformance needs no evaluator (§3.4).\n\nEvery evaluation payload still names both versions — `specVersion` is the pack's own, and\n`evaluatorSpecVersion` is the contract's — because they are two different facts and a consumer should\nread the applied contract rather than infer it. A refusal names the contract too, on\n`evaluationError.evaluatorSpecVersion`. Payloads also carry a `conformanceClaimReference` member whose\nvalue is `CONFORMANCE.md`: a locator, not a claim.\n\n**Inputs are admitted before anything is resolved (§8.2).** They are validated in one order — the\npack, then the facts document, then the evidence-availability document, then the pack's\n`metadata.requiredExtensions` against the caller's `--supported-extension` set — and that validation\nfinishes before §8 step 1 runs. An omitted `--evidence` document is the implicit empty object, which\nmakes every declared requirement `unknown`; that is the only form its absence takes, and it is not an\nerror. An evidence input that is not a JSON object, names an undeclared requirement, or carries a\nvalue outside `present`, `absent`, and `unknown` is refused.\n\n**A produced result is the portable disposition (§8.3).** Under `--format json` without `--pretty`\nthe `disposition` member is written in its RFC 8785 canonical form: members ordered by name, both sets\nsorted and duplicate-free, absent members omitted rather than serialized as `null`, and no whitespace.\n`--pretty` indents the whole payload and that indentation reaches inside this member too, so the member\norder and both sets stay canonical but those exact bytes are not present. §8.3 requires\ncanonicalization \"where a byte comparison is required\", so a byte comparison against another\nimplementation must recanonicalize either side it did not itself produce; under `--pretty` it must.\nThe pack's configured escalation target is reported beside the disposition, in `handoffTarget`, never\ninside it. Human output is unchanged prose.\n\n**A refusal is an evaluation error, and never a disposition (§8.4).** Every evaluation this runtime\nrefuses reports exactly one class in band — `evaluationError.class`, with `evaluationError.phase` and\n`evaluationError.evaluatorSpecVersion` — and no disposition at all, not even a partial one. That\nincludes every §8.2 preflight condition on every surface: a document above the byte limit and an empty\nsupplied evidence document are classed and ordered like any other, not refused ahead of the preflight.\nThe finer `JPS-*` code stays beside the class as its detail. An invocation that never became an\nevaluation — a missing flag, an input this runtime could not read as a bounded regular file — is an\nordinary operational error and carries no class, because §8.4 classes evaluation conditions and leaves\ntransport undefined. The four classes are evaluated in Core's fixed order, which is the preflight order\nabove:\n\n| Class | Reached when |\n| --- | --- |\n| `pack-not-conformant` | the pack input is not a semantically conforming document, at any layer |\n| `malformed-input` | an input failed the preflight: unusable JSON, a non-object or invalid evidence document, an undeclared evidence key, or a document limit reached while admitting an input |\n| `unsupported-required-extension` | the pack requires a capability this caller does not support |\n| `resource-exhaustion` | a documented §10 limit was reached while evaluating an admitted input: this runtime's evaluation-work limit, on the ordinary Core path as well as under `--rfc0008-quantifiers` |\n\nThe phase split is the one §10 draws: a limit reached while *admitting* an input is\n`malformed-input`, because the input was refused rather than partly processed, and\n`resource-exhaustion` is reserved for a limit reached while *evaluating* an input already admitted.\nThis runtime's admission limits are the carrier limits listed under [security\ndefaults](#security-defaults). The 250,000-node cap is one of them, and its §10 category is stated\nrather than left implicit: it is a budget over the whole parsed document, so it is a document-size\nlimit like bytes and depth, reached while admitting an input, and it is reported as\n`malformed-input` in the `preflight` phase. It is *also* this evaluator's collection-size bound, for the\nreason the next section gives: a collection of n members is n+1 parsed nodes, so a document-size budget\nover nodes bounds every collection inside it.\n\n### The two §10 limits of the claimed class\n\n§10 requires an implementation claiming this class to define and document at least its collection-size\nand evaluation-work limits, and makes reaching one of them during an evaluation `resource-exhaustion`\nrather than a disposition. Both are defined here and enforced in\n[`internal/evaluation/limits.go`](internal/evaluation/limits.go):\n\n- **Evaluation-work limit: 20,971,520 work units per evaluation.** A unit is one visited condition\n  node, one §8 iteration over an authored evidence requirement, exception, or rule, one step of a\n  pointer resolution, or one byte of a path, an object member name, or a scalar token that a comparison\n  has to read. The charge for each condition tree is complete before any predicate in it runs and §8's\n  own iteration is charged before step 1, so the total does not depend on evaluation order and an\n  exhausted limit never truncates a disposition — it produces `resource-exhaustion` in the `evaluation`\n  phase with no disposition at all.\n\n  The number is **derived in code** from the carrier's byte cap rather than chosen: it is exactly twice\n  10 MiB, the per-document byte cap every admitted input passes, and `limits.go` computes it from that\n  constant so the two cannot drift. That ratio is an **arithmetic fact about two numbers**, and nothing\n  more: **it gives no guarantee about any whole evaluation.** Three documents may be admitted rather\n  than two — the pack, the facts document, and an optional evidence-availability document — each under\n  the same cap; and a unit is charged per *use* rather than once per admitted byte, since the bytes of a\n  pointer and of a selected value are charged again every time a condition reads them, with §8's fixed\n  per-node and per-iteration charges on top. An earlier version of this section inferred from the ratio\n  that \"one full read of every admitted byte always fits\" and that a single maximal cross-document\n  comparison therefore sits exactly at the boundary; **both are withdrawn** — the premise bounds nothing\n  under that charge model, and neither was exercised against an admitted input through the accounting\n  path (ADR-0011 records the correction). Amplification is what the limit refuses in practice — the same\n  large selected value re-read once per `in` candidate or once per condition — and this runtime does not\n  claim that only amplification is refused. Against a 100 KB facts document the limit still admits about\n  two hundred whole-document comparisons, and every row of the bundled evaluation corpus charges under\n  1,000 units: measurements of those inputs, not bounds on inputs no row contains.\n  Callers may configure a lower limit per evaluation; the draft-RFC prototype has its own, smaller\n  budget of 100,000 units (ADR-0009).\n- **Collection-size limit: 250,000 members** — the 250,000-node carrier cap above, stated as the §10\n  limit it is. Every input is admitted under that cap, so no admitted document holds a larger\n  collection, and every collection this evaluator traverses comes from an admitted document: Core\n  constructs none of its own and has no operator that iterates one. Because the bound is enforced while\n  *admitting* an input, reaching it is `malformed-input` in the `preflight` phase — §2.1 refuses such a\n  document whole rather than processing part of it — which is stricter than an evaluation-phase check of\n  the same bound, not weaker. That is why this runtime documents the determination instead of adding a\n  second mechanism that could only report what the preflight already refuses.\n\nLimits are not portable: two implementations may set different ones, and an input above either is\noutside the portable claim (§10). The evaluation corpus therefore keeps every case well inside any\nplausible limit rather than probing one.\n\n## The `jpack.json` project convention\n\nA project that owns several packs needs a name for each one that works the same from a shell, from\nCI, and from an agent's tool call. `jpack.json` is that index — and it is a **convention of this\nruntime, not part of the Judgment Pack Specification** ([ADR-0012](docs/adr/0012-jpack-project-convention.md)).\nNo other implementation is obliged to understand it, and a project that never writes one loses\nnothing: every command still takes a pack by path, and every MCP tool still takes one as text.\n\n```json\n{\n  \"configVersion\": \"1\",\n  \"packs\": {\n    \"expense-approval\": {\n      \"path\": \"packs/expense-approval-1.2.0.pack.json\",\n      \"matrix\": \"packs/expense-approval.matrix.json\",\n      \"description\": \"May this expense be reimbursed without a manager's sign-off?\",\n      \"expectedVersion\": \"1.2.0\",\n      \"facts\": { \"/expense/amountUsd\": { \"source\": \"Snowflake FINANCE.EXPENSES\", \"hint\": \"amount_usd as a decimal string\" } },\n      \"evidence\": { \"itemised-receipt\": { \"source\": \"SharePoint /Finance/Receipts\" } }\n    }\n  }\n}\n```\n\nThe file is selected by `--config`, then `JPACK_CONFIG`, then `./jpack.json`. Its schema is closed\nand printable with `jpack packs schema`: every member it does not name is rejected.\n`configVersion` is a single integer as a string, on the `outputVersion` precedent rather than\nsemantic versioning; `\"1\"` is the shape without graphs, `\"2\"` the shape with them (ADR-0017), `\"3\"`\nthe shape that may also ask for an audit trail (ADR-0018), and this runtime reads all three.\n\nThere is **no templating, no target or environment blocks, and no selection**. A templated pack was\nnever the pack anyone reviewed; environments are one file per environment by convention\n(`jpack.staging.json`, `--config`); and choosing which decision to ask is the application's, because\napplicability is not authorization. Approval is your pull request — there is no approval state in\nthe file.\n\n**A pack's identity is stated once, in the pack document's `id` and `version` members.** Everything\nelse that names a version is a validated reference to that statement, never a second one:\n`expectedVersion` is a pin `packs validate` compares; the optional `<decision-id>-<semver>.pack.json`\nfilename is cross-checked when followed and skipped when not; and the `packId` and `packVersion`\nmembers on every evaluation payload are echoes read off the document that was evaluated. Any of the\nthree may disagree with the document, and a disagreement is an error — none of them can win one.\n\nThe `facts` and `evidence` hints are non-normative guidance for an agent gathering inputs: they say,\nin your words, where a value is held. **The runtime never reads a source** — it holds no credential\nand opens no network connection — and every file the convention names is read through a reader bound\nto a handle held open on the configuration's own directory. Containment is two checks and neither\nsubstitutes for the other: a lexical one refuses an absolute or escaping path before anything is\nread, and resolving against the held directory at read time refuses a path that reaches outside\nthrough a symlinked component, which a lexical check cannot see. The second is a handle rather than a\npathname so that containment holds *through* the open: a path that is checked and then opened by\npathname can have an intermediate directory swapped for an outward symlink in between, and resolving\nagainst the handle makes that impossible rather than unlikely. A final component that is a symlink is\nrefused whatever it points at, and only a regular file is read. Every surface that reaches a pack\nthrough the configuration takes this one reader, `--pack-id` included, and none of them is handed a\npathname to open for itself.\n\nThe one thing this file can ask the runtime to **write** is a record of what it evaluated\n([ADR-0018](docs/adr/0018-opt-in-evaluation-audit-trail.md)). Under `configVersion \"3\"`, an\n`audit` member names a directory relative to the configuration — `\"audit\": { \"dir\": \"audit\" }` —\nand each completed evaluation of `experimental evaluate`, `experimental graph evaluate`, and the\nMCP `experimental_evaluate` tool — unless it was declared a rehearsal (ADR-0028) — then appends one JSON line to `evaluations.jsonl` in it: the\npack's id, version, `specVersion` and the digest of its exact bytes, the facts and evidence\ndocuments as evaluated, and the disposition in its canonical form. Test runs never record —\n`packs test`, `experimental graph test`, and `experimental evaluate-corpus` are checks on packs,\nnot decisions anyone took — a refused evaluation records nothing, because it has no disposition to\nrecord, and a record that cannot be written refuses the run (exit 4) rather than reporting a\ndisposition nothing kept. The write goes through the same held handle every read does, into the\nproject's own tree and nowhere else. Declare no `audit` member and nothing is written at all.\n\n### The reviewed set\n\n`jpack packs lock` writes `jpack.lock.json` beside the configuration: the digest of the\nconfiguration's exact bytes and of every pack and graph it declares\n([ADR-0019](docs/adr/0019-reviewed-set-lock.md)). Running it **is** the amendment — it is how a\nproject says, in a file a reviewer diffs, that the law changed on purpose — and it approves nothing:\nyour pull request is still the approval. The file is generated and deterministic, so re-running it\nover an unchanged tree leaves no diff. `jpack packs verify` names every difference from it:\n`config-drift`, `document-drift`, `document-missing`, `lock-entry-missing`,\n`locked-but-undeclared`, and `path-mismatch`.\n\nIts **presence** is the opt-in, and it is found by convention rather than declared: no\n`configVersion` moves, the schema does not change, and a project with no lock file behaves exactly\nas it did. With one, the deciding surfaces — `experimental evaluate`, `experimental graph evaluate`,\nand the MCP `experimental_evaluate` tool — hold the law they are about to apply to it, declared\nrehearsals excepted (ADR-0028), and refuse a\nmismatch (`JPS-LOCK-VERIFY`, exit 1) with the two honest ways forward: declare the amendment, or\nrestore the reviewed bytes. `packs test`, `experimental graph test`, and `experimental\nevaluate-corpus` consult it never: the author's loop is free and decisions are classified. A pack\nnamed by path, or passed as text over MCP, is a draft — evaluated, never refused for being unlocked,\nand recorded as a draft. Where an audit trail is configured, each record carries `reviewed`: `true`\nwhen every document applied was declared and matched, `false` for a draft, absent when the project\ndeclares no lock.\n\n**What it is not.** It is not a wall. Anything that can edit a pack can run `packs lock` again, and\nthis runtime cannot tell that from an author amending policy on purpose — they are the same act.\nWhat the lock buys is that the amendment stops being silent.\n\nSeven commands, one CI line, and one amendment step:\n\n```bash\njpack packs list                                            # the resolved inventory\njpack packs validate && jpack packs lint && jpack packs test && jpack packs verify   # the CI gate\n```\n\n`packs lock` is deliberately not in that line. It is the amendment — you run it when the law changes\non purpose, and you commit its output with the change it pins. Running it immediately before\n`packs verify` would make the verification vacuous, which is the one way to hold this convention\nbackwards.\n\n`packs validate` reports six named checks per pack — path containment, document validation, the\n`expectedVersion` pin, the filename cross-check, the hint keys, and matrix well-formedness — each as\n`passed`, `failed`, or `skipped`. `packs test` runs each pack's instance matrix through the experimental\nevaluator and compares every row the way the bundled evaluation corpus is compared: the RFC 8785\ncanonical §8.3 disposition byte for byte, or the expected §8.4 error class and phase, plus the one\noptional per-row assertion about the escalation target §8.3 keeps *outside* the disposition\n(`expectedHandoffTarget`, which needs `matrixVersion: \"2\"`). The two carriers share the fields the\ncomparator reads, which is what makes the comparison one implementation rather than two — not a\nclaim that a row moves between them untouched. Corpus admission additionally requires its own\nmembers (`pack`, `origin`, `supportedExtensions`, `focus`, `specSection`), and its closed schema\nforbids `expectedHandoffTarget`, so lifting a project row means supplying those and removing any\ntarget assertion. Both commands exit `1` on any failure, a pack with no\nmatrix is reported *skipped* rather than passed, and a `packs test` run in which no row ran at all\nis reported *skipped* and exits `1`: a green gate over zero rows would say a project was tested when\nnothing was. `packs lint` closes the gap neither of them covers: a pack consulting a pointer no\nsource feeds raises no error anywhere — the condition is unknowable, every rule touching it\nescalates, and the system looks conservative rather than broken — so the lint holds every consulted\npointer to a producer declaration (the configuration's own hints, or an explicit `--producers`\nmanifest) and fails the build where that defect otherwise hides (ADR-0022).\n\nFrom a shell, `jpack experimental evaluate --pack-id expense-approval --facts facts.json`\nreaches the same pack by the same name. Over MCP the same inventory is `list_packs`, one document is\n`get_pack`, and `experimental_evaluate` accepts `pack_id` instead of pasted `pack` text; the graph\nconvention has the same pair, `experimental_list_graphs` and `experimental_get_graph`, beside the\nCLI's `experimental graph list` (ADR-0029). With no configuration,\n`list_packs` answers empty with an explanation of where the runtime looked, rather than failing.\n\n[docs/building-with-packs.md](docs/building-with-packs.md) is the builder's guide: the packs-as-code\nlifecycle, the three-owner model (the application selects, the agent gathers and never invents, the\npack judges), hints in practice, and the data-sufficiency-as-another-pack pattern.\n\n## Process contract\n\n| Exit | Meaning |\n| ---: | --- |\n| `0` | Command succeeded; validation passed the reported scope. |\n| `1` | Document invalid, an expectation mismatched, or a check the command makes failed — a conformance mismatch, a difference from the reviewed set, or law that left it. |\n| `2` | Exact JPS version or required extension unsupported. |\n| `3` | Invocation or suite configuration invalid. |\n| `4` | Input/output or resource-limit failure. |\n| `5` | Internal runtime or bundled-artifact failure. |\n\n`--format json` writes exactly one versioned JSON object plus a newline to standard output for\nnormal valid, invalid, unsupported, mismatch, and handled operational results. It never mixes human\nprose or ANSI controls into that stream. Results include `diagnosticsTruncated` so automation can\ndetect a reached output limit. `--quiet` is available only with human output.\n\nHuman document results, including `invalid` and `unsupported`, use standard output. Invocation,\ninput/output, resource, and internal failures use standard error.\n\n## Security defaults\n\nThe current implementation:\n\n- performs no runtime network requests and never dereferences document locators;\n- accepts one explicitly selected regular file or standard input, not URLs or special files;\n- writes only where it was told to, in three ways and no others: a copy of a bundled schema or\n  example at the target an operator names with `--write`, which refuses to overwrite an existing\n  file; one appended record per completed non-rehearsal evaluation (ADR-0028) when a project's `jpack.json` declares an\n  `audit` directory ([ADR-0018](docs/adr/0018-opt-in-evaluation-audit-trail.md)), into that\n  directory, through the handle held open on the configuration's own directory — a record is not a\n  diagnostic, and it carries the documents the project asked to have recorded; and the reviewed-set\n  lock `jpack packs lock` generates beside the configuration when an operator runs that command\n  ([ADR-0019](docs/adr/0019-reviewed-set-lock.md)), replaced in place through the same handle and\n  refused outright if it would land on a document the configuration declares;\n- rejects duplicate decoded member names at every depth, invalid UTF-8, trailing JSON, and\n  non-JSON constants;\n- caps a document at 10 MiB, nesting at 128, parsed nodes at 250,000, and diagnostics at 100;\n- caps local conformance suites at 10,000 cases and 100 MiB total;\n- caps diagnostics retained across one conformance result at 1,000;\n- validates suite metadata before resolving fixtures and rejects traversal and symlink paths;\n- treats extension values as inert data; and\n- emits sanitized, value-free human diagnostics.\n\nSee [SECURITY.md](SECURITY.md) for reporting and boundary details.\n\n## Artifact provenance\n\nRuntime validation uses only files embedded in the binary and verified against one lock per bundled\nspecification version:\n[`internal/artifacts/jps/0.1.0-draft/lock.json`](internal/artifacts/jps/0.1.0-draft/lock.json) and\n[`internal/artifacts/jps/0.2.0-draft/lock.json`](internal/artifacts/jps/0.2.0-draft/lock.json).\nEach lock records the source repository, exact commit/ref and source state, plus SHA-256 and size\nmetadata for every imported file — 50 for `0.1.0-draft`, and 56 for `0.2.0-draft`, whose bundle adds\nthe evaluation corpus of §3.4.1 (its manifest, that manifest's schema, and the four pack fixtures its\nrows name). The release gate checks every bundle: a development snapshot remains visibly labelled\n`unreleased-local-snapshot` and cannot pass it.\n\n> **Provenance note.** The `0.2.0-draft` bundle is pinned to the specification tag `v0.2.0-draft`.\n> The `0.1.0-draft` bundle stays pinned to an exact commit rather than to its tag: the `v0.1.0-draft`\n> tag carries schema `$id`s under a temporary repository-hosted URL, while the permanent\n> `https://judgmentpack.org/schema/` identifiers landed after it, and the specification's release\n> tooling requires the tag string to equal `specVersion`, so they cannot be published under a second\n> `0.1.0-draft` tag. A full-length commit digest is an explicitly supported immutable reference here,\n> and the release gate accepts it.\n\nArtifact bundle and conformance-corpus digests use `sha256-length-prefixed-v1`: each sorted path and\nfile body is encoded as an unsigned 64-bit big-endian byte length followed by those exact bytes.\nThe corpus digest covers `manifest.json`, `manifest.schema.json`, and every manifest fixture, so an\nequivalent bundled and local corpus produces the same value. Human and JSON conformance output both\nreport it.\n\nBefore any release, those files must be re-imported from an approved immutable specification\ncommit or tag. The lock must say `immutable-git-ref`, record a clean source worktree, and identify\nthe exact ref and commit. Mutable `main` is never a runtime validation authority.\n\nMaintainers can create a new, initially absent snapshot directory with:\n\n```bash\nenv GO111MODULE=on go run ./tools/sync-spec-artifacts \\\n  --source ../judgment-pack-spec \\\n  --destination ./internal/artifacts/jps/<exact-version> \\\n  --allow-dirty\n```\n\n`--allow-dirty` is deliberately required for an unreleased snapshot. A release candidate instead\nuses `--source-ref <exact-commit-or-tag>`; that mode verifies the official repository origin, a\nclean worktree, and that the ref resolves to checked-out `HEAD`. Artifact updates are reviewed\nsource changes; the runtime never runs this tool.\n\n## Public and commercial boundaries\n\nThis repository is intended to remain a self-contained Apache-2.0 public core. It must build,\ninstall, validate, and run its conformance tests without private repositories, credentials,\nservices, package indexes, or feature flags.\n\nCommercial capabilities should live in separate private repositories; this public repository must\nnever depend on them. The current supported integration boundary is the `jpack` executable\nand its versioned JSON output. Go packages are intentionally `internal`, and there is not yet a\nstable in-process SDK or plugin API. Before commercial commands are composed into one binary, the\npublic project must define and version that contract deliberately. Future commands should use\ndistinct namespaces such as `jpack cloud` or `jpack org`; they must not override\n`jpack spec` conformance semantics or auto-load during validation.\n\nThe normative specification, schemas, and public corpus remain in the separate\n[`judgment-pack-spec`](https://github.com/Judgment-Pack/judgment-pack-spec) repository.\n\n## Development\n\n```bash\nenv GO111MODULE=on go fmt ./...\nenv GO111MODULE=on go vet ./...\nenv GO111MODULE=on go test ./...\nenv GO111MODULE=on CGO_ENABLED=0 go build -trimpath ./cmd/jpack\n```\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md), [docs/architecture.md](docs/architecture.md), and the\n[maintainer release runbook](docs/releasing.md).\n\nIn VS Code, **Terminal → Run Task → judgment-pack: Build CLI** builds `bin/jpack`; the test\nand bundled conformance tasks are available from the same menu. The tasks explicitly enable Go\nmodule mode for older WSL configurations.\n\n## License\n\nApache License 2.0. See [LICENSE](LICENSE).\n",
  "bytes": 39072,
  "sha": "2795790f66d84b1b6125a171b26a35ac088dcc986e160935e24cf94f590dd968",
  "repo_slug": "judgment-pack/judgment-pack-runtime",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_judgment_pack_judgment_pack_f27c4311/readme"
}