Skip to content

Pre-push checklist

Run these gates before every git push on a module build. Each one is fast to check locally and catches a class of failure that has burned CI cycles and produced wiki-sync churn across multiple modules in practice.

The gates below were identified after MOD-118 required 5 push cycles to reach Built+Deployed — each cycle due to a failure category in this list. Catching them locally saves ~30–60 min of CI compute and avoids the handoff collisions that failed pipelines generate.

This checklist is separate from the acceptance criteria (which covers what must be true when the module is complete) and the module build prompt (which covers what to read before writing code). Run this checklist immediately before pushing.


Gate 1 — Lockfile drift on a new workspace package

What fails: pnpm install --frozen-lockfile errors with ERR_PNPM_OUTDATED_LOCKFILE in migrate-all or any CI job that runs install. Happens when a new package.json is added to the workspace without re-running pnpm install at the repo root.

Check:

git diff --name-only main HEAD | grep -q "package\.json$" && \
  pnpm install --no-frozen-lockfile && \
  git diff --quiet pnpm-lock.yaml || \
  echo "❌ pnpm-lock.yaml has uncommitted changes — stage and commit before pushing"

If the check prints the error line, stage the updated lockfile and amend or add a commit before pushing.

Also affects: any commit that adds a shared package (e.g. @bank-core/idempotency, @bank-core/mod-NNN-contracts) — the lockfile must be updated in the same push.


Gate 2 — changes: rule scope on lockfile-only or infra-only commits

What fails: GitLab creates no pipeline at all. The push sits on main silently. If the intent was to re-fire a module's deploy after a previously-failed pipeline, the deploy never runs.

Why it happens: Module-deploy changes: rules require MOD-NNN-.../** or .gitlab/ci/mod-NNN.gitlab-ci.yml. A repo-root file change (lockfile fix, shared infra update, workspace config) does not match any per-module rule and triggers nothing.

Check:

# Does this push touch at least one file inside the target module's directory?
git diff --name-only main HEAD | grep -q "^MOD-${MODULE_ID}-" || \
  echo "❌ No module-dir file changed — GitLab may not trigger the deploy job"

Fix: if the push's only purpose is to re-fire a specific module deploy, touch any file inside the module directory in the same commit. A comment addition in any source file is sufficient.


Gate 3 — Cleanup + immutability collision in test fixtures

What fails: afterAll or afterEach in integration tests panics with a foreign key violation when attempting to DELETE from a parent table that has a Cat 1 immutable child table (ADR-048) referencing it.

update or delete on table [parent] violates foreign key constraint
[child]_[parent_id]_fkey

Why it happens: ADR-048 Cat 1 immutable tables carry a BEFORE UPDATE OR DELETE OR TRUNCATE trigger that rejects all mutations. A cleanupFoo() helper that does DELETE FROM parent will fail once a child row exists, because the child's FK constraint prevents the parent delete.

Check: For any module with a Cat 1 immutable table that has a FK into a parent in the same schema — confirm the test fixture cleanup does NOT delete the parent directly.

Fix options: - Use randomUUID() per test run and accept dev-DB leakage (preferred for immutable tables — the leakage is harmless and isolated per environment) - Use a savepoint / rollback pattern instead of explicit deletes - Clean child rows first if the child is not itself immutable, then delete the parent


Gate 4 — Reserved-concurrency budget on the dev account

What fails: sst deploy errors with:

InvalidParameterValueException: Specified ReservedConcurrentExecutions for
function decreases account's UnreservedConcurrentExecution below its minimum
value of [100].

Why it happens: The AWS dev account has a per-region unreserved concurrency floor of 100. Each Lambda with reservedConcurrentExecutions set consumes from the account pool. When multiple modules each allocate reserved concurrency, the pool depletes.

Rule: New modules must not set reservedConcurrentExecutions in their Lambda config. Leave it undefined and rely on unreserved concurrency for dev/staging. Tune reserved concurrency for production only, after a capacity review that sums existing reservations across all deployed modules.

Check:

grep -r "reservedConcurrentExecutions" MOD-${MODULE_ID}-*/infra/ && \
  echo "❌ reservedConcurrentExecutions is set — remove for dev; only set in prod after capacity review" || \
  echo "✓ no reserved concurrency set"

Gate 5 — Column-name typos in integration test SQL

What fails: Integration tests error at runtime with:

error: column "column_name" does not exist

Why it happens: tsc --noEmit type-checks TypeScript but does not validate raw SQL string literals inside template expressions. A column referenced in an ORDER BY, WHERE, or SELECT can be misspelled without the type checker catching it.

Check: Before pushing, cross-reference any raw SQL in test files against the migration that created the table.

# List column references in test SQL strings and compare against the V### migration
grep -rh "ORDER BY\|WHERE\|SELECT" MOD-${MODULE_ID}-*/tests/ --include="*.ts" | \
  grep -oP '(?<=\b(ORDER BY|WHERE|AND|OR) )\w+' | sort -u

Then verify each listed name appears in the corresponding migrations/V*.sql file.

A lightweight permanent fix: use a thin query-builder helper that introspects the schema at first call, making typos a type error rather than a runtime failure.


Running the full set

Before any push on a new or in-progress module:

MODULE_ID="NNN"   # e.g. 135

# Gate 1 — lockfile
git diff --name-only main HEAD | grep -q "package\.json$" && \
  pnpm install --no-frozen-lockfile && \
  git diff --quiet pnpm-lock.yaml || echo "❌ Gate 1: lockfile drift"

# Gate 2 — changes: rule scope
git diff --name-only main HEAD | grep -q "^MOD-${MODULE_ID}-" || \
  echo "⚠️  Gate 2: no module-dir file changed — check deploy trigger"

# Gate 3 — immutability / fixture cleanup
# (manual: review test helpers for DELETE on Cat 1 parent tables)

# Gate 4 — reserved concurrency
grep -r "reservedConcurrentExecutions" MOD-${MODULE_ID}-*/infra/ 2>/dev/null && \
  echo "❌ Gate 4: reserved concurrency set" || echo "✓ Gate 4: ok"

# Gate 5 — column name typos
# (manual: cross-check SQL literals in tests against V### migrations)

Extending this list

When a new recurring failure category is identified in CI, add it here with: - What the error looks like (exact message or symptom) - Why it happens (root cause, one paragraph) - The pre-push check command or manual step - Any permanent fix that would make the gate unnecessary

File the addition as a wiki edit — no ADR required.