Skip to content

feat(mt#4701): Give the warrant vocabulary a falsifier: detect a hedge that decays across turns #2184

feat(mt#4701): Give the warrant vocabulary a falsifier: detect a hedge that decays across turns

feat(mt#4701): Give the warrant vocabulary a falsifier: detect a hedge that decays across turns #2184

name: Integration Tests
# mt#2634 — CI wiring decision for tests/integration/*.integration.test.ts.
#
# These tests are gated behind RUN_INTEGRATION_TESTS=1 (and, for two of the
# four files, real external credentials/infra). Before this workflow existed
# they were reachable only via `bun run test:integration` /
# `test:integration:docker` run by hand — no CI signal ever ran them.
#
# This workflow is intentionally NOT part of branch protection's required
# `build` check (see ci.yml). It is informational: every job below either
# runs against real external services (flaky by nature) or needs secrets
# that may not be configured on this repo. Treat a red job here as a
# heads-up, not a merge blocker.
#
# Coverage decision per file (see the task spec for the full rationale):
#
# - session-edit-file-cursor-parity.integration.test.ts
# Needs only RUN_INTEGRATION_TESTS=1 (no external creds — internally
# skips its Morph-API-dependent assertions when no Morph key is
# configured). Cheapest candidate; runs unconditionally on every
# matching PR via the `credential-free` job below.
#
# - github-api.integration.test.ts / github-issues-backend.integration.test.ts
# Create/close real issues against a DEDICATED test repo
# (owner/repo default to edobry/minsky-test, overridable via
# GITHUB_TEST_OWNER/GITHUB_TEST_REPO env vars). The built-in
# `secrets.GITHUB_TOKEN` is scoped ONLY to this repo (edobry/minsky)
# and cannot authenticate against a different repo, so a dedicated
# PAT secret is required: `INTEGRATION_TEST_GITHUB_TOKEN` (repo scope
# on the test repo). The `github-api` job below is skipped entirely
# when that secret is not configured (see the `check-secrets` gate
# job — GitHub Actions does not allow referencing `secrets.*` directly
# in a job `if:`, so presence is checked inside a job step and
# exposed as a job output instead).
#
# - postgres-pool-saturation.supabase.integration.test.ts
# Needs a live Supabase branch database (SUPABASE_INTEGRATION_BRANCH_URL).
# Per docs/persistence-configuration.md § Saturation Integration Tests,
# an always-on branch costs ~$10/mo; an ephemeral per-CI-run branch is
# cheap but requires build-out of Supabase-branch-lifecycle automation
# (create before, delete after) that is out of scope for mt#2634. The
# `supabase` job below is skipped when the secret is absent — wiring an
# always-on branch (and setting the secret) is left as an explicit,
# cost-aware operator decision, not something this workflow forces.
#
# - postgres-pool-saturation.testcontainer.integration.test.ts
# Needs a Docker daemon (present by default on GitHub-hosted
# ubuntu-latest runners — no extra provisioning needed) but is NOT run
# automatically on every PR: it pulls a Postgres image cold on every
# run (adds 1-3 minutes) and its own file header documents known
# Bun/testcontainers interaction quirks (see the file for detail) that
# make it a worse fit for unconditional per-PR execution than the
# other three files. It stays available on demand via the
# `run_docker_suite` workflow_dispatch input, or locally via
# `bun run test:integration:docker`.
#
# Manual fallback for everything in this file: `bun run test:integration`
# (credential-free + GitHub-API files, if creds are exported locally) or
# `bun run test:integration:docker` (the Docker suite).
on:
pull_request:
branches: [main]
paths:
- "tests/integration/**"
- "src/**"
- "packages/**"
- "package.json"
- "bun.lock"
- ".github/workflows/integration-tests.yml"
workflow_dispatch:
inputs:
run_docker_suite:
description: "Also run the Docker/testcontainer Postgres-saturation suite"
type: boolean
default: false
# Nightly, for the `docker-suite` job ONLY (mt#4347). Every other job in this
# file explicitly opts OUT of this trigger, so their behaviour is unchanged —
# see the `github.event_name != 'schedule'` guards below.
#
# Why: the Docker suite was reachable only by a deliberate workflow_dispatch
# with an explicit input, AND is continue-on-error, so a failure in it had no
# audience at all. It went 119 days with a red test nobody could see (mt#4347).
# A nightly run does not make it a merge blocker — it just means a failure
# lands somewhere with a timestamp.
#
# 07:17 UTC = 03:17 EDT, off-hours for the operator; offset from the hour
# because scheduled runs bunch up and get delayed at :00.
schedule:
- cron: "17 7 * * *"
env:
# Secondary hedge for the mt#3623 install flake: disables the streaming
# tarball path implicated upstream (oven-sh/bun#34821; fix PR #34827 is still
# unmerged, so no release carries a fix). Measured at 0 failures in 10
# cold-cache installs with the flag set, against a 3-in-10 unmitigated
# baseline -- mt#3623 records both runs and the method. The per-step retry
# loops remain the load-bearing mitigation.
# Remove both when a bun release carries the upstream fix.
BUN_FEATURE_FLAG_DISABLE_STREAMING_INSTALL: "1"
jobs:
# Presence-check gate. GitHub Actions does not support referencing the
# `secrets` context directly in a job-level `if:` (see
# https://github.com/actions/runner/issues/520) — the workaround is to
# read the secret into a step's `env:` inside a job, test THAT with
# `env.X != ''`, and publish the boolean as a job output for downstream
# jobs to consume via `needs.<job>.outputs.<name>`.
check-secrets:
name: Check optional integration-test secrets
# Opt OUT of the nightly schedule (mt#4347): that trigger exists for
# `docker-suite` alone. Skipping this job also skips `github-api` and
# `supabase`, which both `needs:` it — so the nightly run's blast radius
# stays exactly one job. Whether those should ALSO run nightly is a real
# question and deliberately not decided here.
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
outputs:
has_github_token: ${{ steps.check.outputs.has_github_token }}
has_supabase_url: ${{ steps.check.outputs.has_supabase_url }}
steps:
- name: Check secret presence
id: check
env:
GH_TOKEN_VALUE: ${{ secrets.INTEGRATION_TEST_GITHUB_TOKEN }}
SUPABASE_URL_VALUE: ${{ secrets.SUPABASE_INTEGRATION_BRANCH_URL }}
run: |
if [ -n "$GH_TOKEN_VALUE" ]; then
echo "has_github_token=true" >> "$GITHUB_OUTPUT"
else
echo "has_github_token=false" >> "$GITHUB_OUTPUT"
fi
if [ -n "$SUPABASE_URL_VALUE" ]; then
echo "has_supabase_url=true" >> "$GITHUB_OUTPUT"
else
echo "has_supabase_url=false" >> "$GITHUB_OUTPUT"
fi
credential-free:
name: Integration (credential-free)
# Opt OUT of the nightly schedule — see check-secrets above (mt#4347).
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.14"
- name: Cache Bun install
uses: actions/cache@v4
with:
path: $HOME/.bun/install/cache
key: bun-install-${{ runner.os }}-${{ hashFiles('bun.lock', 'services/reviewer/bun.lock') }}
restore-keys: |
bun-install-${{ runner.os }}-
- name: Install dependencies
run: for i in 1 2 3; do if bun install --frozen-lockfile --no-progress; then break; fi; if [ "$i" = 3 ]; then exit 1; fi; echo "bun install failed (mt#3623 tarball flake) - retry $i"; sleep 5; done
- name: Run credential-free integration test
env:
RUN_INTEGRATION_TESTS: "1"
run: |
bun test --preload ./tests/setup.ts --timeout=30000 \
tests/integration/session-edit-file-cursor-parity.integration.test.ts
github-api:
name: Integration (GitHub API — real minsky-test repo)
needs: check-secrets
if: needs.check-secrets.outputs.has_github_token == 'true'
runs-on: ubuntu-latest
# Real external API + a separate repo's issue state — treat as flaky
# by nature. Never a hard block (this workflow isn't a required check
# either way, but continue-on-error keeps the job's own summary honest
# about "flaky external dependency" vs. "real regression").
continue-on-error: true
steps:
- uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.14"
- name: Cache Bun install
uses: actions/cache@v4
with:
path: $HOME/.bun/install/cache
key: bun-install-${{ runner.os }}-${{ hashFiles('bun.lock', 'services/reviewer/bun.lock') }}
restore-keys: |
bun-install-${{ runner.os }}-
- name: Install dependencies
run: for i in 1 2 3; do if bun install --frozen-lockfile --no-progress; then break; fi; if [ "$i" = 3 ]; then exit 1; fi; echo "bun install failed (mt#3623 tarball flake) - retry $i"; sleep 5; done
- name: Run GitHub API integration tests
env:
RUN_INTEGRATION_TESTS: "1"
GITHUB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITHUB_TOKEN }}
run: |
bun test --preload ./tests/setup.ts --timeout=30000 \
tests/integration/github-api.integration.test.ts \
tests/integration/github-issues-backend.integration.test.ts
supabase:
name: Integration (Supabase branch DB)
needs: check-secrets
if: needs.check-secrets.outputs.has_supabase_url == 'true'
runs-on: ubuntu-latest
# Real external Postgres over the network — flaky by nature; never a
# hard block.
continue-on-error: true
steps:
- uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.14"
- name: Cache Bun install
uses: actions/cache@v4
with:
path: $HOME/.bun/install/cache
key: bun-install-${{ runner.os }}-${{ hashFiles('bun.lock', 'services/reviewer/bun.lock') }}
restore-keys: |
bun-install-${{ runner.os }}-
- name: Install dependencies
run: for i in 1 2 3; do if bun install --frozen-lockfile --no-progress; then break; fi; if [ "$i" = 3 ]; then exit 1; fi; echo "bun install failed (mt#3623 tarball flake) - retry $i"; sleep 5; done
- name: Run Supabase pool-saturation integration test
env:
RUN_INTEGRATION_TESTS: "1"
SUPABASE_INTEGRATION_BRANCH_URL: ${{ secrets.SUPABASE_INTEGRATION_BRANCH_URL }}
run: |
bun test --preload ./tests/setup.ts --timeout=60000 \
tests/integration/postgres-pool-saturation.supabase.integration.test.ts
# mt#3349 — integration tests that need a REAL Postgres but NOT Supabase.
#
# Distinct from the `supabase` job above on purpose. That one is gated on the
# SUPABASE_INTEGRATION_BRANCH_URL secret because its test asserts on the
# `XX000 "max clients reached"` shape SUPAVISOR emits under pooler exhaustion
# — the pooler is its system under test. (That secret is currently unset, so
# the job never runs; see mt#3356.)
#
# Tests here need only ordinary Postgres semantics, so they run against a
# service container: free, no secret, and on EVERY pull request. Mirrors
# `.github/workflows/cold-start-migrate.yml`, which established this pattern.
postgres-service:
name: Integration (Postgres service container)
# Opt OUT of the nightly schedule — see check-secrets above (mt#4347).
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
services:
postgres:
# pgvector image, matching cold-start-migrate.yml: the schema uses the
# `vector` type, so `persistence migrate` fails on vanilla postgres:16.
image: pgvector/pgvector:pg16
env:
POSTGRES_USER: minsky_test
POSTGRES_PASSWORD: minsky_test
POSTGRES_DB: minsky_integration
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-timeout 5s
--health-retries 10
env:
# Composed from parts so gitleaks doesn't flag a hardcoded URL with a
# password (same rationale as cold-start-migrate.yml). Test-only
# credentials with no reach outside this job's ephemeral container.
INTEGRATION_POSTGRES_URL: ${{ format('postgres://{0}:{1}@{2}:5432/{3}', 'minsky_test', 'minsky_test', 'localhost', 'minsky_integration') }}
steps:
- uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.14"
- name: Cache Bun install
uses: actions/cache@v4
with:
path: $HOME/.bun/install/cache
key: bun-install-${{ runner.os }}-${{ hashFiles('bun.lock', 'services/reviewer/bun.lock') }}
restore-keys: |
bun-install-${{ runner.os }}-
- name: Install dependencies
run: for i in 1 2 3; do if bun install --frozen-lockfile --no-progress; then break; fi; if [ "$i" = 3 ]; then exit 1; fi; echo "bun install failed (mt#3623 tarball flake) - retry $i"; sleep 5; done
- name: Apply migrations
# The tests write real rows, so the schema has to exist. Run from source
# rather than the bundle — this job is not testing bundle packaging
# (cold-start-migrate.yml owns that), so there is no reason to pay for a
# build here.
env:
MINSKY_PERSISTENCE_BACKEND: postgres
MINSKY_POSTGRES_URL: ${{ format('postgres://{0}:{1}@{2}:5432/{3}', 'minsky_test', 'minsky_test', 'localhost', 'minsky_integration') }}
run: bun src/cli.ts persistence migrate --execute
- name: Verify agent_transcripts table exists
# Belt-and-suspenders: if migrations silently no-op, the test below would
# fail with a confusing driver error instead of a clear one.
#
# Done in bun rather than `psql` (PR #2416 R2). `psql` IS present on
# today's ubuntu-latest — this step ran green with `count 0 (1 row)` in
# run 30564350726 before this change — but the runner image shipping a
# postgres client is not a contract, and this repo already has a real
# Postgres driver in the lockfile. `cold-start-migrate.yml` still uses
# `psql` for its equivalent assertion; the divergence is deliberate, and
# if that one ever breaks on a runner-image change, this is the pattern
# to move it to.
run: |
bun -e '
const postgres = (await import("postgres")).default;
const sql = postgres(process.env.INTEGRATION_POSTGRES_URL, { max: 1 });
try {
const [row] = await sql`SELECT count(*)::int AS n FROM agent_transcripts`;
console.log(`agent_transcripts present, ${row.n} rows`);
} catch (err) {
console.error("::error::agent_transcripts missing after migrate:", err.message);
process.exit(1);
} finally {
await sql.end({ timeout: 5 });
}
'
- name: Run Postgres-backed integration tests
env:
RUN_INTEGRATION_TESTS: "1"
run: |
bun test --preload ./tests/setup.ts --timeout=60000 \
tests/integration/transcript-metadata-fill-if-null.integration.test.ts \
tests/integration/postgres-client-bounded-socket.integration.test.ts
# HELD OUT until mt#3509 lands (mt#3482 / PR #2503 R1):
#
# tests/integration/transcript-attachment-parent-row.integration.test.ts
# bun scripts/verify-transcript-attachment-ingest.ts
#
# Both need `agent_transcript_attachments`, and the migrate step above
# does not create it: on a fresh database `persistence migrate --execute`
# plans 83 pending, reports "Applied 34", exits 0, and leaves that table
# (migration 0039) absent — reproduced locally and by this very job on
# PR #2503. Wiring them in now would make this job red for a reason that
# has nothing to do with what they assert. Re-wire them as part of
# mt#3509 — that task's acceptance tests include the fresh-migrate check
# these two depend on. Until then both remain runnable locally against a
# database that has the table; see the mt#3482 PR body for their output.
docker-suite:
name: Integration (Docker/testcontainer — nightly + manual)
# NOTE: `inputs.run_docker_suite == true` (boolean comparison) is correct
# here, NOT a bug. Since GitHub's June 2022 change, the `inputs` context
# (unlike the legacy `github.event.inputs` context, which stringifies
# everything) preserves the declared `type: boolean` as a real boolean —
# comparing against the string 'true' would be the actual mistake, since
# a real boolean never equals a string. See
# https://github.com/orgs/community/discussions/9343.
# Runs on the nightly schedule, or on a workflow_dispatch that explicitly
# opts in. The schedule half is mt#4347: `continue-on-error` plus
# dispatch-only reachability meant a failure here had no audience, and one
# sat red for 119 days. Nightly gives it one without making it a blocker.
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.run_docker_suite == true)
runs-on: ubuntu-latest
# Cold image pull + documented Bun/testcontainers quirks (see the test
# file's header) make this the most flake-prone suite; never a hard block.
#
# Keep continue-on-error even now that it runs nightly: the deliberate
# decision is that a flaky container suite must not gate merges. What
# changed is that the run happens at all — visibility, not enforcement.
continue-on-error: true
steps:
- uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.14"
- name: Cache Bun install
uses: actions/cache@v4
with:
path: $HOME/.bun/install/cache
key: bun-install-${{ runner.os }}-${{ hashFiles('bun.lock', 'services/reviewer/bun.lock') }}
restore-keys: |
bun-install-${{ runner.os }}-
- name: Install dependencies
run: for i in 1 2 3; do if bun install --frozen-lockfile --no-progress; then break; fi; if [ "$i" = 3 ]; then exit 1; fi; echo "bun install failed (mt#3623 tarball flake) - retry $i"; sleep 5; done
- name: Run Docker/testcontainer pool-saturation integration test
run: bun run test:integration:docker