fix(plugin): drop the duplicate hooks declaration from the manifest #1216
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Tests | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| node-tests: | |
| name: Node.js ${{ matrix.node-version }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| node-version: [20, 22, 24] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js ${{ matrix.node-version }} | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ matrix.node-version }} | |
| - name: Install dependencies | |
| run: npm install --no-package-lock | |
| - name: Run tests | |
| run: npm test | |
| python-tests: | |
| name: Python ${{ matrix.python-version }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.10", "3.11", "3.12", "3.13"] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Install dependencies | |
| run: | | |
| python3 -m pip install --upgrade pip | |
| # Install from the canonical list, not a hand-maintained subset -- the | |
| # same fix shell-tests already made after its own drift incident (see | |
| # the comment on that job). Two hand-written lists for one test suite | |
| # drift apart by construction; one source cannot. | |
| # | |
| # This list also carries the pinned `mcp`, which the hand-written one | |
| # omitted. Note that installing it does NOT by itself un-skip | |
| # tests/test_mcp_agent_metrics_honesty.py: the repo's own `mcp/` | |
| # package shadows the installed SDK on sys.path, so `from mcp import | |
| # server` reaches our module, which then cannot import the SDK and | |
| # exits. That shadowing is a separate defect, tracked separately -- | |
| # what this change fixes is the drift, not the skip. | |
| python3 -m pip install -r requirements-test.txt | |
| - name: Run tests | |
| run: python3 -m pytest | |
| shell-tests: | |
| name: Shell tests (shard ${{ matrix.shard }}/4) | |
| runs-on: ubuntu-latest | |
| # SHARDED because this job WAS the pipeline. Measured 2026-07-30: | |
| # Shell tests 13m01s <- sets the wall clock single-handedly | |
| # Bun (ubuntu) 2m17s | |
| # Python 3.13 1m59s | |
| # Helm lint 8s | |
| # 289 suites ran serially on one runner. Four shards cut that to roughly | |
| # 3m15s, putting the whole pipeline near the ~2.5m the other jobs already | |
| # take. | |
| # | |
| # fail-fast is OFF deliberately: one shard failing must not cancel the | |
| # others, or a single red suite hides every other failure and the next | |
| # push rediscovers them one cycle at a time. | |
| # | |
| # Safety: tests/test-shard-coverage.sh proves the shards partition the | |
| # suite list exactly (every suite in exactly one shard, none dropped, none | |
| # duplicated) and that an invalid shard spec exits non-zero rather than | |
| # silently running nothing. | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [0, 1, 2, 3] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install Python dependencies | |
| run: | | |
| python3 -m pip install --upgrade pip | |
| # Install from the canonical list, not a hand-maintained subset. This | |
| # job previously installed only "pydantic httpx fastapi uvicorn" and | |
| # omitted sqlalchemy/aiosqlite, so every shell test that imports | |
| # dashboard/server.py died with | |
| # ModuleNotFoundError: No module named 'sqlalchemy' | |
| # and reported as a product failure. Five suites were red for that one | |
| # missing dependency. Sourcing requirements-test.txt keeps this job and | |
| # the python-tests job from drifting apart again. | |
| python3 -m pip install -r requirements-test.txt | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - name: Install Node.js dependencies | |
| run: npm install --no-package-lock | |
| # REQUIRED, not optional. tests/test-trust-core-tests-detect.sh runs two | |
| # mutation probes as `cd loki-ts && bun test ...`. Without bun the probe | |
| # baseline exits 127, the probe reports rc=67, and the suite goes red -- | |
| # which is the honest outcome, but the cause is a missing toolchain here, | |
| # not a defect in the trust core. | |
| # | |
| # These probes are deliberately NOT skipped when bun is absent: they guard | |
| # the trust core, and a probe that silently does not run is an absent | |
| # measurement reported as a pass. | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install loki-ts dependencies (for the TS mutation probes) | |
| working-directory: loki-ts | |
| run: bun install | |
| - name: Run shell tests (shard ${{ matrix.shard }} of 4) | |
| env: | |
| LOKI_TEST_SHARD: ${{ matrix.shard }}/4 | |
| run: bash tests/run-all-tests.sh | |
| helm-lint: | |
| name: Helm lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Helm | |
| uses: azure/setup-helm@v4 | |
| - name: Lint Helm chart | |
| run: helm lint deploy/helm/autonomi | |
| # LINT IS NOT ENOUGH ON ITS OWN. `helm lint` renders with the DEFAULT | |
| # values, where ingress and keda are off and worker.mode is "job". Every | |
| # template guarded by `{{- if .Values.ingress.enabled }}` or the | |
| # deployment/serverless worker branches is therefore never exercised, so a | |
| # broken conditional ships green and only fails for the operator who turns | |
| # that feature on. | |
| # | |
| # Rendering each optional path costs seconds and is the difference between | |
| # "the chart parses" and "the chart works in the configurations we | |
| # document". | |
| - name: Render optional paths | |
| run: | | |
| set -euo pipefail | |
| for combo in \ | |
| "ingress.enabled=true" \ | |
| "keda.enabled=true" \ | |
| "worker.mode=deployment" \ | |
| "worker.mode=serverless" \ | |
| "security.networkPolicy.enabled=true" | |
| do | |
| echo "== helm template --set ${combo}" | |
| helm template ci deploy/helm/autonomi --set "${combo}" > /dev/null | |
| done | |
| # The schema must REJECT bad values, not merely exist. A schema that | |
| # accepts everything looks like validation and is not. | |
| - name: values.schema.json rejects bad values | |
| run: | | |
| set -uo pipefail | |
| if helm template ci deploy/helm/autonomi --set worker.mode=Deployment >/dev/null 2>&1; then | |
| echo "FAIL: worker.mode=Deployment was accepted; the schema is not enforcing" | |
| exit 1 | |
| fi | |
| echo "OK: invalid worker.mode rejected by values.schema.json" | |
| dashboard-build: | |
| name: Dashboard build verification | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: dashboard-ui/package-lock.json | |
| - name: Install dependencies | |
| run: cd dashboard-ui && npm ci | |
| - name: Build dashboard | |
| run: cd dashboard-ui && npm run build:all | |
| - name: Verify build output | |
| run: | | |
| if [ ! -f dashboard/static/index.html ]; then | |
| echo "ERROR: dashboard/static/index.html not found after build" | |
| exit 1 | |
| fi | |
| size=$(wc -c < dashboard/static/index.html) | |
| echo "Dashboard build output size: $size bytes" | |
| if [ "$size" -lt 100000 ]; then | |
| echo "ERROR: Build output smaller than expected (${size} < 100000 bytes)" | |
| exit 1 | |
| fi | |
| bun-tests: | |
| name: Bun tests on ${{ matrix.os }} bun=${{ matrix.bun-version }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest] | |
| # v7.4.10: Bun version matrix. 1.3.13 is the canonical pin shipped | |
| # in the Dockerfile; latest catches breakage from upstream Bun | |
| # releases before users hit it via `brew upgrade bun`. | |
| bun-version: ["1.3.13", "latest"] | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Bun ${{ matrix.bun-version }} | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: ${{ matrix.bun-version }} | |
| - name: Setup Python (for bash route fallthroughs) | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Setup Node.js (for shim fallthroughs to bash CLI) | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - name: Install Python deps used by bash CLI tests | |
| run: | | |
| python3 -m pip install --upgrade pip | |
| python3 -m pip install pydantic httpx fastapi uvicorn | |
| - name: Install Node deps used by bash CLI tests | |
| run: npm install --no-package-lock | |
| - name: Install loki-ts dependencies | |
| working-directory: loki-ts | |
| run: bun install | |
| - name: Typecheck loki-ts | |
| working-directory: loki-ts | |
| run: bun run typecheck | |
| - name: Run loki-ts unit tests | |
| working-directory: loki-ts | |
| run: bun test | |
| - name: Build loki-ts bundle (for the SDK bridge tests + bin/loki) | |
| working-directory: loki-ts | |
| run: bun run build | |
| # v8: the raw-SDK judge/text bridges. With bun + the built bundle present, | |
| # these run the wiring / opt-in / binary-free-ordering / mocked-success | |
| # assertions FOR REAL (they self-skip only when bun is absent). No API key | |
| # is set, so every path is exercised fail-closed -- no billable call. | |
| - name: Run v8 SDK bridge tests (fail-closed, no ANTHROPIC_API_KEY) | |
| run: | | |
| bash tests/test-sdk-done-recog-bridge.sh | |
| bash tests/test-sdk-text-bridge.sh | |
| bash tests/test-sdk-council-vote.sh | |
| bash tests/test-sdk-voter-agents.sh | |
| bash tests/test-sdk-loop-routing.sh | |
| bash tests/test-sdk-mode.sh | |
| bash tests/test-bundled-sdk-provider.sh | |
| bash tests/test-sandbox-deprecation.sh | |
| bash tests/test-acceptance-resume-idempotence.sh | |
| # First-run funnel privacy. Stubs curl and asserts on the real POST body, | |
| # so it observes what would actually reach the wire (the reverted attempt's | |
| # test stubbed loki_telemetry itself and could never see a leak). Hermetic: | |
| # fake HOME, fake curl on PATH, no real network send. It sets | |
| # LOKI_TELEMETRY=on explicitly, which by the documented gate precedence | |
| # overrides the CI auto-off, so the gates are genuinely open here. | |
| - name: Run first-run funnel privacy tests (stubbed curl, no real egress) | |
| run: bash tests/test-funnel-privacy.sh | |
| - name: Run CLI tests via bash route (LOKI_LEGACY_BASH=1) | |
| env: | |
| LOKI_LEGACY_BASH: "1" | |
| run: bash tests/test-cli-commands.sh | |
| - name: Run CLI tests via Bun shim route | |
| run: PATH="$PWD/bin:$PATH" bash tests/test-cli-commands.sh | |
| - name: Install hyperfine (Linux) | |
| if: runner.os == 'Linux' | |
| run: sudo apt-get update && sudo apt-get install -y hyperfine | |
| - name: Install hyperfine (macOS) | |
| if: runner.os == 'macOS' | |
| run: brew install hyperfine | |
| - name: Sanity bench-suite (hyperfine, 5 runs, 1 warmup) | |
| working-directory: loki-ts | |
| run: bun run scripts/bench-suite.ts --runs 5 --warmup 1 | |
| # v8: the real SDK-loop E2E (LOKI_SDK_LOOP=1 + LOKI_E2E_SDK=1) makes a BILLABLE | |
| # API call, so it runs ONLY when the ANTHROPIC_API_KEY secret is configured. | |
| # It self-skips when the gate is off, so forks / secret-less runs stay free and | |
| # green. This closes the loop on the SDK RARV path: the unit + bridge tests | |
| # prove the mechanism keyless; this proves a real one-file app builds end to end. | |
| sdk-loop-e2e: | |
| name: v8 SDK-loop E2E (gated on ANTHROPIC_API_KEY secret) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: "1.3.13" | |
| - name: Install loki-ts dependencies | |
| working-directory: loki-ts | |
| run: bun install | |
| - name: Build loki-ts bundle | |
| working-directory: loki-ts | |
| run: bun run build | |
| - name: Run the gated SDK-loop E2E (real build; self-skips without the key) | |
| working-directory: loki-ts | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| LOKI_E2E_SDK: ${{ secrets.ANTHROPIC_API_KEY != '' && '1' || '0' }} | |
| run: bun test ./tests/integration/sdk_loop_e2e.test.ts | |
| # v8.1 (Story 6, T3-prep): prove the SDK JUDGE path works from the SHIPPED npm | |
| # tarball on a host with NO `claude` binary on PATH and only ANTHROPIC_API_KEY. | |
| # This is the gate that later lets LOKI_SDK_LOOP flip default-on safely: it | |
| # catches the ".gitignore-excluded dist" class of bug (v6.25 lesson) for the SDK | |
| # route, because the bundled @anthropic-ai/sdk judge code MUST be inside the | |
| # tarball for the keyless fail-closed probe to load at all. | |
| # | |
| # KEYLESS probe (always runs, free): from the packed tarball, `loki internal | |
| # sdk-judge` with no key must exit 1 (fail-closed null) WITHOUT a module-not-found | |
| # -- proving the bundled SDK ships and the judge wiring is intact. BILLABLE probe | |
| # (gated on the ANTHROPIC_API_KEY secret): the same call with a key returns exit 0 | |
| # + valid JSON, proving the SDK judge path end-to-end from the artifact. | |
| sdk-tarball-no-binary: | |
| name: v8.1 SDK judge from packed tarball (no claude binary) | |
| runs-on: ubuntu-latest | |
| # The `secrets` context is NOT available in a step-level `if:` -- referencing | |
| # it there makes GitHub reject the whole workflow file at parse time, so ZERO | |
| # jobs are created and the run reports the opaque "workflow file issue" | |
| # failure. Hoist the secret to job-level env and gate on the env var instead; | |
| # `env` IS available in `if:`. A secret-less fork sees an empty string and the | |
| # billable steps self-skip exactly as intended. | |
| env: | |
| HAS_ANTHROPIC_KEY: ${{ secrets.ANTHROPIC_API_KEY != '' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: "1.3.13" | |
| - name: Setup Node.js (npm pack/install) | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - name: Build the loki-ts bundle (dist/loki.js the tarball ships) | |
| working-directory: loki-ts | |
| run: bun install && bun run build | |
| - name: Pack the npm tarball and install it into a clean prefix | |
| run: | | |
| set -euo pipefail | |
| # Pack exactly what npm publish would ship. | |
| TARBALL="$(npm pack --silent | tail -1)" | |
| echo "packed: $TARBALL" | |
| # Prove the load-bearing files are IN the tarball before installing. | |
| # Assert the EXACT file bin/loki resolves (dist/loki.js), not just the | |
| # dir prefix: a partial/empty dist/ would pass a prefix grep while | |
| # bin/loki's `[ -f dist/loki.js ]` check fails and routes to bash -- | |
| # green-washing a missing bundle (council S5+S6 R2 HIGH). Exact file closes it. | |
| # List ONCE into a file, then grep the file. Piping `tar tzf` straight | |
| # into `grep -q` is a SIGPIPE trap: grep -q exits at the first match and | |
| # closes the pipe, GNU tar then dies with "tar: stdout: write error", | |
| # and `set -o pipefail` fails the whole step even though the assertion | |
| # SUCCEEDED. That is exactly how this job broke -- it reported a missing | |
| # artifact when the artifact was present. (BSD tar on macOS tolerates | |
| # the closed pipe, so it does not reproduce locally.) | |
| TARLIST="$RUNNER_TEMP/tarball-contents.txt" | |
| tar tzf "$TARBALL" > "$TARLIST" | |
| grep -qx 'package/loki-ts/dist/loki.js' "$TARLIST" \ | |
| || { echo 'FAIL: loki-ts/dist/loki.js missing from tarball (SDK judge would not ship)'; exit 1; } | |
| grep -qx 'package/autonomy/lib/sdk-mode.sh' "$TARLIST" \ | |
| || { echo 'FAIL: autonomy/lib/sdk-mode.sh missing from tarball (mode resolver would not ship)'; exit 1; } | |
| mkdir -p "$RUNNER_TEMP/loki-prefix" | |
| npm install -g --prefix "$RUNNER_TEMP/loki-prefix" "./$TARBALL" | |
| echo "$RUNNER_TEMP/loki-prefix/bin" >> "$GITHUB_PATH" | |
| # Resolve the INSTALLED dist/loki.js and pin LOKI_TS_ENTRY to it for the | |
| # probes. This forces bin/loki onto the Bun/TS route: if the installed | |
| # bundle is missing, bin/loki prints an explicit "LOKI_TS_ENTRY ... does | |
| # not exist; falling through" and routes to bash -- which the probe below | |
| # asserts must NOT happen. Exit code alone cannot tell the TS null path | |
| # (exit 1) from bash's Unknown-command (also exit 1); this pin + the | |
| # positive TS-route assertion below close that false-green. | |
| DIST="$(find "$RUNNER_TEMP/loki-prefix/lib/node_modules" -path '*/loki-ts/dist/loki.js' 2>/dev/null | head -1)" | |
| [ -n "$DIST" ] && [ -f "$DIST" ] \ | |
| || { echo "FAIL: installed loki-ts/dist/loki.js not found under the npm prefix"; exit 1; } | |
| echo "installed dist: $DIST" | |
| echo "LOKI_TS_ENTRY=$DIST" >> "$GITHUB_ENV" | |
| - name: Assert NO claude binary on PATH (the whole point) | |
| run: | | |
| if command -v claude >/dev/null 2>&1; then | |
| echo "FAIL: a claude binary is on PATH; this job must prove the binary-free SDK path" | |
| exit 1 | |
| fi | |
| echo "confirmed: no claude binary on PATH" | |
| - name: Prepare a tiny judge prompt + schema fixture | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$RUNNER_TEMP/fix" | |
| printf '%s\n' 'Is the number 4 even? Answer via the schema.' > "$RUNNER_TEMP/fix/prompt.txt" | |
| cat > "$RUNNER_TEMP/fix/schema.json" <<'JSON' | |
| { | |
| "type": "object", | |
| "additionalProperties": false, | |
| "required": ["verdict"], | |
| "properties": { "verdict": { "type": "string", "enum": ["yes", "no"] } } | |
| } | |
| JSON | |
| - name: POSITIVE proof the TS SDK route runs (bash fall-through cannot fake) | |
| run: | | |
| set -uo pipefail | |
| # Exit-1 alone is a false-green: if dist/loki.js were missing, bin/loki | |
| # falls through to the bash CLI, which has NO `internal` handler -> prints | |
| # "Unknown command: internal", exits 1, empty stdout -- passing the keyless | |
| # probe's exit-1/no-module/empty checks for the WRONG reason (council | |
| # S5+S6 R2 HIGH). Close it with a POSITIVE assertion only the TS bridge | |
| # can satisfy: call `internal sdk-judge` with NO flags. The TS bridge | |
| # (internal_sdk_judge.ts) exits 2 with the EXACT string | |
| # "sdk-judge: --prompt-file and --schema-file are required"; the bash CLI | |
| # would instead say "Unknown command: internal". This proves control | |
| # actually reached the shipped TS bundle, not the bash fall-through. | |
| set +e | |
| loki internal sdk-judge >"$RUNNER_TEMP/pout.txt" 2>"$RUNNER_TEMP/perr.txt" | |
| PRC=$? | |
| set -e | |
| echo "no-flags exit=$PRC"; echo "--- stderr ---"; cat "$RUNNER_TEMP/perr.txt" || true | |
| if grep -qiE 'Unknown command|falling through|does not exist' "$RUNNER_TEMP/perr.txt"; then | |
| echo "FAIL: bin/loki fell through to the bash CLI -- the TS SDK bundle did NOT run (missing dist?)"; exit 1 | |
| fi | |
| grep -q 'sdk-judge: --prompt-file and --schema-file are required' "$RUNNER_TEMP/perr.txt" \ | |
| || { echo "FAIL: did not see the TS bridge's arg-required message; TS SDK route not confirmed"; exit 1; } | |
| [ "$PRC" -eq 2 ] || { echo "FAIL: expected exit 2 (TS bridge bad-args); got $PRC"; exit 1; } | |
| echo "PASS: the shipped TS SDK bridge ran (arg-required message + exit 2 from internal_sdk_judge.ts)" | |
| - name: UNPINNED proof (natural bin/loki dist resolution, no LOKI_TS_ENTRY) | |
| run: | | |
| set -uo pipefail | |
| # The step above PINS LOKI_TS_ENTRY, which disambiguates exit codes but | |
| # masks bin/loki's NATURAL `[ -f dist/loki.js ]` resolution -- the path a | |
| # real npm user (no LOKI_TS_ENTRY) actually hits. Run the SAME positive | |
| # assertion with LOKI_TS_ENTRY UNSET so a bin/loki resolution regression | |
| # (broken REPO_ROOT / dist branch) that routes a real user to bash is | |
| # caught HERE too, not only by the dual-route CI (council S5+S6 re-review | |
| # R1 LOW hardening). Same TS-only markers; bash fall-through cannot fake. | |
| set +e | |
| env -u LOKI_TS_ENTRY loki internal sdk-judge >"$RUNNER_TEMP/upout.txt" 2>"$RUNNER_TEMP/uperr.txt" | |
| URC=$? | |
| set -e | |
| echo "unpinned no-flags exit=$URC"; echo "--- stderr ---"; cat "$RUNNER_TEMP/uperr.txt" || true | |
| if grep -qiE 'Unknown command|falling through|does not exist' "$RUNNER_TEMP/uperr.txt"; then | |
| echo "FAIL: natural bin/loki resolution routed to bash -- a real npm user would not hit the TS SDK"; exit 1 | |
| fi | |
| grep -q 'sdk-judge: --prompt-file and --schema-file are required' "$RUNNER_TEMP/uperr.txt" \ | |
| || { echo "FAIL: natural resolution did not reach the TS bridge"; exit 1; } | |
| [ "$URC" -eq 2 ] || { echo "FAIL: expected exit 2 from the natural TS route; got $URC"; exit 1; } | |
| echo "PASS: natural bin/loki dist resolution reaches the shipped TS bridge (no LOKI_TS_ENTRY)" | |
| - name: KEYLESS fail-closed probe (no ANTHROPIC_API_KEY) -- always runs | |
| env: | |
| LOKI_SDK_MODE: judges | |
| run: | | |
| set -uo pipefail | |
| # With the TS route proven above, this asserts the fail-closed contract: | |
| # no key -> judgeJson returns null -> EXACTLY exit 1, no stdout, no module | |
| # error. Distinct from exit 2 (bad args) / exit 3 (read error) / 0 (ran). | |
| # The named --prompt-file/--schema-file flags the bridge requires are used. | |
| set +e | |
| OUT="$(loki internal sdk-judge --prompt-file "$RUNNER_TEMP/fix/prompt.txt" --schema-file "$RUNNER_TEMP/fix/schema.json" 2>"$RUNNER_TEMP/err.txt")" | |
| RC=$? | |
| set -e | |
| echo "exit=$RC stdout=[$OUT]" | |
| echo "--- stderr ---"; cat "$RUNNER_TEMP/err.txt" || true | |
| if grep -qiE 'cannot find (package|module)|ERR_MODULE_NOT_FOUND|Cannot find module' "$RUNNER_TEMP/err.txt"; then | |
| echo "FAIL: the bundled @anthropic-ai/sdk did not ship in the tarball (module-not-found)"; exit 1 | |
| fi | |
| if grep -qiE 'Unknown command|falling through|does not exist' "$RUNNER_TEMP/err.txt"; then | |
| echo "FAIL: bin/loki fell through to bash -- exit 1 here would be a false-green"; exit 1 | |
| fi | |
| if [ "$RC" -ne 1 ]; then | |
| echo "FAIL: expected exit 1 (fail-closed null from judgeJson); got $RC." | |
| echo " exit 2 = args never parsed; exit 3 = fixture unreadable; exit 0 = ran without a key." | |
| exit 1 | |
| fi | |
| [ -z "$OUT" ] || { echo "FAIL: fail-closed path must print no stdout"; exit 1; } | |
| echo "PASS: keyless SDK judge fail-closed cleanly (exit 1) from the tarball -- SDK code shipped" | |
| - name: BILLABLE judge probe (gated on the ANTHROPIC_API_KEY secret) | |
| if: env.HAS_ANTHROPIC_KEY == 'true' | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| LOKI_SDK_MODE: judges | |
| run: | | |
| set -euo pipefail | |
| OUT="$(loki internal sdk-judge --prompt-file "$RUNNER_TEMP/fix/prompt.txt" --schema-file "$RUNNER_TEMP/fix/schema.json")" | |
| echo "verdict output: $OUT" | |
| echo "$OUT" | python3 -c "import json,sys; d=json.load(sys.stdin); assert d['verdict'] in ('yes','no'), d; print('PASS: real SDK judge verdict from the tarball:', d['verdict'])" | |
| # RUN-25 iter 5 (T3(d)): drive the full RARV LOOP end-to-end from the INSTALLED | |
| # tarball with NO claude binary and LOKI_SDK_LOOP=1, on a tiny fixture spec. The | |
| # judge probes above prove the one-shot SDK path ships; this proves the agentic | |
| # loop (the Agent SDK query() path + consumeSdkStream) works from the shipped | |
| # artifact -- the platform-specific Agent-SDK native binary resolves under a clean | |
| # npm install, the loop runs to a terminal result, and it writes the real .loki | |
| # state (agents.json + result-cost). This is the LAST loop-flip pre-flight gate: | |
| # without it, flipping LOKI_SDK_LOOP default-on could ship a tarball whose loop | |
| # crashes on a fresh install (the v6.25 dist-excluded bug class, for the loop). | |
| # Billable + gated on the secret (self-skips on forks / secret-less pushes). | |
| - name: BILLABLE RARV-loop E2E from the tarball (gated on ANTHROPIC_API_KEY) | |
| if: env.HAS_ANTHROPIC_KEY == 'true' | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| LOKI_SDK_LOOP: "1" | |
| LOKI_MAX_ITERATIONS: "2" | |
| run: | | |
| set -uo pipefail | |
| # A trivially-completable one-file spec so the loop terminates fast+cheap. | |
| WORK="$RUNNER_TEMP/loop-e2e" | |
| mkdir -p "$WORK" | |
| cat > "$WORK/spec.md" <<'SPEC' | |
| # Tiny spec | |
| Create a file hello.txt containing exactly the text "hello world". | |
| That is the entire task. When hello.txt exists with that content, the task is complete. | |
| SPEC | |
| cd "$WORK" | |
| # Bound wall-clock so a hang fails the job instead of running the full budget. | |
| set +e | |
| timeout 600 loki start ./spec.md --max-iterations 2 > "$RUNNER_TEMP/loop.out" 2>&1 | |
| RC=$? | |
| set -e | |
| echo "loop exit=$RC"; echo "--- tail ---"; tail -30 "$RUNNER_TEMP/loop.out" || true | |
| # The loop must have written real .loki state from the SDK stream parser. | |
| if [ ! -f "$WORK/.loki/state/agents.json" ]; then | |
| echo "FAIL: .loki/state/agents.json not written -- the SDK loop did not run from the tarball"; exit 1 | |
| fi | |
| if ! ls "$WORK/.loki/metrics/"result-cost-*.json >/dev/null 2>&1; then | |
| echo "FAIL: no result-cost-*.json -- the SDK loop produced no terminal result from the tarball"; exit 1 | |
| fi | |
| # Module-not-found / SDK-load failure would mean the Agent SDK binary did not | |
| # resolve from the installed tarball. | |
| if grep -qiE 'cannot find (package|module)|ERR_MODULE_NOT_FOUND|sdk-loop error' "$RUNNER_TEMP/loop.out"; then | |
| echo "FAIL: Agent SDK failed to load/run from the tarball"; exit 1 | |
| fi | |
| echo "PASS: RARV loop ran end-to-end from the tarball (agents.json + result-cost written, no SDK-load error)" |