Skip to content

ci: add pre-merge checks (tests, core conformance, built-image smoke) - #96

Merged
jmlago merged 2 commits into
mainfrom
ci/pre-merge-gate
Aug 7, 2026
Merged

ci: add pre-merge checks (tests, core conformance, built-image smoke)#96
jmlago merged 2 commits into
mainfrom
ci/pre-merge-gate

Conversation

@MuncleUscles

@MuncleUscles MuncleUscles commented Aug 6, 2026

Copy link
Copy Markdown
Member

This repo has no pre-merge checks. notify-ci.yml triggers on: push: branches: [main] — after the merge button — and only dispatches to the private CI repo. PR checks are CodeRabbit alone. Branch protection requires one review and required_status_checks returns 404, "not enabled".

So the 726-test suite gates nothing. Not theoretical: tests/test_antseed_node.py sat red for a month across #63, #68, #69, #70 and #71, each PR body noting "1 pre-existing failure", and all five merged — not by overriding a gate, but because there wasn't one.

Three jobs

  • tests — pytest against a UTF8 Postgres service, submodules checked out (the Lua core is a submodule; without it the engine-backed tests import nothing and the suite is quietly meaningless), node present for the sidecar suite. POSTGRES_INITDB_ARGS: --encoding=UTF8 is load-bearing: a SQL_ASCII cluster makes psycopg return TEXT as bytes, settings.reload() silently drops every override, and ~26 store-backed tests fail looking like application bugs.
  • core-tests — the policy core's unit + golden conformance vectors.
  • images — builds both images, then smokes the artifact.

Why images is the one that matters

A green suite proves the repo is self-consistent and says nothing about what COPY put in the image. That gap caused a production outage today: #94 added ids.js/queue.js and made control.js require them, Dockerfile.antseed named its COPY files individually and wasn't updated, the build succeeded, every test stayed green — and the sidecar's control server died at import in prod (Cannot find module './ids.js'), :8379 never bound, and every wallet endpoint 502'd. The only symptom was a Cloudflare error page on the dashboard Deposit button.

scripts/check_sidecar_modules.js resolves — never executes, so a CI run cannot start the control server or touch a wallet — each shipped module's local imports inside the built image. Verified in both directions locally:

repo dir                     ok — 9 shipped modules, all local imports resolve
old COPY list (no ids.js)    control.js imports ./ids.js — not present in the image
                             control.js imports ./queue.js — not present in the image   exit=1

This generalises past this one bug: missing COPY, wrong entrypoint, a dependency in requirements-dev but not requirements — all invisible to any test that runs against source.

Ordering. This is expected to be RED until #95 merges, because its images job correctly catches the ids.js regression still on main. That failure is the feature demonstrating itself. Merge #95 first.

Status of the checks here. GitHub Actions has been in a major outage since 15:22 UTC (incident "Investigating", critical). On the branch these commits came from: images passed twice including the module guard and the healthz boot; tests and core-tests passed on the prior commit and are unchanged by the commit that followed. The failures since are Set up job with no steps, no logs and no annotations — platform, not content.

For discussion, not assumed by this PR

  1. What does the private CI repo run post-merge? If it duplicates these jobs, point at it instead and I'll drop the overlap.
  2. Should these be required in branch protection? Without that this is documentation, not a gate. Fair warning that today is a preview of the cost: required checks mean platform incidents block merges. Correct trade, but worth making knowingly.

Summary by CodeRabbit

  • Tests

    • Added automated validation for Python, Lua, and image-related tests.
    • Added checks to confirm bundled sidecar modules resolve correctly.
  • Chores

    • Added continuous integration checks for pull requests and changes to the main branch.
    • Added validation that built images start successfully and respond to health checks.
    • Added PostgreSQL-backed integration checks for the router.

The repo had no pre-merge checks. notify-ci.yml fires on push to main — after
the merge button — and only dispatches to the private CI repo; PR checks were
CodeRabbit alone, and branch protection required a review but zero status
checks. tests/test_antseed_node.py was consequently red for a month across
#63/#68/#69/#70/#71 and every one merged.

