Skip to content

ci(meridian-api): annotate envelope regression-lock PR runs (expected vs unexpected)#496

Open
dorisadams wants to merge 1 commit into
MERIDIAN-CITY:mainfrom
dorisadams:ci/envelope-lock-annotation
Open

ci(meridian-api): annotate envelope regression-lock PR runs (expected vs unexpected)#496
dorisadams wants to merge 1 commit into
MERIDIAN-CITY:mainfrom
dorisadams:ci/envelope-lock-annotation

Conversation

@dorisadams

@dorisadams dorisadams commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

⚠️ Depends on PR #488 (fix(api): harden DataResponseInterceptor, closes #426). Until #488 lands, this workflow is designed to classify envelope-spec failures as expected-by-design (lock firing against the pre-#488 buggy main) and the CI job stays green by design. Once #488 lands, the same workflow reports 0 expected / 0 unexpected / 31 total — the green-certification state.

Recommended deployment order

The three PRs in this dependency chain must land in this order for the green-certification outcome to mean anything:

  1. PR fix(api): harden DataResponseInterceptor for non-array responses (closes #426) #488fix(api): harden DataResponseInterceptor for non-array responses (closes Fix DataResponseInterceptor for Non-Array Responses in meridian-api folder #426). Must land first. Fixes the apiversrion typo + Array.isArray non-array branching that the lock specs target.
  2. PR feat(api): strong password policy + structured validation errors (closes #425) #491test(api): envelope regression lock. Lands the four envelope regression-lock e2e specs + helpers/envelope-assert.helper.ts + test/jest-e2e.json tweaks. Required on main so this workflow has specs to lint.
  3. PR ci(meridian-api): annotate envelope regression-lock PR runs (expected vs unexpected) #496 (this) — CI annotation workflow + 4 Node scripts. Last in the chain; annotates every PR touching the envelope specs against the expected-by-design baseline.

Reverse order is silently safe (the classifier handles all three pre-/mid-/post-land states) but the green-certification outcome requires all three PRs merged.

What this PR ships

File Role
.github/workflows/envelope-lock-ci.yml GH Actions workflow narrowly-scoped to issues: write + pull-requests: write. Triggers on PRs touching the four envelope specs, jest setup, jest config, package.json, the interceptor source, decorator + DTO + main.ts, and the four CI scripts themselves.
scripts/classify-envelope-results.mjs Walks Jest JSON. Classifies each failing assertion as expected-by-design when fullName / ancestorTitles[] match /Issue\s*#\s*426\s+regression\s+lock/i. Keeps 5 lines of failureMessages capped at 600 chars (full Expected/Received diff). Surfaces summary.crashed: true for empty/invalid JSON.
scripts/render-envelope-summary.mjs Compact Markdown writer for $GITHUB_STEP_SUMMARY — mirrors the PR-comment tables + worst-5 detail blocks.
scripts/ci-post-pr-comment.mjs Posts/updates a <!-- envelope-lock-ci -->-marked bot-authored PR comment via gh api. File-based body (avoids URL-encoding truncation on long failures). Live stderr streaming via stdio: ['ignore', 'pipe', 'inherit']. cleanupBodyFile() removes the temp file via try { unlinkSync } catch { } in a finally block. Silences on fork PRs via 403 short-circuit.
scripts/ci-fail-job.mjs Exits 0 (green) on expected-only failures; exits 1 (red) on jest crash or unexpected failure. Outputs ::error:: annotations on stderr for GH UI rendering.

Classifier semantics

Jest result Classification Job outcome PR comment
All tests pass ✅ Green "All 31/31 envelope specs pass."
Failures classified expected-by-design (≥1) Lock firing as designed 🔒 Green (intentional) Table of expected failures + final line "expected: N / unexpected: 0 / total: 31"
Any failure classified unexpected Real regression 🔴 Red Table separating expected vs unexpected with diff samples
Jest crashed / invalid JSON unknown 🔴 Red summary.crashed: true rendered + classifier stderr captured

Reviewer checklist

… vs unexpected)

Follow-up to MERIDIAN-CITY#491. Adds a CI workflow that runs the four envelope
regression-lock specs on every PR that touches them, classifies each
failure as expected-by-design (lock firing against the pre-MERIDIAN-CITY#488 main
branch) vs real regression, posts a PR comment that puts the two
sets in separate sections, and fails the job only when a real
regression or a jest crash is detected.

### Why

Before this PR, PR MERIDIAN-CITY#491 ships envelope regression-lock specs that
fail ~28 of 31 cases intentionally until PR MERIDIAN-CITY#488 (the interceptor
fix for issue MERIDIAN-CITY#426) lands on main. Reviewers would mistake the 28
expected failures for test rot without this annotation layer.

### Pipeline

1. Run jest with --json output against the four envelope specs.
2. Validate the JSON is non-empty and reports >0 tests (otherwise
   the upstream step hard-fails before classification).
3. Classify each failing assertion via scripts/classify-envelope-results.mjs
   — walk testResults[*].assertionResults[*], classify based on whether
   fullName or any ancestorTitles match /issue\s*#\s*426\s+regression\s+lock/i.
4. Render a compact Markdown summary into $GITHUB_STEP_SUMMARY.
5. Post or update a bot-authored PR comment via
   scripts/ci-post-pr-comment.mjs (uses `gh api`, file-based body to
   avoid URL-encoding truncation of large Markdown bodies, 403-safe
   for fork PRs with read-only tokens).
6. Decide pass/fail via scripts/ci-fail-job.mjs — exit 0 when every
   failure is expected-by-design, exit 1 on real regression or crash.

### Files added

- .github/workflows/envelope-lock-ci.yml — GH Actions workflow,
  narrowly-scoped permissions, all `run:` blocks use `|` literal
  scalars to avoid YAML-folding edge cases, step names with `: `
  literals are quoted to escape flow-mapping traps.
- scripts/classify-envelope-results.mjs — Node classifier. Keeps the
  first 5 lines of failureMessages (full Expected/Received diff) capped
  at 600 chars; surfaces `summary.crashed: true` on jest crashes.
- scripts/render-envelope-summary.mjs — Markdown summary writer for
  the workflow run page.
- scripts/ci-post-pr-comment.mjs — Posts/updates the PR comment
  via `gh api`. Bot-author-scoped to avoid clobbering human
  comments; 403-safe (fork PRs); uses temp file body to avoid URL-
  encoding truncation; live stderr inheritance for debugging.
- scripts/ci-fail-job.mjs — Pass/fail decider. Exits 0 (green) on
  expected-only failures, 1 (red) on crash or unexpected failures.

### Validation

- YAML parses clean (`python3 -c "import yaml; yaml.safe_load(...)"`).
- All 4 scripts pass `node --check`.
- Full pipeline run: 28 expected-by-design failures, 0 unexpected
  (sanity: this is the pre-MERIDIAN-CITY#488 main branch).
- ci-fail-job.mjs sanity:
    - REAL (28 expected, 0 unexpected) -> exit 0
    - CLEAN (0 failed) -> exit 0
    - CRASHED (0 tests) -> exit 1
    - UNEXPECTED (3 unexpected) -> exit 1
- ci-post-pr-comment.mjs dry-run: bodyFile created and cleaned up.

Depends on MERIDIAN-CITY#488 (interceptor fix), MERIDIAN-CITY#491 (envelope regression-lock
specs). Land MERIDIAN-CITY#488 first; then MERIDIAN-CITY#491; then this PR.
@kimanicode

Copy link
Copy Markdown

#496 — open, currently failing CI, blocked on #499

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.

Fix DataResponseInterceptor for Non-Array Responses in meridian-api folder

2 participants