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
195 changes: 16 additions & 179 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
name: CI

# The check list is dev.json, run by scripts/dev/check.sh (the development kit's vendored runner);
# the longer steps are scripts under scripts/checks/ with the comments they had here. Two jobs, both
# required by branch protection under their names: `Runner (<os>)` runs every step but the release
# ones on Linux and macOS, and `Release contents` runs the release steps with the exact toolchain
# release.yml names.

on:
pull_request:
push:
Expand All @@ -11,7 +17,7 @@ permissions:
contents: read

jobs:
test:
check:
name: Runner (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 15
Expand All @@ -21,170 +27,17 @@ jobs:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
# First, so that a manifest that does not parse fails before the long steps start.
- name: The manifest parses and its first step runs
run: scripts/dev/check.sh --manifest dev.json --only local-paths
- uses: actions/setup-go@v5
with:
go-version: '1.26.x'
cache: true
- name: Check formatting
run: |
go fmt ./...
git diff --exit-code
- name: Vet
run: go vet ./...
# Real-Runtime tests must run in CI, not silently skip behind an unset
# JPACK_TEST_BIN. Pin the independently versioned evaluator contract.
# A commit is pinned and not a tag, since a commit cannot come to name
# other code. The name after it is the release the commit was taken
# from when the pin was set: whoever moves one moves the other.
# v0.26.0 is the first Runtime release that chains and signs its trail,
# so TestTheRuntimesVerifierReadsTheChainOfRuns,
# TestASignedRunKeepsItsSignature and
# TestVerifyRunChecksARunTheRuntimeSigned run here and do not skip (#35).
# v0.30.0 is the first that refuses to sign with a seed whose public
# key is not JPACK_SIGNING_PUBLIC_KEY, which
# TestASeedReplacedAfterStartSignsNothing needs (#54). v0.31.0 is the
# first that takes a caller's binding (`--binding`, runtime ADR-0050),
# which the binding tests need (#55). A pin moved to a Runtime without
# `jpack audit` or `--binding` would skip them again, and the step after
# the tests fails if any of them does not pass.
- name: Check out tested Runtime
uses: actions/checkout@v4
with:
repository: Judgment-Pack/judgment-pack-runtime
ref: 09206e743cdf410d7b5ee970f1c9d09ca7c015ca # merged citation session identifier of any string (runtime #291)
path: .ci-runtime
persist-credentials: false
# The build names the pin's release, as the release build does, so a
# test can tell what the Runtime under test answers by its release
# (TestTheRuntimesVerifierReadsTheChainOfRuns). Whoever moves the pin
# moves the version with it.
- name: Build Runtime
working-directory: .ci-runtime
run: go build -trimpath -ldflags "-X github.com/Judgment-Pack/judgment-pack-runtime/internal/result.CLIVersion=0.33.0-dev.09206e7" -o "$RUNNER_TEMP/jpack" ./cmd/jpack
# Test-only old companion: its current-store refusal is part of the
# explicit manual-store preparation contract, not a release dependency.
- name: Check out ownership-era Runner
uses: actions/checkout@v4
with:
repository: Judgment-Pack/judgment-pack-runner
ref: 1cc6b29885b344716a970cd21fefeaca0a4edaeb
path: .ci-runner-old
persist-credentials: false
- name: Build ownership-era Runner
working-directory: .ci-runner-old
run: go build -buildvcs=false -trimpath -o "$RUNNER_TEMP/jpack-runner-old" ./cmd/jpack-runner
# A Runtime that signs and does not know the expected key, as every
# release from v0.26.0 to v0.29.x: Runner must name the key a replaced
# seed signed under (TestAnEarlierRuntimeSigningWithAReplacedSeedIsNamed,
# #54). It takes no binding either, as every release before v0.31.0:
# Runner must give it none, and verify-run report its record unbound
# (TestARuntimeWithoutTheBindingIsGivenNone,
# TestVerifyRunReportsARecordTheRuntimeDidNotBind, #55). Pinned by
# commit as the tested Runtime is.
- name: Check out an earlier signing Runtime
uses: actions/checkout@v4
with:
repository: Judgment-Pack/judgment-pack-runtime
ref: 7a2fcfe20cedba5f164246fc544c4920971d2c56 # v0.27.1
path: .ci-runtime-earlier
persist-credentials: false
- name: Build the earlier Runtime
working-directory: .ci-runtime-earlier
run: go build -trimpath -ldflags "-X github.com/Judgment-Pack/judgment-pack-runtime/internal/result.CLIVersion=0.27.1" -o "$RUNNER_TEMP/jpack-earlier" ./cmd/jpack
- name: Check out durable Gateway contract
uses: actions/checkout@v4
with:
repository: Judgment-Pack/judgment-pack-gateway
ref: 5b49e1e094b55fdd1deb9f613815c2beebd411f1 # v0.9.1
path: .ci-gateway
persist-credentials: false
- name: Build Gateway and MCP adapter
run: |
cd .ci-gateway/go
go build -trimpath -o "$RUNNER_TEMP/gateway" .
cd ../adapters
go build -trimpath -o "$RUNNER_TEMP/adapter-mcp" ./cmd/adapter-mcp
- name: Manual admission helper and real Runtime gate tests cannot skip
run: |
out="$(JPACK_TEST_BIN="$RUNNER_TEMP/jpack" \
JPACK_OLD_RUNNER_TEST_BIN="$RUNNER_TEMP/jpack-runner-old" \
go test -count=1 -v \
-run '^(TestManualGovernedRealRuntimeGate|TestManualGovernedActualRuntimeHasNoAuthorityFD|TestManualStorePrepareActualChildRetainsOriginalCustody|TestManualStorePreparationDurableAndNoDowngrade|TestManualStorePreparationSubstitutedStateRefuses|TestManualStoreSQLiteTargetUsesHeldDirectory|TestManualStoreLibraryNeverChangesCWD|TestManualInheritedAuthoritySocketClosesBeforeRuntimeExec|TestManualAuthorityDescriptorObserverDetectsSocket|TestManualStorePreparationPendingAndUnsupported|TestManualExternalDemandCannotBeOmitted|TestManualQueuedRunListsKeepPrivatePreimages)$' \
./internal/runner)" || { printf '%s\n' "$out"; exit 1; }
printf '%s\n' "$out"
for name in \
TestManualGovernedRealRuntimeGate \
TestManualGovernedRealRuntimeGate/success \
TestManualGovernedRealRuntimeGate/blocked \
TestManualGovernedRealRuntimeGate/lost-reply \
TestManualGovernedRealRuntimeGate/changed-facts \
TestManualGovernedRealRuntimeGate/changed-config \
TestManualGovernedRealRuntimeGate/changed-lock \
TestManualGovernedRealRuntimeGate/changed-signing \
TestManualGovernedRealRuntimeGate/changed-epoch \
TestManualGovernedRealRuntimeGate/changed-demand \
TestManualGovernedRealRuntimeGate/journal-failure \
TestManualGovernedRealRuntimeGate/start-journal-failure \
TestManualGovernedRealRuntimeGate/recovered-reserve \
TestManualStorePrepareActualChildRetainsOriginalCustody \
TestManualStorePrepareActualChildRetainsOriginalCustody/committed \
TestManualStorePrepareActualChildRetainsOriginalCustody/refused \
TestManualStorePrepareActualChildRetainsOriginalCustody/lose-reply \
TestManualStorePreparationDurableAndNoDowngrade \
TestManualStorePreparationSubstitutedStateRefuses \
TestManualStorePreparationSubstitutedStateRefuses/before-adapter \
TestManualStorePreparationSubstitutedStateRefuses/after-adapter \
TestManualStoreSQLiteTargetUsesHeldDirectory \
TestManualStoreLibraryNeverChangesCWD \
TestManualInheritedAuthoritySocketClosesBeforeRuntimeExec \
TestManualAuthorityDescriptorObserverDetectsSocket \
TestManualStorePreparationPendingAndUnsupported \
TestManualExternalDemandCannotBeOmitted \
TestManualQueuedRunListsKeepPrivatePreimages; do
printf '%s\n' "$out" | grep -q -- "^[[:space:]]*--- PASS: $name " || { echo "$name did not run and pass"; exit 1; }
done
# This observer identifies the actual Runtime through Linux /proc.
# Every other named helper/gate test is mandatory on both hosts.
if [ "$RUNNER_OS" = Linux ]; then
name=TestManualGovernedActualRuntimeHasNoAuthorityFD
printf '%s\n' "$out" | grep -q -- "^[[:space:]]*--- PASS: $name " || { echo "$name did not run and pass"; exit 1; }
fi
- name: Unit, conformance and cross-process recovery tests
run: |
JPACK_TEST_BIN="$RUNNER_TEMP/jpack" \
JPACK_EARLIER_TEST_BIN="$RUNNER_TEMP/jpack-earlier" \
JPACK_GATEWAY_TEST_BIN="$RUNNER_TEMP/gateway" \
JPACK_MCP_ADAPTER_TEST_BIN="$RUNNER_TEMP/adapter-mcp" \
JPACK_OLD_RUNNER_TEST_BIN="$RUNNER_TEMP/jpack-runner-old" \
go test -race ./...
# A skipped test passes `go test`; these must run (#35, #54, #55).
- name: Chain, signature, binding and ownership tests run under the pinned Runtimes
run: |
out="$(JPACK_TEST_BIN="$RUNNER_TEMP/jpack" \
JPACK_EARLIER_TEST_BIN="$RUNNER_TEMP/jpack-earlier" \
JPACK_GATEWAY_TEST_BIN="$RUNNER_TEMP/gateway" \
JPACK_MCP_ADAPTER_TEST_BIN="$RUNNER_TEMP/adapter-mcp" \
go test -count=1 -v \
-run '^(TestTheRuntimesVerifierReadsTheChainOfRuns|TestASignedRunKeepsItsSignature|TestVerifyRunChecksARunTheRuntimeSigned|TestASeedReplacedAfterStartSignsNothing|TestAnEarlierRuntimeSigningWithAReplacedSeedIsNamed|TestAnOperationalEvaluationBindsItsPreparation|TestABindingTheRuntimeRefusesFailsTheRun|TestARecordWithoutTheBindingPassedFailsTheRun|TestARuntimeWithoutTheBindingIsGivenNone|TestVerifyRunHoldsTheRecordsBindingToThePreparation|TestVerifyRunReportsARecordTheRuntimeDidNotBind|TestARuntimeThatDoesNotSayItsReleaseEvaluatesNothing|TestNamedDialectRealRuntimeReleaseRunOfflineAndRestart|TestARunWhoseReceiptSessionIsNoTokenIsEvaluatedExportedAndVerified|TestExecutorOwnedServiceRealRuntimeJobsAndChecks|TestExecutorOwnedRealRuntimeSurvivesOwnerDeath)$' \
./internal/runner ./cmd/jpack-runner)" || { printf '%s\n' "$out"; exit 1; }
printf '%s\n' "$out"
for name in TestTheRuntimesVerifierReadsTheChainOfRuns TestASignedRunKeepsItsSignature TestVerifyRunChecksARunTheRuntimeSigned TestASeedReplacedAfterStartSignsNothing TestAnEarlierRuntimeSigningWithAReplacedSeedIsNamed TestAnOperationalEvaluationBindsItsPreparation TestABindingTheRuntimeRefusesFailsTheRun TestARecordWithoutTheBindingPassedFailsTheRun TestARuntimeWithoutTheBindingIsGivenNone TestVerifyRunHoldsTheRecordsBindingToThePreparation TestVerifyRunReportsARecordTheRuntimeDidNotBind TestARuntimeThatDoesNotSayItsReleaseEvaluatesNothing TestNamedDialectRealRuntimeReleaseRunOfflineAndRestart TestNamedDialectRealRuntimeReleaseRunOfflineAndRestart/2 TestNamedDialectRealRuntimeReleaseRunOfflineAndRestart/3 TestARunWhoseReceiptSessionIsNoTokenIsEvaluatedExportedAndVerified TestExecutorOwnedServiceRealRuntimeJobsAndChecks; do
printf '%s\n' "$out" | grep -q -- "^[[:space:]]*--- PASS: $name " || { echo "$name did not run and pass"; exit 1; }
done
# The exact real-Runtime lifetime observer uses Linux /proc and an
# isolated subreaper. The cross-platform Service test above is required
# on both CI hosts; Linux must also prove both owner-stop boundaries.
if [ "$RUNNER_OS" = Linux ]; then
for name in TestExecutorOwnedRealRuntimeSurvivesOwnerDeath TestExecutorOwnedRealRuntimeSurvivesOwnerDeath/close TestExecutorOwnedRealRuntimeSurvivesOwnerDeath/kill; do
printf '%s\n' "$out" | grep -q -- "^[[:space:]]*--- PASS: $name " || { echo "$name did not run and pass"; exit 1; }
done
fi
- name: Check API document
run: python3 -m json.tool openapi.json > /dev/null
- name: Build Runner
run: |
go build -trimpath -o "$RUNNER_TEMP/jpack-runner" ./cmd/jpack-runner
go build -trimpath -o "$RUNNER_TEMP/jpack-source-worker" ./cmd/jpack-source-worker
# The pinned Runtimes, the ownership-era Runner and the Gateway are checked out and built by
# the build-pinned step (scripts/checks/build-pinned.sh).
- name: Checks
run: scripts/dev/check.sh --all --skip release-toolchain,third-party-notices,release-archives,release-programs

# What a repository release carries (docs/releasing.md), checked on every
# commit so a tag is never the first to find a gap. The toolchain is the exact
Expand All @@ -199,21 +52,5 @@ jobs:
with:
go-version: '1.26.5'
cache: true
- name: Check the toolchain is the one the release workflow names
run: |
named="$(sed -n "s/^ *go-version: *['\"]\{0,1\}\([0-9][0-9.]*\)['\"]\{0,1\} *\$/\1/p" .github/workflows/release.yml)"
[ -n "$named" ] && [ "go$named" = "$(go env GOVERSION)" ] || { echo "release.yml names Go ${named:-nothing}; this job runs $(go env GOVERSION)"; exit 1; }
- name: Check third-party notices against linked modules
run: python3 tools/third_party_notices.py --check
# What the release workflow checks of its archives, run against sound
# archives and against archives spoiled one way at a time: real programs,
# built by this toolchain and archived.
- name: Check the archive check refuses each fault it names
run: python3 tools/test_release_archives.py
# Names in a file compared with directories: an early warning only. The
# release workflow checks the archives themselves.
- name: Check every program is named in the release build
run: |
want="$(ls cmd | sort)"
have="$(sed -n 's/^ binary: //p' .goreleaser.yml | sort)"
[ "$want" = "$have" ] || { echo "cmd and .goreleaser.yml name different programs:"; diff <(echo "$want") <(echo "$have"); exit 1; }
- name: The release steps of dev.json
run: scripts/dev/check.sh --all --only release-toolchain,third-party-notices,release-archives,release-programs
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
*.sqlite*
/dist/
__pycache__/

# The pinned Runtimes, Gateway and old Runner the tests run against (scripts/checks/build-pinned.sh).
/.ci/
45 changes: 45 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Working in this repository

This file is read by Codex as `AGENTS.md` and by Claude as `CLAUDE.md`; the two are the same text.
Edit `AGENTS.md` and run `scripts/sync-agent-docs.sh` to regenerate `CLAUDE.md`.

## Before you open a pull request

- Run `scripts/dev/check.sh` (or `jpdev check` where the kit is installed) once. It runs the
checks listed in `dev.json` in order and prints one summary block.
- Paste that summary block in the pull request body, under a "Checks" heading.
- If a check fails: read its first lines in the block, fix the cause, re-run that one check with
`--only NAME`, then run the whole list once more. Do not loop on the whole list.
- Start no CI watch. Push, report once, and stop; CI owns everything deterministic.

## Rules

- Write test vectors and fixtures by hand. Never record them "from a run" of the code under test.
- Keep machine-local paths and personal names out of tracked files, commit messages and pull
request bodies. Refer to people by role or handle where a name is needed.
- Sign off every commit (`git commit -s`). Never force-push; never rewrite published history.
- End a process you started by its process id. Never use a pattern kill (`pkill`, `killall`).
- One pull request per issue or batch. State what changed and why in plain, factual wording.
- A change to a public surface (a schema, a contract, a command's output) says so in the pull
request and changes its vectors and documentation in the same pull request.
- If a safeguard stops you (a permission prompt, a refused command, a failed gate), stop and say
so in your report. Do not work around it.

## Reports

Your final report is short: the pull request and its head commit, what changed, the check
summary block, and anything you stopped on or chose not to do.

## This repository

- The checks are `dev.json`, run by `scripts/dev/check.sh`; CI runs the same list in the
`Runner (ubuntu-latest)` and `Runner (macos-latest)` jobs, and the four release steps again in
`Release contents` with the exact Go toolchain `release.yml` names. Branch protection requires
those job names. The longer steps are scripts under `scripts/checks/`.
- The real-Runtime tests need the binaries `scripts/checks/build-pinned.sh` builds into `.ci/bin`
from pinned Runtime, Gateway and old-Runner commits (it needs network access to fetch them). A
pin and the release name beside it are moved together. The two test steps fail if a named test
does not run and pass, so a skip is a failure there.
- Runner is a single-owner local Jobs pilot; do not add shared-user permissions, external actions
or agent loops. Do not put real credentials, customer evidence or private operational records in
examples or fixtures.
45 changes: 45 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Working in this repository

This file is read by Codex as `AGENTS.md` and by Claude as `CLAUDE.md`; the two are the same text.
Edit `AGENTS.md` and run `scripts/sync-agent-docs.sh` to regenerate `CLAUDE.md`.

## Before you open a pull request

- Run `scripts/dev/check.sh` (or `jpdev check` where the kit is installed) once. It runs the
checks listed in `dev.json` in order and prints one summary block.
- Paste that summary block in the pull request body, under a "Checks" heading.
- If a check fails: read its first lines in the block, fix the cause, re-run that one check with
`--only NAME`, then run the whole list once more. Do not loop on the whole list.
- Start no CI watch. Push, report once, and stop; CI owns everything deterministic.

## Rules

- Write test vectors and fixtures by hand. Never record them "from a run" of the code under test.
- Keep machine-local paths and personal names out of tracked files, commit messages and pull
request bodies. Refer to people by role or handle where a name is needed.
- Sign off every commit (`git commit -s`). Never force-push; never rewrite published history.
- End a process you started by its process id. Never use a pattern kill (`pkill`, `killall`).
- One pull request per issue or batch. State what changed and why in plain, factual wording.
- A change to a public surface (a schema, a contract, a command's output) says so in the pull
request and changes its vectors and documentation in the same pull request.
- If a safeguard stops you (a permission prompt, a refused command, a failed gate), stop and say
so in your report. Do not work around it.

## Reports

Your final report is short: the pull request and its head commit, what changed, the check
summary block, and anything you stopped on or chose not to do.

## This repository

- The checks are `dev.json`, run by `scripts/dev/check.sh`; CI runs the same list in the
`Runner (ubuntu-latest)` and `Runner (macos-latest)` jobs, and the four release steps again in
`Release contents` with the exact Go toolchain `release.yml` names. Branch protection requires
those job names. The longer steps are scripts under `scripts/checks/`.
- The real-Runtime tests need the binaries `scripts/checks/build-pinned.sh` builds into `.ci/bin`
from pinned Runtime, Gateway and old-Runner commits (it needs network access to fetch them). A
pin and the release name beside it are moved together. The two test steps fail if a named test
does not run and pass, so a skip is a failure there.
- Runner is a single-owner local Jobs pilot; do not add shared-user permissions, external actions
or agent loops. Do not put real credentials, customer evidence or private operational records in
examples or fixtures.
Loading
Loading