Skip to content

feat(jobs): recurring cause-agnostic re-enrichment sweep for terminal enriched_no_match rows #254

feat(jobs): recurring cause-agnostic re-enrichment sweep for terminal enriched_no_match rows

feat(jobs): recurring cause-agnostic re-enrichment sweep for terminal enriched_no_match rows #254

name: Schema shape probe self-test
# Fail-loudly regression detector for `scripts/schema-shape-report.mjs`.
#
# This workflow used to own the PR comment as well. It no longer does: #1982
# folded the actual data probe into the `migrate-dryrun` job in test.yml, which
# already restores the latest automated prod snapshot into an ephemeral sandbox
# and therefore needs no standing staging credential. `STAGING_DATABASE_URL_RO`
# — the secret this workflow probed for two months — was never provisioned, so
# the report never produced a single row count. Exactly one thing now owns the
# `<!-- wxyc-schema-shape-report -->` marker, and it is test.yml.
#
# What is left here is the half that must stay loud. The probe inside
# migrate-dryrun is deliberately advisory (it must never redden a required
# migration gate), which means a script that cannot start would degrade to a
# warning annotation nobody reads — the exact silent-failure mode of #1982,
# where ESM module resolution was broken from 2026-06-09 and the check stayed
# green across 47 migrations. This job re-runs that startup path with no
# database and no AWS spend, and it FAILS on regression.
#
# Coverage note: `migrate-dryrun` is gated on the `db-init` paths filter, which
# already covers both `shared/database/src/schema.ts` and
# `shared/database/src/migrations/**`. So every PR that can introduce a new
# constraint still gets a real report. The narrowing is limited to edits of the
# probe's own machinery (this file and the script), which change no constraint
# and now get this self-test instead of a live probe.
on:
pull_request:
branches:
- main
paths:
- 'shared/database/src/schema.ts'
- 'shared/database/src/migrations/*.sql'
- '.github/workflows/schema-shape-report.yml'
- 'scripts/schema-shape-report.mjs'
# No `pull-requests: write` — this job posts nothing. The comment is owned by
# the migrate-dryrun job in test.yml.
permissions:
contents: read
concurrency:
group: schema-shape-self-test-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
self-test:
name: Schema shape probe self-test
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout PR head with merge-base history
uses: actions/checkout@v7
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Set up Node
uses: actions/setup-node@v7
with:
node-version: '24'
cache: 'npm'
- name: Install postgres client
# KEEP IN SYNC with the `Install minimal deps for dryrun script` step
# in test.yml's migrate-dryrun job. The whole point of this self-test
# is to exercise the same module-resolution path the real probe uses;
# if the two installs diverge, this job starts testing something else
# and #1982 can recur undetected.
#
# `--workspaces=false` skips workspace resolution, but npm still reads
# the root manifest and resolves the `@wxyc/shared` dependency against
# GitHub Packages, so NPM_TOKEN is required (verified: without it the
# install dies with E401 on `@wxyc%2fshared`). This replaces the old
# isolated-install-plus-symlink workaround, which existed only because
# Node's ESM loader ignores NODE_PATH.
run: npm install --workspaces=false --no-save --no-package-lock --no-audit --no-fund postgres drizzle-orm
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Verify postgres module resolves (ESM smoke check)
# The #1982 regression detector proper. Node's ESM resolver walks up
# parent node_modules dirs from the importing file, so this is a real
# test of the install above rather than a tautology.
run: |
node -e "
import('postgres')
.then(() => { console.log('postgres module resolves OK'); })
.catch((err) => {
console.error('postgres module failed to resolve via ESM import:', err.message);
process.exit(1);
});
"
- name: Run the report script end-to-end with no probe target
# Exercises everything the live probe does except the SQL round-trip:
# module load, diff parse, all five constraint detectors, SELECT
# generation and markdown rendering. With no probe target configured
# the script must still exit 0 and emit a marker-bearing comment body
# (#703's never-throw contract). A non-zero exit or a missing marker
# means the script is broken and the real probe would silently produce
# nothing — which is the failure this job exists to catch.
run: |
set -euo pipefail
mkdir -p .cache
BASE_REF='${{ github.event.pull_request.base.ref }}'
# Full fetch (no --depth=1) plus an explicit merge-base, matching the
# probe step in test.yml. The original workflow shallow-fetched the
# base and used `git diff origin/main...HEAD`, which dies with
# "no merge base" whenever the branch is behind main — a depth-1
# fetch gets main's tip and nothing else, so there is no common
# ancestor to find. That went unnoticed because the diff was
# suffixed `|| true`, silently yielding an EMPTY diff and a
# "no schema or migration changes detected" comment.
git fetch --no-tags origin "$BASE_REF"
MERGE_BASE=$(git merge-base FETCH_HEAD HEAD)
git diff "$MERGE_BASE" HEAD -- \
'shared/database/src/schema.ts' \
'shared/database/src/migrations/*.sql' \
> .cache/pr.diff
node scripts/schema-shape-report.mjs --diff .cache/pr.diff > .cache/comment.md
echo '--- rendered comment (not posted) ---'
cat .cache/comment.md
if ! grep -q '<!-- wxyc-schema-shape-report -->' .cache/comment.md; then
echo 'schema-shape-report.mjs produced no marker-bearing comment body' >&2
exit 1
fi