[E00-S05-T01] Implement CI quality baseline (required PR stages) #405

Merged
kpcto merged 7 commits from feature/187 into main 2026-08-31 00:07:03 +00:00
Showing only changes of commit 89fe0b52e6 - Show all commits
+187 -287
View File
@@ -5,9 +5,34 @@ on:
push:
branches: [main]
# E00-S05-T01 — CI quality baseline (required PR stages).
#
# Every pull request runs the required quality stages in order, each gated on
# the previous stage through `needs`:
#
# 1. frozen-install — the committed lockfile installs cleanly
# 2. typecheck — every workspace package passes `tsc --noEmit`
# 3. formatting-lint — the dependency-free formatting/lint policy (`pnpm lint`)
# 4. unit — deterministic unit suites (health, config, env example…)
# 5. architecture — static workspace/container structure and policy suites
# 6. postgres-integration — PostgreSQL adapter suites (docker-gated real-stack
# probes run where a Docker daemon is available and
# skip cleanly otherwise)
# 7. build-apps — builds the workspace applications (apps/*: server
# today, admin when E06-S01 lands) and verifies the
# compiled artifact
#
# The stage order, the `needs` chain and the tests/ coverage are locked in by
# tests/ci-stages.test.mjs (architecture stage). Container/Compose smoke on
# main/release branches and the Docker Compose baseline stack (E00-S02) stay
# out of scope for this stage list.
jobs:
# Stage 1 — frozen install (E00-S05-T01). Runs before every later stage: the
# committed lockfile must install cleanly and be up to date with the
# manifests before any stage proceeds.
frozen-install:
name: Frozen lockfile install
name: Stage 1 — Frozen lockfile install (E00-S05-T01)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -22,11 +47,10 @@ jobs:
- name: Verify workspace groups
run: pnpm -r list --depth -1
# E00-S02-T08: the static assertions of tests/secrets-not-embedded.test.mjs
# gate every PR (the docker-gated layer-scan probe inside the same file runs
# where a Docker daemon is available and skips cleanly otherwise).
secrets-not-embedded:
name: Secrets not embedded (E00-S02-T08)
# Stage 2 — typecheck (E00-S05-T01).
typecheck:
name: Stage 2 — Typecheck (E00-S05-T01)
needs: frozen-install
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -34,290 +58,78 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Run secrets-not-embedded test suite
- name: Enable pnpm (corepack, pinned to 11.23.0 via packageManager)
run: corepack enable
- name: Install dependencies (frozen lockfile)
run: pnpm install --frozen-lockfile
- name: Typecheck every workspace package
run: pnpm typecheck
# Stage 3 — formatting/lint policy (E00-S05-T01). `pnpm lint` runs the
# dependency-free formatting-policy suite (tests/formatting-policy.test.mjs):
# LF line endings, no BOM, no trailing whitespace, no tab indentation, final
# newline, and valid JSON with 2-space indentation and no duplicate keys.
formatting-lint:
name: Stage 3 — Formatting/lint policy (E00-S05-T01)
needs: typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Enable pnpm (corepack, pinned to 11.23.0 via packageManager)
run: corepack enable
- name: Install dependencies (frozen lockfile)
run: pnpm install --frozen-lockfile
- name: Run the formatting/lint policy
run: pnpm lint
# Stage 4 — unit tests (E00-S05-T01). Deterministic suites that gate every
# PR without external services: the app health endpoint, the secrets scan
# and the configuration service suites (schema, startup error, log
# redaction, env adapter, .env.example). The config suites boot the
# committed server, so the config and database-postgres packages are built
# first.
unit:
name: Stage 4 — Unit tests (E00-S05-T01)
needs: formatting-lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Enable pnpm (corepack, pinned to 11.23.0 via packageManager)
run: corepack enable
- name: Install dependencies (frozen lockfile)
run: pnpm install --frozen-lockfile
- name: Build the config and database-postgres packages (the probes boot the committed server which imports them)
run: pnpm --filter @personal-blog/config build && pnpm --filter @personal-blog/database-postgres build
- name: Run the health-endpoint unit suite
run: node --test tests/health-endpoint.test.mjs
- name: Run the secrets-not-embedded unit suite
run: node --test tests/secrets-not-embedded.test.mjs
# E00-S03-T02: the static assertions of tests/database-postgres-imports.test.mjs
# gate every PR — the scan proves pg/Kysely imports live only in
# packages/database-postgres and the mutation probes prove the scan catches
# a driver import injected into any other package.
database-postgres-imports:
name: Database-postgres import isolation (E00-S03-T02)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Run database-postgres import isolation suite
run: node --test tests/database-postgres-imports.test.mjs
# E00-S03-T03: the static assertions of tests/database-postgres-ledger.test.mjs
# gate every PR — the suite locks in the migration ledger (schema_migrations
# table DDL, idempotent parameterized record, driver-boundary re-export)
# with mutation probes, and the docker-gated real-stack probe (migrate an
# empty database and confirm the ledger exists) runs where a Docker daemon
# is available and skips cleanly otherwise. The job installs the frozen
# workspace because the real-stack probe executes the committed ledger
# module from the host (it imports `pg` through the package's own links).
database-postgres-ledger:
name: Migration ledger (E00-S03-T03)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Enable pnpm (corepack, pinned to 11.23.0 via packageManager)
run: corepack enable
- name: Install dependencies (frozen lockfile)
run: pnpm install --frozen-lockfile
- name: Run migration ledger test suite
run: node --test tests/database-postgres-ledger.test.mjs
# E00-S03-T04: the static assertions of tests/database-postgres-lock.test.mjs
# gate every PR — the suite locks in the migration advisory lock (session-
# scoped pg_advisory_lock/pg_try_advisory_lock over a stable keyed hash on a
# dedicated connection, re-entrant-safe in-flight acquire so concurrent
# acquire() calls share one connection, driver-boundary re-export) with
# mutation probes, and the docker-gated real-stack concurrent probe (a
# second runner waits or fails while the first holds the lock; concurrent
# acquire() checks out exactly one connection; the lock releases when the
# holding session ends) runs where a Docker daemon is available and skips
# cleanly otherwise. The job installs the frozen workspace because the
# real-stack probe executes the committed lock module from the host (it
# imports `pg` through the package's own links).
database-postgres-lock:
name: Migration advisory lock (E00-S03-T04)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Enable pnpm (corepack, pinned to 11.23.0 via packageManager)
run: corepack enable
- name: Install dependencies (frozen lockfile)
run: pnpm install --frozen-lockfile
- name: Run migration advisory lock test suite
run: node --test tests/database-postgres-lock.test.mjs
# E00-S03-T05: the static assertions of tests/database-postgres-diagnostic.test.mjs
# gate every PR — the suite locks in the migration failure diagnostic (a
# structured MigrationFailedError whose diagnostic identifies the failing
# migration, the failure phase, the underlying cause, and the applied/pending
# ledger state, serializable via toJSON) with mutation probes, and a
# deterministic stub-pool behavioral probe (intentionally failing migration
# fixture -> structured diagnostic naming the failing migration) runs on
# Node 24; the docker-gated real-stack probe (the issue's test plan: "run an
# intentionally failing migration fixture and confirm the diagnostic") runs
# where a Docker daemon is available and skips cleanly otherwise. The job
# installs the frozen workspace because the probes execute the committed
# runner module from the host (it imports `pg` through the package's own
# links).
database-postgres-diagnostic:
name: Migration failure diagnostic (E00-S03-T05)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Enable pnpm (corepack, pinned to 11.23.0 via packageManager)
run: corepack enable
- name: Install dependencies (frozen lockfile)
run: pnpm install --frozen-lockfile
- name: Run migration failure diagnostic test suite
run: node --test tests/database-postgres-diagnostic.test.mjs
# E00-S03-T06: the static assertions of tests/app-readiness.test.mjs gate
# every PR — the suite locks in the readiness gate (the app answers
# GET /health with 503 {"status":"not ready"} until the startup migration
# run completes, then 200 {"status":"ok"}) with mutation probes, the
# deterministic probes (boot the committed server: no DATABASE_URL ->
# ready immediately; unreachable DATABASE_URL -> stays not-ready) run on
# Node 24, and the docker-gated real-stack probe (the issue's test plan:
# "start with pending migrations and confirm readiness waits" — the app's
# migration run is blocked behind a held ACCESS EXCLUSIVE lock on the
# migration ledger, /health stays not-ready, then flips ready once the lock
# releases) runs where a Docker daemon is available and skips cleanly
# otherwise. The job installs the frozen workspace and builds the config
# and database-postgres packages because the probes boot the committed
# server from the host (it imports @personal-blog/config and
# @personal-blog/database-postgres through the packages' own links; the
# required EPPP_SESSION_SECRET is provided by the probe's boot env).
app-readiness:
name: App readiness after migrations (E00-S03-T06)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Enable pnpm (corepack, pinned to 11.23.0 via packageManager)
run: corepack enable
- name: Install dependencies (frozen lockfile)
run: pnpm install --frozen-lockfile
- name: Build the config and database-postgres packages (the probes boot the committed server which imports them)
run: pnpm --filter @personal-blog/config build && pnpm --filter @personal-blog/database-postgres build
- name: Run app readiness test suite
run: node --test tests/app-readiness.test.mjs
# E00-S04-T02: the static assertions of tests/config-startup-error.test.mjs
# gate every PR — the suite locks in the field-specific startup error (a
# missing required setting fails startup with an error naming the missing
# field: packages/config's MissingRequiredSettingError/assertValidConfig,
# wired into the committed server before it binds) with mutation probes, and
# the deterministic probes execute the issue's test plan ("start with a
# missing required field and confirm the error names it"): booting the
# committed server without EPPP_SESSION_SECRET exits non-zero naming
# sessionSecret, while a valid secret boots to GET /health 200. The job
# installs the frozen workspace and builds the config and database-postgres
# packages because the probes boot the committed server which imports them.
config-startup-error:
name: Field-specific startup errors (E00-S04-T02)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Enable pnpm (corepack, pinned to 11.23.0 via packageManager)
run: corepack enable
- name: Install dependencies (frozen lockfile)
run: pnpm install --frozen-lockfile
- name: Build the config and database-postgres packages (the probes boot the committed server which imports them)
run: pnpm --filter @personal-blog/config build && pnpm --filter @personal-blog/database-postgres build
- name: Run config startup error test suite
run: node --test tests/config-startup-error.test.mjs
# E00-S04-T03: the static assertions of tests/config-log-redaction.test.mjs
# gate every PR — the suite locks in automatic secret redaction from logs
# (packages/config's redactConfig/redactText + the server's redacting
# logger: every log line is scrubbed of the config's secret values) with
# mutation probes, and the deterministic probes execute the issue's test
# plan ("log configuration and confirm secret values are redacted"):
# booting the committed server logs its resolved configuration with the
# secret values replaced by [REDACTED], and no secret value appears in the
# log output. The job installs the frozen workspace and builds the config
# and database-postgres packages because the probes boot the committed
# server which imports them.
config-log-redaction:
name: Secret redaction from logs (E00-S04-T03)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Enable pnpm (corepack, pinned to 11.23.0 via packageManager)
run: corepack enable
- name: Install dependencies (frozen lockfile)
run: pnpm install --frozen-lockfile
- name: Build the config and database-postgres packages (the probes boot the committed server which imports them)
run: pnpm --filter @personal-blog/config build && pnpm --filter @personal-blog/database-postgres build
- name: Run config log redaction test suite
run: node --test tests/config-log-redaction.test.mjs
# E00-S04-T04: the static assertions of tests/config-env-adapter.test.mjs
# gate every PR — the suite locks in the environment adapter (packages/config
# is the single owner of process.env reads; the server and every other module
# read no process.env, all settings flow through loadConfigFromEnv into the
# validated config) with a comment-stripped workspace scan, mutation probes
# (injecting a direct process.env read into any other module fails the scan),
# a deterministic boundary probe (full env mapping, defaults, bad-PORT
# fallback, HOST validated as hostname/IP with invalid values throwing a
# field-specific startup error, missing required secret ->
# MissingRequiredSettingError) and server-boot probes (a PORT/HOST override
# shows up in the resolved configuration; HOST=127.0.0.1 binds loopback only
# and the startup log reflects the actual bind; an invalid HOST fails startup
# naming the field without echoing the raw value; a missing required secret
# still fails startup). The job installs the frozen workspace and builds the
# config and database-postgres packages because the probes boot the committed
# server which imports them.
config-env-adapter:
name: Env adapter owns process.env (E00-S04-T04)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Enable pnpm (corepack, pinned to 11.23.0 via packageManager)
run: corepack enable
- name: Install dependencies (frozen lockfile)
run: pnpm install --frozen-lockfile
- name: Build the config and database-postgres packages (the probes boot the committed server which imports them)
run: pnpm --filter @personal-blog/config build && pnpm --filter @personal-blog/database-postgres build
- name: Run config env adapter test suite
run: node --test tests/config-env-adapter.test.mjs
# E00-S04-T01: the static assertions of tests/config-schema.test.mjs gate
# every PR — the suite locks in the TypeBox/Ajv configuration schema
# (packages/config, golden-tuple pins @sinclair/typebox@0.34.52 +
# ajv@8.20.0) with mutation probes, and the deterministic probe executes
# the issue's test plan ("validate a full config against the TypeBox/Ajv
# schema") against the committed schema through Ajv. The job installs the
# frozen workspace and builds the config package because the probe also
# exercises the compiled package boundary (@personal-blog/config) exactly
# as the later configuration adapter will consume it.
config-schema:
name: TypeBox/Ajv config schema (E00-S04-T01)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Enable pnpm (corepack, pinned to 11.23.0 via packageManager)
run: corepack enable
- name: Install dependencies (frozen lockfile)
run: pnpm install --frozen-lockfile
- name: Build the config package (the probe exercises the compiled package boundary)
run: pnpm --filter @personal-blog/config build
- name: Run config schema test suite
- name: Run the config-schema unit suite
run: node --test tests/config-schema.test.mjs
# E00-S04-T05: the static assertions of tests/env-example.test.mjs gate every
# PR — the suite locks in the committed `.env.example` template: it exists at
# the repo root, is un-ignored in .gitignore (real `.env` files stay ignored
# while the example is tracked), documents every configuration environment
# source (HOST/PORT/DATABASE_URL/EPPP_SESSION_SECRET), and contains
# placeholder values only — no credential URI, no long secret-looking value,
# and no compose default credential — with mutation probes proving the
# assertions are non-vacuous. It also locks the fail-closed EPPP_SESSION_SECRET
# placeholder (shorter than the schema's 32-character minimum), builds the
# secret-shaped probe at runtime so the branch stays gitleaks-clean, and
# masks raw values in assertion messages. The test needs no dependencies, so
# the job only installs Node.
env-example:
name: .env.example placeholders only (E00-S04-T05)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Run .env.example test suite
- name: Run the config-startup-error unit suite
run: node --test tests/config-startup-error.test.mjs
- name: Run the config-log-redaction unit suite
run: node --test tests/config-log-redaction.test.mjs
- name: Run the config-env-adapter unit suite
run: node --test tests/config-env-adapter.test.mjs
- name: Run the env-example unit suite
run: node --test tests/env-example.test.mjs
# E00-S03-T01: the static assertions of tests/compose-config.test.mjs (db
# image pinned to postgres:18.6-bookworm, health gate, volume persistence,
# build platforms) gate every PR (the docker-gated real-stack probes inside
# the same file run where a Docker daemon is available and skip cleanly
# otherwise).
compose-config:
name: Compose config (E00-S03-T01)
# Stage 5 — architecture tests (E00-S05-T01). Static structure and policy
# suites: dependency boundaries, workspace layout/configuration, strict
# TypeScript base, engine/TypeScript pins, root commands, frozen-install
# clean clone, container definition structure, and the CI baseline itself.
architecture:
name: Stage 5 — Architecture tests (E00-S05-T01)
needs: unit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -325,5 +137,93 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Run compose-config test suite
- name: Enable pnpm (corepack, pinned to 11.23.0 via packageManager)
run: corepack enable
- name: Install dependencies (frozen lockfile)
run: pnpm install --frozen-lockfile
- name: Run the architecture-import suite
run: node --test tests/architecture-import.test.mjs
- name: Run the no-core-extension-imports suite
run: node --test tests/no-core-extension-imports.test.mjs
- name: Run the workspace-layout suite
run: node --test tests/workspace-layout.test.mjs
- name: Run the workspace-config suite
run: node --test tests/workspace-config.test.mjs
- name: Run the strict-tsconfig suite
run: node --test tests/strict-tsconfig.test.mjs
- name: Run the typescript-pin suite
run: node --test tests/typescript-pin.test.mjs
- name: Run the node-engine suite
run: node --test tests/node-engine.test.mjs
- name: Run the frozen-install suite
run: node --test tests/frozen-install.test.mjs
- name: Run the root-commands suite
run: node --test tests/root-commands.test.mjs
- name: Run the compose-config suite
run: node --test tests/compose-config.test.mjs
- name: Run the build-targets suite
run: node --test tests/build-targets.test.mjs
- name: Run the non-root-user suite
run: node --test tests/non-root-user.test.mjs
- name: Run the readonly-rootfs suite
run: node --test tests/readonly-rootfs.test.mjs
- name: Run the database-postgres-imports suite
run: node --test tests/database-postgres-imports.test.mjs
- name: Run the ci-stages baseline suite
run: node --test tests/ci-stages.test.mjs
# Stage 6 — PostgreSQL integration tests (E00-S05-T01). The PostgreSQL
# adapter suites (migration ledger, advisory lock, failure diagnostic) and
# the app readiness suite: their docker-gated real-stack probes (migrate an
# empty database, hold/release the advisory lock, readiness waits on the
# startup migration run) run where a Docker daemon is available and skip
# cleanly otherwise; the static and deterministic probes always gate. The
# app-readiness probes boot the committed server, so the config and
# database-postgres packages are built first.
postgres-integration:
name: Stage 6 — PostgreSQL integration tests (E00-S05-T01)
needs: architecture
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Enable pnpm (corepack, pinned to 11.23.0 via packageManager)
run: corepack enable
- name: Install dependencies (frozen lockfile)
run: pnpm install --frozen-lockfile
- name: Build the config and database-postgres packages (the probes boot the committed server which imports them)
run: pnpm --filter @personal-blog/config build && pnpm --filter @personal-blog/database-postgres build
- name: Run the database-postgres-ledger suite
run: node --test tests/database-postgres-ledger.test.mjs
- name: Run the database-postgres-lock suite
run: node --test tests/database-postgres-lock.test.mjs
- name: Run the database-postgres-diagnostic suite
run: node --test tests/database-postgres-diagnostic.test.mjs
- name: Run the app-readiness suite
run: node --test tests/app-readiness.test.mjs
# Stage 7 — build the applications (E00-S05-T01). Builds every workspace
# application under apps/ (apps/server today; apps/admin when E06-S01 lands
# — the pnpm apps-group glob picks it up automatically) and verifies the
# compiled server artifact.
build-apps:
name: Stage 7 — Build the admin and server applications (E00-S05-T01)
needs: postgres-integration
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Node.js 24
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Enable pnpm (corepack, pinned to 11.23.0 via packageManager)
run: corepack enable
- name: Install dependencies (frozen lockfile)
run: pnpm install --frozen-lockfile
- name: Build the workspace applications (apps/* — server today, admin when E06-S01 lands)
run: pnpm --filter "./apps/**" run build
- name: Verify the compiled server application artifact
run: test -f apps/server/dist/index.js