Skip to content

fix(billing): USD-only checkout presentment on /pricing (Option B) #480

fix(billing): USD-only checkout presentment on /pricing (Option B)

fix(billing): USD-only checkout presentment on /pricing (Option B) #480

Workflow file for this run

# Fork-level CI for HaraldeRoessler/moltrust-api.
#
# Intentionally lightweight — exercises the things that catch the
# common breakage modes without needing a running API or database:
#
# syntax — `python -m compileall` on every source dir.
# import-smoke — actually try `from app.main import app` with all
# required env vars set to placeholder values. This
# is the only job that catches startup-time
# RuntimeError raises (e.g. NONCE_SECRET unset).
# pytest-coll — `pytest --collect-only` over the in-repo tests.
# Catches ImportError / SyntaxError in test modules
# without needing the API + Postgres stack online.
# ruff — informational lint, never blocks merge.
# bandit — informational SAST, never blocks merge.
#
# Triggers on every push and PR. Separate file name (`fork-ci.yml`)
# from PR #14's proposed `ci.yml` so the two can co-exist if PR #14
# lands upstream later.
name: Fork CI
on:
push:
pull_request:
workflow_dispatch:
permissions:
contents: read
security-events: write
concurrency:
group: fork-ci-${{ github.ref }}
cancel-in-progress: true
jobs:
syntax:
name: syntax (compileall)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: byte-compile every source dir
run: |
python -m compileall -q app
for d in scripts agents agent operator moltbook monitor; do
[ -d "$d" ] && python -m compileall -q "$d" || true
done
# Also top-level scripts that aren't in a directory.
python -m compileall -q seed_ecosystem.py test_protocol_compliance.py test_sandbox.py 2>/dev/null || true
import-smoke:
name: import smoke test (with required env vars)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
cache-dependency-path: requirements.txt
- name: install requirements
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: import app.main with placeholder env
# The whole point of this job: catch any startup-time
# RuntimeError. The security hardening pass added fail-fast
# checks for NONCE_SECRET, MOLTRUST_API_KEYS, MOLTSTACK_DB_PW
# — if one of those is missing, the import explodes here
# rather than in production.
env:
MOLTRUST_API_KEYS: 'mt_ci_placeholder_key_does_not_authenticate'
NONCE_SECRET: 'ci-placeholder-nonce-secret'
MOLTSTACK_DB_PW: 'ci-placeholder-db-pw'
MOLTRUST_ADMIN_USERS: 'ci-admin:admin:$2b$12$ciplaceholderhashthatwillnevermatchanypassword.ciplaceholderhash'
MOLTRUST_ENV: 'ci'
run: |
python -c "from app.main import app; print('app.main imported OK, FastAPI app:', type(app).__name__)"
pytest-collect:
name: pytest --collect-only
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
cache-dependency-path: requirements.txt
- name: install requirements + pytest
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-asyncio httpx
- name: collect tests
# Collection imports test modules without running them. Catches
# ImportError / SyntaxError / fixture-parse errors without
# needing the API stack online.
env:
MOLTRUST_API_KEYS: 'mt_ci_placeholder_key'
NONCE_SECRET: 'ci-placeholder'
MOLTSTACK_DB_PW: 'ci-placeholder'
run: |
pytest --collect-only -q tests/ test_*.py 2>&1 | tail -40
pytest-run:
name: pytest (credit middleware)
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: moltstack_sandbox
POSTGRES_USER: moltstack
POSTGRES_PASSWORD: ci-pw
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
cache-dependency-path: requirements.txt
- name: install requirements + pytest
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-asyncio httpx
- name: init test DB schema
# init_db.sql uses IF NOT EXISTS — idempotent, safe to run
# repeatedly. Uses plain text password via PGPASSWORD — safe
# because this runs in an ephemeral CI container.
env:
PGPASSWORD: ci-pw
PGUSER: moltstack
run: |
psql -h localhost -U moltstack -d moltstack_sandbox -f init_db.sql
# CI schema alignment: columns/tables present on live DB but missing from init_db.sql
psql -h localhost -U moltstack -d moltstack_sandbox -c "ALTER TABLE api_keys ADD COLUMN IF NOT EXISTS email TEXT;"
# request_log is referenced by middleware during test runs. Table not in
# init_db.sql — add minimal schema so INSERTs don't flood the log.
psql -h localhost -U moltstack -d moltstack_sandbox -c "CREATE TABLE IF NOT EXISTS request_log (id BIGSERIAL PRIMARY KEY, endpoint TEXT, method TEXT, status_code INT, ip TEXT, user_agent TEXT, response_ms FLOAT, source TEXT, ip_org TEXT, ip_country TEXT, created_at TIMESTAMPTZ DEFAULT NOW());"
# PR #81 wired record_spend_event() into the credit-deduct path; that
# call needs agents.operator_did + agent_budget_caps + budget_spend_events.
# 009 has a FK on gate_events(id) so 008 must run first. Both migrations
# use IF NOT EXISTS / ADD COLUMN IF NOT EXISTS — idempotent.
psql -h localhost -U moltstack -d moltstack_sandbox -v ON_ERROR_STOP=1 -f app/migrations/008_gate_events.sql
psql -h localhost -U moltstack -d moltstack_sandbox -v ON_ERROR_STOP=1 -f app/migrations/009_agent_budget_caps.sql
# 010: D3 aae_envelopes store (component 1). Idempotent (IF NOT EXISTS /
# OR REPLACE / DROP-before-CREATE TRIGGER), additiv, kein ALTER an Bestand.
psql -h localhost -U moltstack -d moltstack_sandbox -v ON_ERROR_STOP=1 -f app/migrations/010_aae_envelopes.sql
# 011: D3 aae_evaluations (component 2 evaluator store + signed audit trail).
# Append-only; FK aae_ref -> 010 muss zuerst laufen. Idempotent.
psql -h localhost -U moltstack -d moltstack_sandbox -v ON_ERROR_STOP=1 -f app/migrations/011_aae_evaluations.sql
# 012: drop FK aae_evaluations.aae_ref (FK-lock vs immutable-store REVOKE conflict);
# sha256-CHECK stays. Idempotent.
psql -h localhost -U moltstack -d moltstack_sandbox -v ON_ERROR_STOP=1 -f app/migrations/012_drop_aae_eval_fk.sql
# 013: D-1 issuer_trust_tier column on aae_envelopes (additive). Idempotent.
psql -h localhost -U moltstack -d moltstack_sandbox -v ON_ERROR_STOP=1 -f app/migrations/013_issuer_trust_tier.sql
# 2026-06-15: insufficient_credit_events — caller_did-on-402 metric (dashboard_overview reads it; additive, IF NOT EXISTS).
psql -h localhost -U moltstack -d moltstack_sandbox -v ON_ERROR_STOP=1 -f app/migrations/2026-06-15_insufficient_credit_events.sql
- name: run credit middleware tests
# These tests require a live Postgres because credit_middleware
# connects via db_pool (asyncpg). The service container provides
# a fresh Postgres on localhost:5432.
# MOLTRUST_ADMIN_USERS needs a valid bcrypt hash for the format
# check — the placeholder below passes the regex but won't match
# any real password.
env:
MOLTRUST_API_KEYS: 'mt_ci_placeholder_key'
NONCE_SECRET: 'ci-placeholder-nonce-secret'
MOLTSTACK_DB_PW: 'ci-pw'
DB_NAME: moltstack_sandbox
DB_HOST: localhost
MOLTRUST_ADMIN_USERS: 'ci-admin:admin:$2b$12$ciplaceholderhashforadminusersformatcheck'
MOLTRUST_ENV: 'ci'
CREDITS_ENABLED: 'true'
run: |
python -m pytest tests/test_credit_middleware.py -v --tb=short 2>&1
ruff:
name: ruff (informational)
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install ruff
- name: ruff check
# Informational only — don't block merge on lint noise. Surfaces
# in the workflow log so we can clean up over time.
run: ruff check app/ agents/ scripts/ monitor/ --output-format=concise || true
bandit:
name: bandit SAST (informational)
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install bandit
- name: bandit scan
# -ll = report MEDIUM+ severity only (filters out the noise).
# Findings here are a useful belt-and-suspenders signal — most
# are already either fixed in this PR or dismissed in CodeQL.
run: bandit -r app/ agents/ scripts/ monitor/ -ll || true