Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d581983
test(db): fail CI if RPC revoke breaks last published CLI
cursoragent Aug 25, 2026
fc059b8
fix(test): resolve published CLI tag/npm version for contract CI
cursoragent Aug 25, 2026
5f197e2
fix(test): drop unused import in published CLI unit test
cursoragent Aug 25, 2026
f1063bb
docs: surface published CLI must-not-break rule for humans and agents
cursoragent Aug 25, 2026
80e273d
fix(test): read full CLI files for published RPC arg extraction
cursoragent Aug 25, 2026
81bd6b5
fix(test): address CodeRabbit review on published CLI contract
cursoragent Aug 25, 2026
4856a34
fix(test): reuse shared pg pool and cache Deno in CRITICAL CI job
cursoragent Aug 25, 2026
bd36d7c
fix(ci): cache Deno after checkout in published CLI job
cursoragent Aug 25, 2026
6c129e3
fix(test): match empty-arg RPC calls against any overload by name
cursoragent Aug 25, 2026
5a8424a
fix(test): require strict zero-arg RPC overload matching
cursoragent Aug 26, 2026
043394f
chore(ci): retrigger tests after concurrency cancellation
cursoragent Aug 26, 2026
a2fe5c3
chore(ci): retrigger tests after queue drain
cursoragent Aug 26, 2026
94e0863
chore(ci): trigger full test suite
cursoragent Aug 26, 2026
feebc91
fix(test): address CodeRabbit review on published CLI contract
cursoragent Aug 26, 2026
8d0beda
fix(test): align RPC overload arg counts with input-only names
cursoragent Aug 26, 2026
03b333c
fix(test): restore published CLI job and use pronargs for overload co…
cursoragent Aug 26, 2026
cf5fca5
fix(test): revert privilege SQL to last passing overload lookup
cursoragent Aug 26, 2026
7407534
fix(test): union RPC arg keys from ternary object literals
cursoragent Aug 26, 2026
bfee42a
ci: retrigger full test run after CRITICAL fix
cursoragent Aug 26, 2026
c256a4b
fix(test): harden published CLI contract resolution and npm parsing
cursoragent Aug 26, 2026
eb33d0d
fix(test): mark unresolved published CLI RPC args as dynamic
cursoragent Aug 26, 2026
8269b77
fix(test): address CodeRabbit published CLI contract review
cursoragent Aug 26, 2026
847d6e5
ci: raise published CLI contract job timeout to 15 minutes
cursoragent Aug 26, 2026
eca04ef
fix(test): drop duplicate overload schema assertion
cursoragent Aug 26, 2026
a6539b3
ci: reset isolated DB before published CLI contract tests
cursoragent Aug 26, 2026
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
89 changes: 87 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ jobs:
- name: Run backend integration tests
env:
VITEST_SHARD: ${{ matrix.shard }}
run: bun run supabase:with-env -- bunx vitest run --exclude=tests/*.unit.test.ts --exclude=tests/cli* --exclude=tests/read-replica-schema-catalog.test.ts --exclude=tests/updates* --exclude=tests/stats* --exclude=tests/channel_self* --exclude=tests/channel-rate-limit.test.ts $(sed 's|^|--exclude=|' tests/tinbase-db-tests.txt | tr '\n' ' ') --shard="$VITEST_SHARD"
run: bun run supabase:with-env -- bunx vitest run --exclude=tests/*.unit.test.ts --exclude=tests/cli* --exclude=tests/published-cli-rpc-contract.test.ts --exclude=tests/read-replica-schema-catalog.test.ts --exclude=tests/updates* --exclude=tests/stats* --exclude=tests/channel_self* --exclude=tests/channel-rate-limit.test.ts $(sed 's|^|--exclude=|' tests/tinbase-db-tests.txt | tr '\n' ' ') --shard="$VITEST_SHARD"
- name: Show Edge server logs after backend test failure
if: failure() && steps.bootstrap_edge_server.outputs.log_path != ''
run: |
Expand Down Expand Up @@ -845,7 +845,7 @@ jobs:
- name: Run Cloudflare Workers integration tests
env:
VITEST_SHARD: ${{ matrix.shard }}
run: bun run supabase:with-env -- bunx vitest run --exclude=tests/cli* --exclude=tests/*.unit.test.ts $(sed 's|^|--exclude=|' tests/tinbase-db-tests.txt | tr '\n' ' ') --config vitest.config.cloudflare.ts --shard="$VITEST_SHARD"
run: bun run supabase:with-env -- bunx vitest run --exclude=tests/cli* --exclude=tests/published-cli-rpc-contract.test.ts --exclude=tests/*.unit.test.ts $(sed 's|^|--exclude=|' tests/tinbase-db-tests.txt | tr '\n' ' ') --config vitest.config.cloudflare.ts --shard="$VITEST_SHARD"
- name: Show Cloudflare Worker logs after Cloudflare test failure
if: failure() && steps.start_cloudflare_workers.outputs.log_path != ''
run: |
Expand Down Expand Up @@ -1524,3 +1524,88 @@ jobs:
fi
done
bun scripts/supabase-worktree.ts stop --no-backup || true

published_cli_contract:
needs: changes
if: needs.changes.outputs.run_cli == 'true' || needs.changes.outputs.run_capgo == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
name: CRITICAL — Published CLI / do not break old CLI
permissions:
contents: read
concurrency:
group: capgo-published-cli-contract-${{ github.event_name }}-${{ github.repository }}-${{ github.head_ref || github.ref_name }}
cancel-in-progress: false
Comment thread
cursor[bot] marked this conversation as resolved.
env:
SUPABASE_WORKTREE_INSTANCE: published-cli-${{ github.run_id }}-${{ github.run_attempt }}
SUPABASE_WORKTREE_PORT_OFFSET: 6100
steps:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24.x
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Cache Deno dependencies
uses: actions/cache@v5 # v5
with:
path: ${{ env.DENO_DIR }}
key: deno-${{ runner.os }}-${{ hashFiles('supabase/functions/deno.lock') }}
Comment thread
cursor[bot] marked this conversation as resolved.
restore-keys: |
deno-${{ runner.os }}-
- name: Setup bun
Comment thread
cursor[bot] marked this conversation as resolved.
run: bash scripts/setup-bun.sh
- name: Install Supabase CLI
uses: supabase/setup-cli@v2
with:
version: 2.109.1
- name: Link Supabase templates
run: ln -sfn supabase/templates templates
- name: Stop this job's Supabase stack
run: bun scripts/supabase-worktree.ts stop --no-backup || true
- name: Install dependencies
run: bun install
- name: Verify published CLI tag exists
run: |
if ! git tag -l 'cli-*' | grep -Eq '^cli-[0-9]'; then
echo '::error::No cli-* tag found for published CLI contract'
exit 1
fi
- name: Run Supabase Start
run: bun scripts/supabase-worktree.ts start -x imgproxy,studio,mailpit,realtime,postgres-meta,supavisor,logflare,vector
Comment thread
cursor[bot] marked this conversation as resolved.
- name: Reset isolated Supabase database
run: bun run supabase:db:reset
- name: Export isolated Supabase endpoints
run: |
bun scripts/supabase-worktree.ts with-env bun -e 'const apiUrl = new URL(process.env.SUPABASE_URL ?? ""); const dbUrl = process.env.SUPABASE_DB_URL ?? ""; if (!apiUrl.host || !dbUrl) throw new Error("Isolated Supabase status is missing API_URL or DB_URL."); console.log(`SUPABASE_FUNCTIONS_HEALTH_URL=http-get://${apiUrl.host}/functions/v1/ok`); console.log(`MAIN_SUPABASE_DB_URL=${dbUrl}`)' >> "$GITHUB_ENV"
- id: bootstrap_published_cli_edge_server
name: Bootstrap Edge server
env:
BACKGROUND_SERVICE_NAME: Bootstrap Edge server
BACKGROUND_RUN_COMMAND: exec bun scripts/supabase-worktree.ts functions serve
BACKGROUND_LOG_PATH: ${{ runner.temp }}/published-cli-edge-server.log
BACKGROUND_WAIT_TIMEOUT_MS: 60000
run: |
export BACKGROUND_WAIT_ON="${SUPABASE_FUNCTIONS_HEALTH_URL:?Missing isolated Supabase health URL}"
bash .github/scripts/start-background-service.sh
- name: Run published CLI contract unit checks
run: bunx vitest run tests/published-cli-rpc-contract.unit.test.ts
- name: Run published CLI contract against PR schema
run: bun run supabase:with-env -- bunx vitest run tests/published-cli-rpc-contract.test.ts --no-file-parallelism --maxWorkers=1
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- name: Show published CLI Edge server logs after failure
if: failure() && steps.bootstrap_published_cli_edge_server.outputs.log_path != ''
run: |
echo "::group::Published CLI Bootstrap Edge server log"
tail -n 200 "${{ steps.bootstrap_published_cli_edge_server.outputs.log_path }}" || true
echo "::endgroup::"
- name: Stop published CLI background services
if: always()
run: |
for pid in "${{ steps.bootstrap_published_cli_edge_server.outputs.pid }}"; do
if [ -n "${pid}" ] && kill -0 "${pid}" 2>/dev/null; then
kill "${pid}" 2>/dev/null || true
fi
done
bun scripts/supabase-worktree.ts stop --no-backup || true
36 changes: 36 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,42 @@
This file provides guidance to AI agents (Claude Code, Cursor, Copilot, etc.)
when working with code in this repository.

## MUST NOT — never break already-published CLI versions

**We cannot break `@capgo/cli` releases that customers already run in production.**

The last published CLI is the newest `cli-<semver>` git tag (and the matching
`@capgo/cli` version on npm when it exists). Customers do not upgrade the CLI on
every backend deploy.

### You MUST NOT

- Revoke `GRANT` / `EXECUTE` on an RPC that the last published CLI still calls
(for example `.rpc('get_user_id', { apikey })` on the anonymous API-key path).
- Change backend identity resolution in ways that break the last published CLI
without shipping a CLI release that stops using the old path first.
- Make CI pass by **inverting** published-CLI contract tests to expect permission
denied (`42501`). Those tests assert **success** — that is the contract.
- Test only the PR-branch workspace CLI when validating RPC/grant changes. CI must
exercise the **published** npm CLI against the PR schema.

### Before you revoke anon/service access on a CLI-facing RPC

1. Ship a CLI release that no longer calls it.
2. Wait for customers to upgrade (the `cli-*` tag must reflect the new behavior).
3. Only then revoke or harden the RPC.

### Where this is enforced

- CI job: **`CRITICAL — Published CLI / do not break old CLI`**
- Tests: `tests/published-cli-rpc-contract.test.ts` (live contract; do not invert)
- Helpers: `scripts/published-cli-contract.ts` (parses `.rpc('...')` from the last `cli-*` tag)

Org-perm / invite **oracle** RPCs (`invite_user_to_org_rbac`, `get_org_perm_for_apikey*`,
`get_user_id(text,text)`, …) stay revoked for anonymous callers — see
`tests/security-oracle-rpc-hardening.test.ts`. That is separate from the published
CLI identity path (`get_user_id(text)` with a valid API key must keep working).

## Essential Development Commands

### Building and Development
Expand Down
24 changes: 24 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,30 @@ the relevant section before making your contribution. It will make it a lot
easier for us maintainers and smooth out the experience for all involved. The
community looks forward to your contributions. 🎉

## Do not break already-published CLI versions

**Never ship backend or database changes that break the Capgo CLI version customers
already have installed.**

The CLI on customer machines is whatever was last published (`cli-<semver>` git tags
and `@capgo/cli` on npm). It does not update when we deploy the API or database.

If you change RPC grants, revoke `EXECUTE`, or alter API-key identity behavior, you
must keep the **last published CLI** working until a new CLI release stops using the
old path and customers have had time to upgrade.

CI enforces this in the job **`CRITICAL — Published CLI / do not break old CLI`**
(`tests/published-cli-rpc-contract.test.ts`). That job runs the **published** npm CLI
against your PR schema — not only the CLI built from your branch. Do not “fix” failing
contract tests by expecting permission denied; they are supposed to pass.

Typical regression: revoking `get_user_id(text)` for `anon` while published CLI still
calls `.rpc('get_user_id', { apikey })` with a valid API key. That broke production in
the past; the contract tests exist so it cannot happen again.

Oracle hardening (blocking anonymous callers from org-perm / invite RPCs) is intentional
and separate — see `tests/security-oracle-rpc-hardening.test.ts`.

## Running tests locally

This project uses a custom test runner located in
Expand Down
Loading
Loading