diff --git a/.github/claude-code-pr-draft.md b/.github/claude-code-pr-draft.md new file mode 100644 index 0000000..99ade26 --- /dev/null +++ b/.github/claude-code-pr-draft.md @@ -0,0 +1,65 @@ +## Summary + +Add an experimental `claude-code` agent kit that runs Claude inside an acq +microVM using a configured OpenAI-shaped model provider, including USAi. + +- Pinned SHA-256-verified Claude native binaries for Linux x64 and ARM64. +- Private authenticated, session-local LiteLLM bridge from Anthropic Messages + to OpenAI Chat Completions, with hash-locked wheel-only dependencies. +- Unprivileged installation, including pip bootstrap for guests without ensurepip. +- Claude-owned model catalog and explicit canonical-name substitutions; no + separate Paseo catalog projection or global user-config rewrite. +- Host TUI access and optional browser access through the separate Paseo kit. +- README warning: browser picker lists more choices than the configured + OpenAI-shaped model provider supports; tracked at getpaseo/paseo#8321. + +Also consolidate repository CI into a four-kit matrix plus a Claude Python 3.12 +lane, reusing pinned upstream Patterns validators rather than importing the +Patterns skill/index toolchain. Shared checks cover lint/format, ShellCheck, +contracts, local doc links and executable modes, tests, dependency audits, +unsafe-shell patterns, and secrets. CI uses read-only permissions, SHA-pinned +actions and checksum-pinned Gitleaks; no model credentials or VM execution. + +## Verification + +- Local `make check` passed with the pinned Patterns checkout and verified + Gitleaks executable; all four kits validated and the provider bundle validated. +- Claude offline suite: 27 passed / 3 explicitly gated live skips. +- Actual relocated Claude live/unit/lifecycle suite: 30 passed, with four + upstream Pydantic deprecation warnings. +- Actual CLI text, file tool/result continuation and real SDK canonical + Sonnet 5.5 model switch were exercised through USAi. +- Operator observed ARM64 MSB 0.7.7 installation, provider-root presence, + browser access, and Sonnet 5/5.5 calls from the acq Quickstart checkout. +- Independent adversarial review performed; reproducibility findings addressed. + +GitHub Actions execution and local Python 3.12 execution remain unverified in +this session. Native x64 execution, backend mount enforcement, actual browser +permission/disconnect paths, and enterprise CA-specific validation remain live +gates. Offline x64 CI does not prove the native x64 Claude executable works. +See `claude-code/docs/verification.md` for history and limitations. + +## Security and licensing + +No credentials are stored in the kit, generated model settings, or CI. +Model routing comes from acq's backend-owned provider input; the proxy never +accepts client-supplied provider routing. Claude child environments have projected +provider credentials stripped, but this is defense in depth, not same-user +process isolation. The kit publishes no model API port and enables no permission +bypass. Optional Paseo browser exposure retains its separate trust boundary. + +Kit provenance from the local CC0 Patterns worktree is recorded in `NOTICE`; +distribution uses this repository's MIT license. Claude Code remains proprietary, +and downloaded dependencies retain their own licenses. + +## Rollback + +Remove the kit reference and recreate the disposable test sandbox without it. +Existing Claude user settings and transcripts are not overwritten. Revert the +repository change to restore the former CI layout if needed. + +## AI assistance + +Implementation, tests, documentation, and independent reviews were AI-assisted. +The human operator performed the reported macOS/MSB/browser checks. Human review +is required before production use. No compliance or ATO assertion is made. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b24aa9f..ad93e01 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,89 +9,73 @@ permissions: contents: read jobs: - test-and-validate: + kits: runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + kit: [opencode-go-provider, pi-coding-agent, first-mate-skill, claude-code] + python: ["3.14"] + include: + - kit: claude-code + python: "3.12" steps: - - name: Check out this repository - uses: actions/checkout@v4 - + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: ${{ matrix.python }} - name: Set up Node.js - uses: actions/setup-node@v4 + if: matrix.kit == 'opencode-go-provider' || matrix.kit == 'pi-coding-agent' + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: "22" + - name: Run kit's offline tests + env: + KIT: ${{ matrix.kit }} + run: python scripts/run-kit-tests.py "$KIT" --install - - name: Install kit test dependencies - working-directory: opencode-go-provider - run: npm ci - - - name: Run kit tests - working-directory: opencode-go-provider - run: npm test - - - name: Install pi agent-kit test dependencies - working-directory: pi-coding-agent - run: npm ci - - - name: Run pi agent-kit tests - working-directory: pi-coding-agent - run: npm test - - - name: Run pi agent-kit offline verification - run: bash pi-coding-agent/scripts/verify - - - name: Run first-mate skill-kit offline verification - run: bash first-mate-skill/scripts/verify - - - name: Set up Python - uses: actions/setup-python@v5 + shared: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - python-version: "3.12" - - - name: Install validator dependencies - run: python -m pip install --disable-pip-version-check jsonschema==4.26.0 PyYAML==6.0.3 - - - name: Check out the pinned patterns release - uses: actions/checkout@v4 + persist-credentials: false + fetch-depth: 0 + - name: Check out pinned Patterns validators + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: GSA-TTS/agentic-coding-patterns - # Pinned to the release this kit was authored and validated against. - # Update deliberately together with the validator. ref: a290d5d80737fc739e7ec139da0eeaf438631631 path: .patterns - - - name: Validate agent-kit and skill-kit specs with the patterns validator (when present) - shell: bash + persist-credentials: false + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.14" + - name: Install hash-locked shared tools run: | - set -euo pipefail - validator=".patterns/integrations/isolation/acq-kits/validate-kits.py" - schema=".patterns/schemas/kit-hybrid-v1.schema.json" - if [ ! -f "$validator" ] || [ ! -f "$schema" ]; then - echo "Pinned patterns checkout predates the neutral kit validator;" - echo "the vendored kit-hybrid-v1 schema test in npm test is authoritative." - exit 0 - fi - work="$(mktemp -d)" - mkdir -p "$work/schemas" "$work/integrations/isolation/acq-kits" - cp "$schema" "$work/schemas/" - printf 'schemaVersion: "acq-kits-registry/v1"\nkits:\n' \ - > "$work/integrations/isolation/acq-kits/kits.yaml" - for name in pi-coding-agent first-mate-skill; do - kit="$work/integrations/isolation/acq-kits/$name" - mkdir -p "$kit" - cp "$name/spec.yaml" "$name/README.md" "$kit/" - cp -R "$name/files" "$kit/files" - printf ' %s:\n backends: [sbx, msb]\n parity: CI validation entry.\n' "$name" \ - >> "$work/integrations/isolation/acq-kits/kits.yaml" - done - python "$validator" --root "$work" --strict - - - name: Validate provider input with a patched credential binding + python -m venv .tmp/ci-venv + .tmp/ci-venv/bin/python -m pip install --disable-pip-version-check --require-hashes --only-binary=:all: -r requirements-dev.lock + - name: Lint, format, infrastructure tests, and all kit contracts + run: | + make lint validate + .tmp/ci-venv/bin/python -m pytest scripts/tests -q + - name: Install checksum-pinned Gitleaks (no action token or license) shell: bash run: | set -euo pipefail - mkdir -p .tmp - python -c 'import json; source = ".patterns/integrations/isolation/schemas/provider-credential-bindings.json"; bindings = json.load(open(source, encoding="utf-8")); bindings["providers"]["opencode-go"] = "OPENCODE_GO_API_KEY"; json.dump(bindings, open(".tmp/provider-credential-bindings.json", "w", encoding="utf-8"), indent=2)' - python .patterns/scripts/validate_provider_input_contract.py \ - --schema-dir .patterns/integrations/isolation/schemas \ - --credential-bindings .tmp/provider-credential-bindings.json \ - --bundle opencode-go-provider/provider-input.json + mkdir -p .tmp/gitleaks + curl --fail --silent --show-error --location --proto '=https' --tlsv1.2 --max-time 120 \ + https://github.com/gitleaks/gitleaks/releases/download/v8.24.3/gitleaks_8.24.3_linux_x64.tar.gz \ + -o .tmp/gitleaks/archive.tar.gz + echo '9991e0b2903da4c8f6122b5c3186448b927a5da4deef1fe45271c3793f4ee29c .tmp/gitleaks/archive.tar.gz' | sha256sum --check + tar -xzf .tmp/gitleaks/archive.tar.gz -C .tmp/gitleaks gitleaks + - name: Dependency audit, unsafe shell, and secrets scans + run: make security GITLEAKS="$PWD/.tmp/gitleaks/gitleaks" diff --git a/.gitignore b/.gitignore index 06b86ce..af08881 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,11 @@ tests/output/ .patterns/ .tmp/ +# Python validation artifacts +__pycache__/ +.pytest_cache/ +.ruff_cache/ + # OS noise .DS_Store Thumbs.db diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..06d04ec --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +PYTHON ?= python3 +VENV ?= .tmp/ci-venv +PY := $(VENV)/bin/python +PATTERNS ?= .patterns +LINT_PATHS := scripts/validate-kits.py scripts/run-kit-tests.py scripts/tests claude-code/files claude-code/tests claude-code/scripts/test claude-code/scripts/verify +GITLEAKS ?= gitleaks + +.PHONY: setup check lint test validate security +setup: + @if command -v uv >/dev/null 2>&1; then uv venv --python $(PYTHON) --seed $(VENV); else $(PYTHON) -m venv $(VENV); fi + $(PY) -m pip install --disable-pip-version-check --require-hashes --only-binary=:all: -r requirements-dev.lock + npm --prefix opencode-go-provider ci --ignore-scripts --no-audit --no-fund + npm --prefix pi-coding-agent ci --ignore-scripts --no-audit --no-fund + +check: lint validate test security + +lint: + $(PY) -m ruff check $(LINT_PATHS) + $(PY) -m ruff format --check $(LINT_PATHS) + shellcheck claude-code/files/home/.local/bin/claude + +validate: + $(PY) scripts/validate-kits.py --patterns $(PATTERNS) + +test: + $(PY) -m pytest scripts/tests -q + $(PY) scripts/run-kit-tests.py all + +security: + $(PY) -m pip_audit --strict --require-hashes --disable-pip -r requirements-dev.lock + $(PY) -m pip_audit --strict --require-hashes --disable-pip -r claude-code/files/home/claude-code/requirements.lock + $(PY) $(PATTERNS)/scripts/scan_unsafe_shell.py claude-code + $(GITLEAKS) dir --redact --no-banner --log-level warn claude-code + $(GITLEAKS) dir --redact --no-banner --log-level warn scripts + $(GITLEAKS) git --redact --no-banner --log-level warn . diff --git a/README.md b/README.md index 1276430..e56694c 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ An agent kit is a `hybrid/v1` mixin that: | Kit | Kind | Provides | Status | | --- | --- | --- | --- | +| [`claude-code/`](claude-code/) | Agent | Pinned Claude Code + private Messages-to-Chat-Completions proxy for a configured OpenAI-shaped model provider | **Experimental** — operator-verified on ARM64 MSB; x64 execution pending; browser model picker overstates availability | | [`opencode-go-provider/`](opencode-go-provider/) | Provider | OpenCode Go (`openai-compatible`) facts and a 19-model neutral catalog | **Preview** — blocked on the acq credential-binding addition | | [`pi-coding-agent/`](pi-coding-agent/) | Agent | Pinned pi CLI + `~/.pi/agent/models.json` renderer | **Preview** — live verification pending acq PR 611 | | [`first-mate-skill/`](first-mate-skill/) | Skill | The `paseo-first-mate` Agent Skill and two helper scripts, dropped into `~/.agents/skills` | Offline-verified; no live sandbox run | @@ -113,6 +114,23 @@ first-mate-skill/ skill kit New kits follow the same directory contract and get a row in the table above. +### Executable scripts on shared mounts + +Shared host/microVM mounts can report filesystem permissions differently from +Git's recorded modes. Validation checks both declared executable payloads and +Git index entries for executable scripts, including normalizers and test/verify +entrypoints. An executable script must be staged with mode `100755`; checking +only `test -x` locally does not prove a fresh CI checkout will be executable. +For an intended executable, stage it and run +`git update-index --chmod=+x