Three jobs:
- tests: pytest against a UTF8 Postgres service (SQL_ASCII makes psycopg return
  TEXT as bytes, which silently voids settings overrides), submodules checked
  out so the engine-backed tests are real, node present for the sidecar suite.
- core-tests: the Lua policy core's unit + golden conformance vectors.
- images: build both images, then smoke the artifact.

The image smoke is the one that would have caught #95. A green suite proves the
REPO is consistent and says nothing about what COPY put in the image — which is
exactly how a control.js requiring ./ids.js shipped without it, died at import,
and took every wallet endpoint down with a 502. scripts/check_sidecar_modules.js
resolves (never executes) each shipped module's local imports inside the built
image; verified to reproduce that failure against the old COPY list.
The router opens a host-store pool at startup, so booting it without Postgres
proved only that it can fail to connect. Adds a postgres service to the images
job and runs the container with --network host so it can reach it (a bridged
container cannot see the runner's localhost).

Also drops the '|| true' after docker run: a container that fails to start must
fail the job immediately, not fall through to a curl loop that reports the same
thing sixty seconds later.
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Added a GitHub Actions workflow for Python, Lua, Docker image, sidecar module, and router health validation. Added a Node.js checker for relative imports in shipped sidecar modules.

Changes

CI and image validation

Layer / File(s) Summary
Sidecar module resolution checker
scripts/check_sidecar_modules.js
The checker scans shipped JavaScript modules, excludes tests, validates relative imports, and returns a nonzero status for invalid paths or missing files.
Python and Lua CI jobs
.github/workflows/ci.yml
The workflow runs on pull requests and pushes to main. Python tests use PostgreSQL 16, Python 3.12, and Node 22. Lua jobs run core unit and conformance tests.
Image integrity and router smoke tests
.github/workflows/ci.yml
The workflow builds both images, validates sidecar imports in the antseed image, and polls /healthz while the router runs against PostgreSQL.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CI as GitHub Actions
  participant Images as Docker images
  participant Checker as check_sidecar_modules.js
  participant Router as Router container
  participant PostgreSQL
  CI->>Images: Build main and antseed images
  CI->>Checker: Validate sidecar imports
  CI->>Router: Start built image
  Router->>PostgreSQL: Connect to PostgreSQL
  CI->>Router: Poll /healthz
  Router-->>CI: Return health status
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the CI workflow additions for tests, core conformance, and built-image smoke checks.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/pre-merge-gate

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 101-117: Update the “Router image boots and serves /healthz”
workflow step to start the required sidecar listener with DATABASE_URL before
launching the router, then wait for the sidecar health endpoint before polling
router health. Ensure the sidecar is started from the appropriate
image/configuration and remains available on the expected listener;
alternatively, replace the upstream-dependent /healthz assertion with a
process-level router readiness check if this job is intended to validate startup
only.

In `@scripts/check_sidecar_modules.js`:
- Line 22: Update the LOCAL_IMPORT pattern used by the sidecar import scanner to
match both ./ and ../ relative specifiers, including side-effect import
statements and dynamic import() calls, while preserving existing require() and
from imports.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 50d33185-c90e-4976-849b-8abaace6679a

📥 Commits

Reviewing files that changed from the base of the PR and between db2c533 and 0196b0c.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • scripts/check_sidecar_modules.js

Comment thread .github/workflows/ci.yml
Comment on lines +101 to +117
- name: Router image boots and serves /healthz
run: |
set -euo pipefail
docker run -d --name router-ci --network host \
-e DATABASE_URL='postgresql://postgres:test@localhost:5432/hoststore_ci' \
--entrypoint python unhardcoded:ci \
serve.py --config config.live.lua --metrics metrics.live.lua \
--default-profile default --host 0.0.0.0 --port 18080
for _ in $(seq 1 30); do
if curl -fsS localhost:18080/healthz >/dev/null 2>&1; then
echo "router image boots and serves /healthz"; exit 0
fi
sleep 2
done
echo "::error::router image did not become healthy within 60s"
docker logs router-ci 2>&1 | tail -60
exit 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Start the sidecar before polling router health.

auth_proxy.py:1030-1036 forwards /healthz to its upstream and returns HTTP 502 when that request fails. This job only runs unhardcoded-antseed:ci for the one-shot import checker at Lines 91-96. It never starts a sidecar listener. The poll therefore cannot return success even when the router starts correctly.

Start the sidecar with its required DATABASE_URL before starting the router, and wait for the sidecar health endpoint. If this test must validate only router process startup, replace the upstream-dependent /healthz check with a process-level readiness check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 101 - 117, Update the “Router image
boots and serves /healthz” workflow step to start the required sidecar listener
with DATABASE_URL before launching the router, then wait for the sidecar health
endpoint before polling router health. Ensure the sidecar is started from the
appropriate image/configuration and remains available on the expected listener;
alternatively, replace the upstream-dependent /healthz assertion with a
process-level router readiness check if this job is intended to validate startup
only.

const path = require("path");

const DIR = process.argv[2] || "/usr/local/lib/antseed";
const LOCAL_IMPORT = /(?:require\(\s*|from\s+)['"](\.\/[^'"]+)['"]/g;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

echo "== locate file =="
git ls-files | rg '^scripts/check_sidecar_modules\.js$|package\.json$|docker|Dockerfile' || true

echo "== file size and outline =="
wc -l scripts/check_sidecar_modules.js
ast-grep outline scripts/check_sidecar_modules.js || true

echo "== relevant file content =="
cat -n scripts/check_sidecar_modules.js | sed -n '1,180p'

echo "== package scripts/deps hints =="
if [ -f package.json ]; then jq '.scripts, .files, .devDependencies, .dependencies' package.json; fi

Repository: genlayerlabs/unhardcoded

Length of output: 1937


🏁 Script executed:

#!/bin/bash
set -eu

echo "== locate file =="
git ls-files | rg '^scripts/check_sidecar_modules\.js$|package\.json$|Dockerfile|docker|\.docker' || true

echo "== file size and outline =="
wc -l scripts/check_sidecar_modules.js
ast-grep outline scripts/check_sidecar_modules.js || true

echo "== relevant file content =="
cat -n scripts/check_sidecar_modules.js | sed -n '1,220p'

echo "== package scripts/files hints =="
if [ -f package.json ]; then jq '.scripts, .files, .devDependencies, .dependencies' package.json; fi

echo "== deterministic regex probe =="
node - <<'JS'
const LOCAL_IMPORT = /(?:require\(\s*|from\s+)['"](\.\/[^'"]+)['"]/g;
const samples = [
  'import sidecar from "./sidecar";',
  './sidecar; import "./sidecar";',
  'import("./sidecar")',
  'import("../sidecar")',
  'require("./sidecar")',
  'require("../sidecar")',
];
for (const s of samples) {
  const copy = s.slice();
  const matches = (() => { const m; const out = []; while ((m = LOCAL_IMPORT.exec(copy)) !== null) out.push(m[1]); return out; })();
  console.log(JSON.stringify({ input: s, matches }));
}
JS

Repository: genlayerlabs/unhardcoded

Length of output: 1937


Match all local import forms.

This scanner only catches ./ specifiers after require( or from, so it misses ../ specifiers, side-effect imports such as import "./sidecar", and dynamic imports such as import("./sidecar"). Include those forms so the check cannot pass while a local import target is missing from the shipped image.

Proposed fix
-const LOCAL_IMPORT = /(?: require\(\s*|from\s+)['"](\.\/[^'"]+)['"]/g;
+const LOCAL_IMPORT = /(?: require\s*\(\s*|import\s*\(\s*|from\s+|import\s*)['"](\.{1,2}\/[^'"]+)['"]/g;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check_sidecar_modules.js` at line 22, Update the LOCAL_IMPORT pattern
used by the sidecar import scanner to match both ./ and ../ relative specifiers,
including side-effect import statements and dynamic import() calls, while
preserving existing require() and from imports.

@jmlago
jmlago merged commit 1a38df9 into main Aug 7, 2026
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants